Merge branch 'wwitzel3-ww/415'
| | |
| | | Features |
| | | -------- |
| | | |
| | | - The ``scan`` method of a ``Configurator`` can be passed an ``ignore`` |
| | | argument, which can be a string, a callable, or a list consisting of |
| | | strings and/or callables. This feature allows submodules, subpackages, and |
| | | global objects from being scanned. See |
| | | http://readthedocs.org/docs/venusian/en/latest/#ignore-scan-argument for |
| | | more information about how to use the ``ignore`` argument to ``scan``. |
| | | |
| | | - Better error messages when a view callable returns a value that cannot be |
| | | converted to a response (for example, when a view callable returns a |
| | | dictionary without a renderer defined, or doesn't return any value at all). |
| | | The error message now contains information about the view callable itself |
| | | as well as the result of calling it. |
| | | |
| | | Dependencies |
| | | ------------ |
| | | |
| | | - Depend on ``venusian`` >= 1.0a3 to provide scan ``ignore`` support. |
| | | |
| | | 1.3a7 (2012-02-07) |
| | | ================== |
| | | |
| | | Features |
| | | -------- |
| | | |
| | | - More informative error message when a ``config.include`` cannot find an |
| | | ``includeme``. See https://github.com/Pylons/pyramid/pull/392. |
| | | |
| | | - Internal: catch unhashable discriminators early (raise an error instead of |
| | | allowing them to find their way into resolveConflicts). |
| | | |
| | | - The `match_param` view predicate now accepts a string or a tuple. |
| | | This replaces the broken behavior of accepting a dict. See |
| | | https://github.com/Pylons/pyramid/issues/425 for more information. |
| | | |
| | | Bug Fixes |
| | | --------- |
| | |
| | | - The ``prequest`` script would fail when used against URLs which did not |
| | | return HTML or text. See https://github.com/Pylons/pyramid/issues/381 |
| | | |
| | | Backwards Incompatibilities |
| | | --------------------------- |
| | | |
| | | - The `match_param` view predicate no longer accepts a dict. This will |
| | | have no negative affect because the implementation was broken for |
| | | dict-based arguments. |
| | | |
| | | Documentation |
| | | ------------- |
| | | |
| | |
| | | |
| | | .. autointerface:: IActionInfo |
| | | :members: |
| | | |
| | | .. autointerface:: IAssetDescriptor |
| | | :members: |
| | |
| | | # other places throughout the built documents. |
| | | # |
| | | # The short X.Y version. |
| | | version = '1.3a6' |
| | | version = '1.3a7' |
| | | |
| | | # The full version, including alpha/beta/rc tags. |
| | | release = version |
| | |
| | | |
| | | .. code-block:: text |
| | | |
| | | $ Scripts\pcreate alchemy MyProject |
| | | $ Scripts\pcreate -s alchemy MyProject |
| | | |
| | | Here's sample output from a run of ``pcreate`` on UNIX for a project we name |
| | | ``MyProject``: |
| | |
| | | |
| | | .. code-block:: text |
| | | |
| | | foo/1/2/ -> {'baz':u'1', 'bar':u'2', 'fizzle':()} |
| | | foo/abc/def/a/b/c -> {'baz':u'abc', 'bar':u'def', 'fizzle': u'a/b/c')} |
| | | foo/1/2/ -> {'baz':u'1', 'bar':u'2', 'fizzle':u''} |
| | | foo/abc/def/a/b/c -> {'baz':u'abc', 'bar':u'def', 'fizzle': u'a/b/c'} |
| | | |
| | | This occurs because the default regular expression for a marker is ``[^/]+`` |
| | | which will match everything up to the first ``/``, while ``{fizzle:.*}`` will |
| | |
| | | Python 3 Compatibility |
| | | ~~~~~~~~~~~~~~~~~~~~~~ |
| | | |
| | | Pyramid is now Python 3 compatible. Python 3.2 or better is required. |
| | | In addition to running on Python 2 (version 2.6 or 2.7 required), Pyramid is |
| | | now Python 3 compatible. For Python 3 compatibility, Python 3.2 or better |
| | | is required. |
| | | |
| | | .. warning:: |
| | | |
| | |
| | | http://www.python.org/dev/peps/pep-0333/#optional-platform-specific-file-handling) |
| | | when one is provided by the web server. |
| | | |
| | | - The :meth:`pyramid.config.Configurator.scan` method can be passed an |
| | | ``ignore`` argument, which can be a string, a callable, or a list |
| | | consisting of strings and/or callables. This feature allows submodules, |
| | | subpackages, and global objects from being scanned. See |
| | | http://readthedocs.org/docs/venusian/en/latest/#ignore-scan-argument for |
| | | more information about how to use the ``ignore`` argument to ``scan``. |
| | | |
| | | Backwards Incompatibilities |
| | | --------------------------- |
| | | |
| | |
| | | (indeterminate value based on Python 3 vs. Python 2). This has to be done |
| | | to normalize matching on Python 2 and Python 3. |
| | | |
| | | - The ``match_param`` view predicate no longer accepts a dict. This will have |
| | | no negative affect because the implementation was broken for dict-based |
| | | arguments. |
| | | |
| | | Documentation Enhancements |
| | | -------------------------- |
| | | |
| | |
| | | ``extra`` provides a facility for inserting extra keys and values |
| | | into an action dictionary. |
| | | """ |
| | | # catch nonhashable discriminators here; most unit tests use |
| | | # autocommit=False, which won't catch unhashable discriminators |
| | | assert hash(discriminator) |
| | | |
| | | if kw is None: |
| | | kw = {} |
| | | |
| | |
| | | return self.manager.pop() |
| | | |
| | | # this is *not* an action method (uses caller_package) |
| | | def scan(self, package=None, categories=None, onerror=None, **kw): |
| | | def scan(self, package=None, categories=None, onerror=None, ignore=None, |
| | | **kw): |
| | | """Scan a Python package and any of its subpackages for objects |
| | | marked with :term:`configuration decoration` such as |
| | | :class:`pyramid.view.view_config`. Any decorated object found will |
| | |
| | | :term:`Venusian` documentation for more information about ``onerror`` |
| | | callbacks. |
| | | |
| | | The ``ignore`` argument, if provided, should be a Venusian ``ignore`` |
| | | value. Providing an ``ignore`` argument allows the scan to ignore |
| | | particular modules, packages, or global objects during a scan. |
| | | ``ignore`` can be a string or a callable, or a list containing |
| | | strings or callables. The simplest usage of ``ignore`` is to provide |
| | | a module or package by providing a full path to its dotted name. For |
| | | example: ``config.scan(ignore='my.module.subpackage')`` would ignore |
| | | the ``my.module.subpackage`` package during a scan, which would |
| | | prevent the subpackage and any of its submodules from being imported |
| | | and scanned. See the :term:`Venusian` documentation for more |
| | | information about the ``ignore`` argument. |
| | | |
| | | .. note:: the ``ignore`` argument is new in Pyramid 1.3. |
| | | |
| | | To perform a ``scan``, Pyramid creates a Venusian ``Scanner`` object. |
| | | The ``kw`` argument represents a set of keyword arguments to pass to |
| | | the Venusian ``Scanner`` object's constructor. See the |
| | |
| | | ctorkw.update(kw) |
| | | |
| | | scanner = self.venusian.Scanner(**ctorkw) |
| | | scanner.scan(package, categories=categories, onerror=onerror) |
| | | |
| | | scanner.scan(package, categories=categories, onerror=onerror, |
| | | ignore=ignore) |
| | | |
| | | def make_wsgi_app(self): |
| | | """ Commits any pending configuration statements, sends a |
| | |
| | | from pyramid.interfaces import IActionInfo |
| | | |
| | | from pyramid.compat import ( |
| | | string_types, |
| | | bytes_, |
| | | is_nonstr_iter, |
| | | ) |
| | |
| | | h.update(bytes_('request_type:%r' % hash(request_type))) |
| | | |
| | | if match_param is not None: |
| | | if isinstance(match_param, string_types): |
| | | match_param, match_param_val = match_param.split('=', 1) |
| | | match_param = {match_param: match_param_val} |
| | | text = "match_param %s" % match_param |
| | | if not is_nonstr_iter(match_param): |
| | | match_param = (match_param,) |
| | | match_param = sorted(match_param) |
| | | text = "match_param %s" % repr(match_param) |
| | | reqs = [p.split('=', 1) for p in match_param] |
| | | def match_param_predicate(context, request): |
| | | for k, v in match_param.items(): |
| | | for k, v in reqs: |
| | | if request.matchdict.get(k) != v: |
| | | return False |
| | | return True |
| | | match_param_predicate.__text__ = text |
| | | weights.append(1 << 9) |
| | | predicates.append(match_param_predicate) |
| | | h.update(bytes_('match_param:%r' % match_param)) |
| | | for p in match_param: |
| | | h.update(bytes_('match_param:%r' % p)) |
| | | |
| | | if custom: |
| | | for num, predicate in enumerate(custom): |
| | |
| | | from pyramid.static import static_view |
| | | from pyramid.threadlocal import get_current_registry |
| | | from pyramid.view import render_view_to_response |
| | | from pyramid.util import object_description |
| | | |
| | | from pyramid.config.util import ( |
| | | DEFAULT_PHASH, |
| | |
| | | |
| | | urljoin = urlparse.urljoin |
| | | url_parse = urlparse.urlparse |
| | | |
| | | def view_description(view): |
| | | try: |
| | | return view.__text__ |
| | | except AttributeError: |
| | | return object_description(view) |
| | | |
| | | def wraps_view(wrapper): |
| | | def inner(self, view): |
| | |
| | | # "wrapped view" |
| | | for attr in ('__permitted__', '__call_permissive__', '__permission__', |
| | | '__predicated__', '__predicates__', '__accept__', |
| | | '__order__'): |
| | | '__order__', '__text__'): |
| | | try: |
| | | setattr(wrapper, attr, getattr(view, attr)) |
| | | except AttributeError: |
| | |
| | | result = view(context, request) |
| | | response = registry.queryAdapterOrSelf(result, IResponse) |
| | | if response is None: |
| | | raise ValueError( |
| | | 'Could not convert view return value "%s" into a ' |
| | | 'response object' % (result,)) |
| | | if result is None: |
| | | append = (' You may have forgotten to return a value from ' |
| | | 'the view callable.') |
| | | elif isinstance(result, dict): |
| | | append = (' You may have forgotten to define a renderer in ' |
| | | 'the view configuration.') |
| | | else: |
| | | append = '' |
| | | msg = ('Could not convert return value of the view callable %s ' |
| | | 'into a response object. ' |
| | | 'The value returned was %r.' + append) |
| | | |
| | | raise ValueError(msg % (view_description(view), result)) |
| | | return response |
| | | |
| | | return viewresult_to_response |
| | |
| | | mapped_view = self.map_class_requestonly(view) |
| | | else: |
| | | mapped_view = self.map_class_native(view) |
| | | mapped_view.__text__ = 'method %s of %s' % ( |
| | | self.attr or '__call__', object_description(view)) |
| | | return mapped_view |
| | | |
| | | def map_nonclass(self, view): |
| | |
| | | mapped_view = self.map_nonclass_requestonly(view) |
| | | elif self.attr: |
| | | mapped_view = self.map_nonclass_attr(view) |
| | | if self.attr is not None: |
| | | mapped_view.__text__ = 'attr %s of %s' % ( |
| | | self.attr, object_description(view)) |
| | | else: |
| | | mapped_view.__text__ = object_description(view) |
| | | return mapped_view |
| | | |
| | | def map_class_requestonly(self, view): |
| | |
| | | |
| | | .. note:: This feature is new as of :app:`Pyramid` 1.2. |
| | | |
| | | This param may be either a single string of the format "key=value" |
| | | or a dict of key/value pairs. |
| | | This value can be a string of the format "key=value" or a tuple |
| | | containing one or more of these strings. |
| | | |
| | | A view declaration with this argument ensures that the view will |
| | | only be called when the :term:`request` has key/value pairs in its |
| | | :term:`matchdict` that equal those supplied in the predicate. |
| | | e.g. ``match_param="action=edit" would require the ``action`` |
| | | parameter in the :term:`matchdict` match the right hande side of |
| | | parameter in the :term:`matchdict` match the right hand side of |
| | | the expression (``edit``) for the view to "match" the current |
| | | request. |
| | | |
| | | If the ``match_param`` is a dict, every key/value pair must match |
| | | If the ``match_param`` is a tuple, every key/value pair must match |
| | | for the predicate to pass. |
| | | |
| | | containment |
| | |
| | | """ |
| | | Resolve the asset spec named as ``spec`` to an object that has the |
| | | attributes and methods described in |
| | | `pyramid.interfaces.IAssetDescriptor`. |
| | | :class:`pyramid.interfaces.IAssetDescriptor`. |
| | | |
| | | If ``spec`` is an absolute filename |
| | | (e.g. ``/path/to/myproject/templates/foo.pt``) or an absolute asset |
| | |
| | | result = render_view_to_response(ctx, req, 'pod_notinit') |
| | | self.assertEqual(result, None) |
| | | |
| | | def test_scan_integration_with_ignore(self): |
| | | from zope.interface import alsoProvides |
| | | from pyramid.interfaces import IRequest |
| | | from pyramid.view import render_view_to_response |
| | | import pyramid.tests.test_config.pkgs.scannable as package |
| | | config = self._makeOne(autocommit=True) |
| | | config.scan(package, |
| | | ignore='pyramid.tests.test_config.pkgs.scannable.another') |
| | | |
| | | ctx = DummyContext() |
| | | req = DummyRequest() |
| | | alsoProvides(req, IRequest) |
| | | req.registry = config.registry |
| | | |
| | | req.method = 'GET' |
| | | result = render_view_to_response(ctx, req, '') |
| | | self.assertEqual(result, 'grokked') |
| | | |
| | | # ignored |
| | | v = render_view_to_response(ctx, req, 'another_stacked_class2') |
| | | self.assertEqual(v, None) |
| | | |
| | | def test_scan_integration_dottedname_package(self): |
| | | from zope.interface import alsoProvides |
| | | from pyramid.interfaces import IRequest |
| | |
| | | self.assertEqual(predicates[5].__text__, 'accept = accept') |
| | | self.assertEqual(predicates[6].__text__, 'containment = containment') |
| | | self.assertEqual(predicates[7].__text__, 'request_type = request_type') |
| | | self.assertEqual(predicates[8].__text__, "match_param {'foo': 'bar'}") |
| | | self.assertEqual(predicates[8].__text__, "match_param ['foo=bar']") |
| | | self.assertEqual(predicates[9].__text__, 'custom predicate') |
| | | self.assertEqual(predicates[10].__text__, 'classmethod predicate') |
| | | self.assertEqual(predicates[11].__text__, '<unknown custom predicate>') |
| | |
| | | self.assertFalse(predicates[0](Dummy(), request)) |
| | | |
| | | def test_match_param_from_dict(self): |
| | | _, predicates, _ = self._callFUT(match_param={'foo':'bar','baz':'bum'}) |
| | | _, predicates, _ = self._callFUT(match_param=('foo=bar','baz=bum')) |
| | | request = DummyRequest() |
| | | request.matchdict = {'foo':'bar', 'baz':'bum'} |
| | | self.assertTrue(predicates[0](Dummy(), request)) |
| | | |
| | | def test_match_param_from_dict_fails(self): |
| | | _, predicates, _ = self._callFUT(match_param={'foo':'bar','baz':'bum'}) |
| | | _, predicates, _ = self._callFUT(match_param=('foo=bar','baz=bum')) |
| | | request = DummyRequest() |
| | | request.matchdict = {'foo':'bar', 'baz':'foo'} |
| | | self.assertFalse(predicates[0](Dummy(), request)) |
| | |
| | | hash2, _, __= self._callFUT(request_method='GET') |
| | | self.assertEqual(hash1, hash2) |
| | | |
| | | def test_match_param_hashable(self): |
| | | # https://github.com/Pylons/pyramid/issues/425 |
| | | import pyramid.testing |
| | | def view(request): pass |
| | | config = pyramid.testing.setUp(autocommit=False) |
| | | config.add_route('foo', '/foo/{a}/{b}') |
| | | config.add_view(view, route_name='foo', match_param='a=bar') |
| | | config.add_view(view, route_name='foo', match_param=('a=bar', 'b=baz')) |
| | | config.commit() |
| | | |
| | | class TestActionInfo(unittest.TestCase): |
| | | def _getTargetClass(self): |
| | | from pyramid.config.util import ActionInfo |
| | |
| | | self.config.registry.registerUtility(policy, IAuthenticationPolicy) |
| | | self.config.registry.registerUtility(policy, IAuthorizationPolicy) |
| | | |
| | | def test_function_returns_non_adaptable(self): |
| | | def view(request): |
| | | return None |
| | | deriver = self._makeOne() |
| | | result = deriver(view) |
| | | self.assertFalse(result is view) |
| | | try: |
| | | result(None, None) |
| | | except ValueError as e: |
| | | self.assertEqual( |
| | | e.args[0], |
| | | 'Could not convert return value of the view callable function ' |
| | | 'pyramid.tests.test_config.test_views.view into a response ' |
| | | 'object. The value returned was None. You may have forgotten ' |
| | | 'to return a value from the view callable.' |
| | | ) |
| | | else: # pragma: no cover |
| | | raise AssertionError |
| | | |
| | | def test_function_returns_non_adaptable_dict(self): |
| | | def view(request): |
| | | return {'a':1} |
| | | deriver = self._makeOne() |
| | | result = deriver(view) |
| | | self.assertFalse(result is view) |
| | | try: |
| | | result(None, None) |
| | | except ValueError as e: |
| | | self.assertEqual( |
| | | e.args[0], |
| | | "Could not convert return value of the view callable function " |
| | | "pyramid.tests.test_config.test_views.view into a response " |
| | | "object. The value returned was {'a': 1}. You may have " |
| | | "forgotten to define a renderer in the view configuration." |
| | | ) |
| | | else: # pragma: no cover |
| | | raise AssertionError |
| | | |
| | | def test_instance_returns_non_adaptable(self): |
| | | class AView(object): |
| | | def __call__(self, request): |
| | | return None |
| | | view = AView() |
| | | deriver = self._makeOne() |
| | | result = deriver(view) |
| | | self.assertFalse(result is view) |
| | | try: |
| | | result(None, None) |
| | | except ValueError as e: |
| | | msg = e.args[0] |
| | | self.assertTrue(msg.startswith( |
| | | 'Could not convert return value of the view callable object ' |
| | | '<pyramid.tests.test_config.test_views.AView object at')) |
| | | self.assertTrue(msg.endswith( |
| | | '> into a response object. The value returned was None. You ' |
| | | 'may have forgotten to return a value from the view callable.')) |
| | | else: # pragma: no cover |
| | | raise AssertionError |
| | | |
| | | def test_requestonly_default_method_returns_non_adaptable(self): |
| | | request = DummyRequest() |
| | | class AView(object): |
| | | def __init__(self, request): |
| | | pass |
| | | def __call__(self): |
| | | return None |
| | | deriver = self._makeOne() |
| | | result = deriver(AView) |
| | | self.assertFalse(result is AView) |
| | | try: |
| | | result(None, request) |
| | | except ValueError as e: |
| | | self.assertEqual( |
| | | e.args[0], |
| | | 'Could not convert return value of the view callable ' |
| | | 'method __call__ of ' |
| | | 'class pyramid.tests.test_config.test_views.AView into a ' |
| | | 'response object. The value returned was None. You may have ' |
| | | 'forgotten to return a value from the view callable.' |
| | | ) |
| | | else: # pragma: no cover |
| | | raise AssertionError |
| | | |
| | | def test_requestonly_nondefault_method_returns_non_adaptable(self): |
| | | request = DummyRequest() |
| | | class AView(object): |
| | | def __init__(self, request): |
| | | pass |
| | | def theviewmethod(self): |
| | | return None |
| | | deriver = self._makeOne(attr='theviewmethod') |
| | | result = deriver(AView) |
| | | self.assertFalse(result is AView) |
| | | try: |
| | | result(None, request) |
| | | except ValueError as e: |
| | | self.assertEqual( |
| | | e.args[0], |
| | | 'Could not convert return value of the view callable ' |
| | | 'method theviewmethod of ' |
| | | 'class pyramid.tests.test_config.test_views.AView into a ' |
| | | 'response object. The value returned was None. You may have ' |
| | | 'forgotten to return a value from the view callable.' |
| | | ) |
| | | else: # pragma: no cover |
| | | raise AssertionError |
| | | |
| | | def test_requestonly_function(self): |
| | | response = DummyResponse() |
| | | def view(request): |
| | |
| | | view_attr='attr') |
| | | self.assertEqual(config.view_kw['attr'], 'attr') |
| | | |
| | | class Test_view_description(unittest.TestCase): |
| | | def _callFUT(self, view): |
| | | from pyramid.config.views import view_description |
| | | return view_description(view) |
| | | |
| | | def test_with_text(self): |
| | | def view(): pass |
| | | view.__text__ = 'some text' |
| | | result = self._callFUT(view) |
| | | self.assertEqual(result, 'some text') |
| | | |
| | | def test_without_text(self): |
| | | def view(): pass |
| | | result = self._callFUT(view) |
| | | self.assertEqual(result, |
| | | 'function pyramid.tests.test_config.test_views.view') |
| | | |
| | | |
| | | class DummyRegistry: |
| | | pass |
| | | |
| | |
| | | 'repoze.lru >= 0.4', # py3 compat |
| | | 'zope.interface >= 3.8.0', # has zope.interface.registry |
| | | 'zope.deprecation >= 3.5.0', # py3 compat |
| | | 'venusian >= 1.0a1', # ``onerror`` |
| | | 'venusian >= 1.0a3', # ``ignore`` |
| | | 'translationstring >= 0.4', # py3 compat |
| | | 'PasteDeploy >= 1.5.0', # py3 compat |
| | | ] |
| | |
| | | ]) |
| | | |
| | | setup(name='pyramid', |
| | | version='1.3a6', |
| | | version='1.3a7', |
| | | description=('The Pyramid web application development framework, a ' |
| | | 'Pylons project'), |
| | | long_description=README + '\n\n' + CHANGES, |
| | |
| | | repoze.sphinx.autointerface |
| | | WebTest |
| | | virtualenv |
| | | venusian>=1.0a3 |
| | | |
| | | [testenv:py32] |
| | | commands = |
| | |
| | | deps = |
| | | WebTest |
| | | virtualenv |
| | | venusian>=1.0a3 |
| | | |
| | | [testenv:cover] |
| | | basepython = |
| | |
| | | WebTest |
| | | repoze.sphinx.autointerface |
| | | virtualenv |
| | | venusian>=1.0a3 |
| | | nose |
| | | coverage==3.4 |
| | | nosexcover |