From a196ba96fea9ee0b8db484a9562446d8ba64d9f2 Mon Sep 17 00:00:00 2001
From: Michael Merickel <michael@merickel.org>
Date: Sat, 08 Jul 2017 19:44:51 +0200
Subject: [PATCH] Merge pull request #3123 from mmerickel/backport-3122-to-1.9-branch

---
 pyramid/response.py            |   30 +++++++++-
 pyramid/tests/test_response.py |   21 +++++-
 pyramid/tests/test_events.py   |   15 ++++-
 pyramid/events.py              |   25 ++++++++
 CHANGES.txt                    |   14 +++-
 pyramid/view.py                |   29 +++++++--
 pyramid/tests/test_view.py     |   45 +++++++++++++-
 7 files changed, 153 insertions(+), 26 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index bca34a5..a5e1035 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,10 +1,16 @@
 unreleased
 ==========
 
-- Add a ``_category`` argument to the ``pyramid.view.view_config`` decorator.
-  This argument will be forwarded to venusian and can be used to affect
-  what items are picked up by ``config.scan(..., category=...)``.
-  See https://github.com/Pylons/pyramid/pull/3121
+- 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/3121 and
+  https://github.com/Pylons/pyramid/pull/3123
 
 1.9 (2017-06-26)
 ================
diff --git a/pyramid/events.py b/pyramid/events.py
index 35da2fa..d3b068b 100644
--- a/pyramid/events.py
+++ b/pyramid/events.py
@@ -68,12 +68,34 @@
     :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 @@
             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 1d9daae..1e2546e 100644
--- a/pyramid/response.py
+++ b/pyramid/response.py
@@ -145,19 +145,43 @@
         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 52e53c3..4f9011c 100644
--- a/pyramid/tests/test_events.py
+++ b/pyramid/tests/test_events.py
@@ -209,7 +209,16 @@
         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 @@
     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 ad55882..53e3ce1 100644
--- a/pyramid/tests/test_response.py
+++ b/pyramid/tests/test_response.py
@@ -136,9 +136,9 @@
     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 @@
         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 @@
     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 7a1c90d..0124ce6 100644
--- a/pyramid/tests/test_view.py
+++ b/pyramid/tests/test_view.py
@@ -91,6 +91,18 @@
         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
@@ -132,6 +144,18 @@
         self.assertEqual(settings[0]['view'], None) # comes from call_venusian
         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):
@@ -183,6 +207,18 @@
         self.assertEqual(settings[0]['view'], None) # comes from call_venusian
         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):
@@ -564,7 +600,9 @@
         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()
@@ -1000,8 +1038,7 @@
         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):
@@ -1028,7 +1065,7 @@
 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 3b2bafa..46aec45 100644
--- a/pyramid/view.py
+++ b/pyramid/view.py
@@ -199,7 +199,8 @@
     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.
+    See the :py:func:`venusian.attach` function in Venusian for more
+    information about the ``_depth`` and ``_category`` arguments.
     
     .. seealso::
     
@@ -395,9 +396,10 @@
     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.
 
     """
 
@@ -408,12 +410,15 @@
 
     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
@@ -455,6 +460,9 @@
 
     See :ref:`changing_the_forbidden_view` for detailed usage information.
 
+    .. versionchanged:: 1.9.1
+       Added the ``_depth`` and ``_category`` arguments.
+
     """
 
     venusian = venusian
@@ -464,12 +472,15 @@
 
     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
@@ -511,6 +522,9 @@
     :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
 
@@ -524,12 +538,15 @@
 
     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

--
Gitblit v1.9.3