diff options
| -rw-r--r-- | CHANGES.txt | 11 | ||||
| -rw-r--r-- | CONTRIBUTORS.txt | 2 | ||||
| -rw-r--r-- | docs/glossary.rst | 2 | ||||
| -rw-r--r-- | docs/narr/paste.rst | 2 | ||||
| -rw-r--r-- | pyramid/events.py | 25 | ||||
| -rw-r--r-- | pyramid/response.py | 30 | ||||
| -rw-r--r-- | pyramid/tests/test_events.py | 15 | ||||
| -rw-r--r-- | pyramid/tests/test_response.py | 21 | ||||
| -rw-r--r-- | pyramid/tests/test_view.py | 65 | ||||
| -rw-r--r-- | pyramid/view.py | 53 |
10 files changed, 194 insertions, 32 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index dd1f3ea52..986274d86 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -4,6 +4,17 @@ unreleased Features -------- +- Add a ``_depth`` and ``_category`` arguments to all of the venusian + decorators. The ``_category`` argument can be used to affect which actions + are registered when performing a ``config.scan(..., category=...)`` with a + specific category. The ``_depth`` argument should be used when wrapping + the decorator in your own. This change affects ``pyramid.view.view_config``, + ``pyramid.view.exception_view_config``, + ``pyramid.view.forbidden_view_config``, ``pyramid.view.notfound_view_config``, + ``pyramid.events.subscriber`` and ``pyramid.response.response_adapter`` + decorators. See https://github.com/Pylons/pyramid/pull/3105 and + https://github.com/Pylons/pyramid/pull/3122 + Bug Fixes --------- diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 242fbbcda..062dcafd7 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -308,3 +308,5 @@ Contributors - Volker Diels-Grabsch, 2017/06/09 - Denis Rykov, 2017/06/15 + +- Tosh Lyons, 2017/06/27 diff --git a/docs/glossary.rst b/docs/glossary.rst index e80b31deb..88be7f51d 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -361,7 +361,7 @@ Glossary library created by Ian Bicking. PasteDeploy - `PasteDeploy <http://pythonpaste.org/deploy/>`_ is a library used by + `PasteDeploy <https://pastedeploy.readthedocs.io/en/latest/>`_ is a library used by :app:`Pyramid` which makes it possible to configure :term:`WSGI` components together declaratively within an ``.ini`` file. It was developed by Ian Bicking. diff --git a/docs/narr/paste.rst b/docs/narr/paste.rst index 26cb1bfa5..c02036f69 100644 --- a/docs/narr/paste.rst +++ b/docs/narr/paste.rst @@ -21,7 +21,7 @@ debugging an application. This chapter is not a replacement for documentation about PasteDeploy; it only contextualizes the use of PasteDeploy within Pyramid. For detailed -documentation, see http://pythonpaste.org/deploy/. +documentation, see https://pastedeploy.readthedocs.io/en/latest/. PasteDeploy ----------- diff --git a/pyramid/events.py b/pyramid/events.py index 35da2fa6f..d3b068bd3 100644 --- a/pyramid/events.py +++ b/pyramid/events.py @@ -68,12 +68,34 @@ class subscriber(object): :ref:`subscriber_predicates` for a description of how predicates can narrow the set of circumstances in which a subscriber will be called. + Two additional keyword arguments which will be passed to the + :term:`venusian` ``attach`` function are ``_depth`` and ``_category``. + + ``_depth`` is provided for people who wish to reuse this class from another + decorator. The default value is ``0`` and should be specified relative to + the ``subscriber`` invocation. It will be passed in to the + :term:`venusian` ``attach`` function as the depth of the callstack when + Venusian checks if the decorator is being used in a class or module + context. It's not often used, but it can be useful in this circumstance. + + ``_category`` sets the decorator category name. It can be useful in + combination with the ``category`` argument of ``scan`` to control which + views should be processed. + + See the :py:func:`venusian.attach` function in Venusian for more + information about the ``_depth`` and ``_category`` arguments. + + .. versionchanged:: 1.9.1 + Added the ``_depth`` and ``_category`` arguments. + """ venusian = venusian # for unit testing def __init__(self, *ifaces, **predicates): self.ifaces = ifaces self.predicates = predicates + self.depth = predicates.pop('_depth', 0) + self.category = predicates.pop('_category', 'pyramid') def register(self, scanner, name, wrapped): config = scanner.config @@ -81,7 +103,8 @@ class subscriber(object): config.add_subscriber(wrapped, iface, **self.predicates) def __call__(self, wrapped): - self.venusian.attach(wrapped, self.register, category='pyramid') + self.venusian.attach(wrapped, self.register, category=self.category, + depth=self.depth + 1) return wrapped @implementer(INewRequest) diff --git a/pyramid/response.py b/pyramid/response.py index 1d9daae7d..1e2546ed0 100644 --- a/pyramid/response.py +++ b/pyramid/response.py @@ -145,19 +145,43 @@ class response_adapter(object): config = Configurator() config.scan('somepackage_containing_adapters') + Two additional keyword arguments which will be passed to the + :term:`venusian` ``attach`` function are ``_depth`` and ``_category``. + + ``_depth`` is provided for people who wish to reuse this class from another + decorator. The default value is ``0`` and should be specified relative to + the ``response_adapter`` invocation. It will be passed in to the + :term:`venusian` ``attach`` function as the depth of the callstack when + Venusian checks if the decorator is being used in a class or module + context. It's not often used, but it can be useful in this circumstance. + + ``_category`` sets the decorator category name. It can be useful in + combination with the ``category`` argument of ``scan`` to control which + views should be processed. + + See the :py:func:`venusian.attach` function in Venusian for more + information about the ``_depth`` and ``_category`` arguments. + + .. versionchanged:: 1.9.1 + Added the ``_depth`` and ``_category`` arguments. + """ venusian = venusian # for unit testing - def __init__(self, *types_or_ifaces): + def __init__(self, *types_or_ifaces, **kwargs): self.types_or_ifaces = types_or_ifaces + self.depth = kwargs.pop('_depth', 0) + self.category = kwargs.pop('_category', 'pyramid') + self.kwargs = kwargs def register(self, scanner, name, wrapped): config = scanner.config for type_or_iface in self.types_or_ifaces: - config.add_response_adapter(wrapped, type_or_iface) + config.add_response_adapter(wrapped, type_or_iface, **self.kwargs) def __call__(self, wrapped): - self.venusian.attach(wrapped, self.register, category='pyramid') + self.venusian.attach(wrapped, self.register, category=self.category, + depth=self.depth + 1) return wrapped diff --git a/pyramid/tests/test_events.py b/pyramid/tests/test_events.py index 52e53c34e..4f9011cc0 100644 --- a/pyramid/tests/test_events.py +++ b/pyramid/tests/test_events.py @@ -209,7 +209,16 @@ class TestSubscriber(unittest.TestCase): def foo(): pass dec(foo) self.assertEqual(dummy_venusian.attached, - [(foo, dec.register, 'pyramid')]) + [(foo, dec.register, 'pyramid', 1)]) + + def test___call___with_venusian_args(self): + dec = self._makeOne(_category='foo', _depth=1) + dummy_venusian = DummyVenusian() + dec.venusian = dummy_venusian + def foo(): pass + dec(foo) + self.assertEqual(dummy_venusian.attached, + [(foo, dec.register, 'foo', 2)]) def test_regsister_with_predicates(self): from zope.interface import Interface @@ -308,8 +317,8 @@ class DummyVenusian(object): def __init__(self): self.attached = [] - def attach(self, wrapped, fn, category=None): - self.attached.append((wrapped, fn, category)) + def attach(self, wrapped, fn, category=None, depth=None): + self.attached.append((wrapped, fn, category, depth)) class Dummy: pass diff --git a/pyramid/tests/test_response.py b/pyramid/tests/test_response.py index ad55882c9..53e3ce17a 100644 --- a/pyramid/tests/test_response.py +++ b/pyramid/tests/test_response.py @@ -136,9 +136,9 @@ class TestResponseAdapter(unittest.TestCase): def tearDown(self): self.config.end() - def _makeOne(self, *types_or_ifaces): + def _makeOne(self, *types_or_ifaces, **kw): from pyramid.response import response_adapter - return response_adapter(*types_or_ifaces) + return response_adapter(*types_or_ifaces, **kw) def test_register_single(self): from zope.interface import Interface @@ -172,7 +172,18 @@ class TestResponseAdapter(unittest.TestCase): def foo(): pass dec(foo) self.assertEqual(dummy_venusian.attached, - [(foo, dec.register, 'pyramid')]) + [(foo, dec.register, 'pyramid', 1)]) + + def test___call___with_venusian_args(self): + from zope.interface import Interface + class IFoo(Interface): pass + dec = self._makeOne(IFoo, _category='foo', _depth=1) + dummy_venusian = DummyVenusian() + dec.venusian = dummy_venusian + def foo(): pass + dec(foo) + self.assertEqual(dummy_venusian.attached, + [(foo, dec.register, 'foo', 2)]) class TestGetResponseFactory(unittest.TestCase): @@ -199,5 +210,5 @@ class DummyVenusian(object): def __init__(self): self.attached = [] - def attach(self, wrapped, fn, category=None): - self.attached.append((wrapped, fn, category)) + def attach(self, wrapped, fn, category=None, depth=None): + self.attached.append((wrapped, fn, category, depth)) diff --git a/pyramid/tests/test_view.py b/pyramid/tests/test_view.py index e03487a70..0124ce632 100644 --- a/pyramid/tests/test_view.py +++ b/pyramid/tests/test_view.py @@ -91,6 +91,18 @@ class Test_notfound_view_config(BaseTest, unittest.TestCase): self.assertEqual(settings[0]['attr'], 'view') self.assertEqual(settings[0]['_info'], 'codeinfo') + def test_call_with_venusian_args(self): + decorator = self._makeOne(_depth=1, _category='foo') + venusian = DummyVenusian() + decorator.venusian = venusian + def foo(): pass + decorator(foo) + attachments = venusian.attachments + category = attachments[0][2] + depth = attachments[0][3] + self.assertEqual(depth, 2) + self.assertEqual(category, 'foo') + class Test_forbidden_view_config(BaseTest, unittest.TestCase): def _makeOne(self, **kw): from pyramid.view import forbidden_view_config @@ -133,6 +145,18 @@ class Test_forbidden_view_config(BaseTest, unittest.TestCase): self.assertEqual(settings[0]['attr'], 'view') self.assertEqual(settings[0]['_info'], 'codeinfo') + def test_call_with_venusian_args(self): + decorator = self._makeOne(_depth=1, _category='foo') + venusian = DummyVenusian() + decorator.venusian = venusian + def foo(): pass + decorator(foo) + attachments = venusian.attachments + category = attachments[0][2] + depth = attachments[0][3] + self.assertEqual(depth, 2) + self.assertEqual(category, 'foo') + class Test_exception_view_config(BaseTest, unittest.TestCase): def _makeOne(self, *args, **kw): from pyramid.view import exception_view_config @@ -184,6 +208,18 @@ class Test_exception_view_config(BaseTest, unittest.TestCase): self.assertEqual(settings[0]['attr'], 'view') self.assertEqual(settings[0]['_info'], 'codeinfo') + def test_call_with_venusian_args(self): + decorator = self._makeOne(_depth=1, _category='foo') + venusian = DummyVenusian() + decorator.venusian = venusian + def foo(): pass + decorator(foo) + attachments = venusian.attachments + category = attachments[0][2] + depth = attachments[0][3] + self.assertEqual(depth, 2) + self.assertEqual(category, 'foo') + class RenderViewToResponseTests(BaseTest, unittest.TestCase): def _callFUT(self, *arg, **kw): from pyramid.view import render_view_to_response @@ -564,7 +600,29 @@ class TestViewConfigDecorator(unittest.TestCase): decorator.venusian = venusian def foo(): pass decorator(foo) - self.assertEqual(venusian.depth, 2) + attachments = venusian.attachments + depth = attachments[0][3] + self.assertEqual(depth, 2) + + def test_call_withoutcategory(self): + decorator = self._makeOne() + venusian = DummyVenusian() + decorator.venusian = venusian + def foo(): pass + decorator(foo) + attachments = venusian.attachments + category = attachments[0][2] + self.assertEqual(category, 'pyramid') + + def test_call_withcategory(self): + decorator = self._makeOne(_category='not_pyramid') + venusian = DummyVenusian() + decorator.venusian = venusian + def foo(): pass + decorator(foo) + attachments = venusian.attachments + category = attachments[0][2] + self.assertEqual(category, 'not_pyramid') class Test_append_slash_notfound_view(BaseTest, unittest.TestCase): def _callFUT(self, context, request): @@ -980,8 +1038,7 @@ class DummyVenusian(object): self.attachments = [] def attach(self, wrapped, callback, category=None, depth=1): - self.attachments.append((wrapped, callback, category)) - self.depth = depth + self.attachments.append((wrapped, callback, category, depth)) return self.info class DummyRegistry(object): @@ -1008,7 +1065,7 @@ class DummyVenusianContext(object): def call_venusian(venusian, context=None): if context is None: context = DummyVenusianContext() - for wrapped, callback, category in venusian.attachments: + for wrapped, callback, category, depth in venusian.attachments: callback(context, None, None) return context.config diff --git a/pyramid/view.py b/pyramid/view.py index dc4aae3fa..46aec45f1 100644 --- a/pyramid/view.py +++ b/pyramid/view.py @@ -185,14 +185,22 @@ class view_config(object): :meth:`pyramid.config.Configurator.add_view`. If any argument is left out, its default will be the equivalent ``add_view`` default. - An additional keyword argument named ``_depth`` is provided for people who - wish to reuse this class from another decorator. The default value is - ``0`` and should be specified relative to the ``view_config`` invocation. - It will be passed in to the :term:`venusian` ``attach`` function as the - depth of the callstack when Venusian checks if the decorator is being used - in a class or module context. It's not often used, but it can be useful - in this circumstance. See the ``attach`` function in Venusian for more - information. + Two additional keyword arguments which will be passed to the + :term:`venusian` ``attach`` function are ``_depth`` and ``_category``. + + ``_depth`` is provided for people who wish to reuse this class from another + decorator. The default value is ``0`` and should be specified relative to + the ``view_config`` invocation. It will be passed in to the + :term:`venusian` ``attach`` function as the depth of the callstack when + Venusian checks if the decorator is being used in a class or module + context. It's not often used, but it can be useful in this circumstance. + + ``_category`` sets the decorator category name. It can be useful in + combination with the ``category`` argument of ``scan`` to control which + views should be processed. + + See the :py:func:`venusian.attach` function in Venusian for more + information about the ``_depth`` and ``_category`` arguments. .. seealso:: @@ -215,12 +223,13 @@ class view_config(object): def __call__(self, wrapped): settings = self.__dict__.copy() depth = settings.pop('_depth', 0) + category = settings.pop('_category', 'pyramid') def callback(context, name, ob): config = context.config.with_package(info.module) config.add_view(view=ob, **settings) - info = self.venusian.attach(wrapped, callback, category='pyramid', + info = self.venusian.attach(wrapped, callback, category=category, depth=depth + 1) if info.scope == 'class': @@ -387,10 +396,11 @@ class notfound_view_config(object): being used, :class:`~pyramid.httpexceptions.HTTPMovedPermanently will be used` for the redirect response if a slash-appended route is found. - .. versionchanged:: 1.6 - See :ref:`changing_the_notfound_view` for detailed usage information. + .. versionchanged:: 1.9.1 + Added the ``_depth`` and ``_category`` arguments. + """ venusian = venusian @@ -400,12 +410,15 @@ class notfound_view_config(object): def __call__(self, wrapped): settings = self.__dict__.copy() + depth = settings.pop('_depth', 0) + category = settings.pop('_category', 'pyramid') def callback(context, name, ob): config = context.config.with_package(info.module) config.add_notfound_view(view=ob, **settings) - info = self.venusian.attach(wrapped, callback, category='pyramid') + info = self.venusian.attach(wrapped, callback, category=category, + depth=depth + 1) if info.scope == 'class': # if the decorator was attached to a method in a class, or @@ -447,6 +460,9 @@ class forbidden_view_config(object): See :ref:`changing_the_forbidden_view` for detailed usage information. + .. versionchanged:: 1.9.1 + Added the ``_depth`` and ``_category`` arguments. + """ venusian = venusian @@ -456,12 +472,15 @@ class forbidden_view_config(object): def __call__(self, wrapped): settings = self.__dict__.copy() + depth = settings.pop('_depth', 0) + category = settings.pop('_category', 'pyramid') def callback(context, name, ob): config = context.config.with_package(info.module) config.add_forbidden_view(view=ob, **settings) - info = self.venusian.attach(wrapped, callback, category='pyramid') + info = self.venusian.attach(wrapped, callback, category=category, + depth=depth + 1) if info.scope == 'class': # if the decorator was attached to a method in a class, or @@ -503,6 +522,9 @@ class exception_view_config(object): :meth:`pyramid.view.view_config`, and each predicate argument restricts the set of circumstances under which this exception view will be invoked. + .. versionchanged:: 1.9.1 + Added the ``_depth`` and ``_category`` arguments. + """ venusian = venusian @@ -516,12 +538,15 @@ class exception_view_config(object): def __call__(self, wrapped): settings = self.__dict__.copy() + depth = settings.pop('_depth', 0) + category = settings.pop('_category', 'pyramid') def callback(context, name, ob): config = context.config.with_package(info.module) config.add_exception_view(view=ob, **settings) - info = self.venusian.attach(wrapped, callback, category='pyramid') + info = self.venusian.attach(wrapped, callback, category=category, + depth=depth + 1) if info.scope == 'class': # if the decorator was attached to a method in a class, or |
