diff options
| -rw-r--r-- | CHANGES.txt | 5 | ||||
| -rw-r--r-- | docs/api/events.rst | 2 | ||||
| -rw-r--r-- | docs/api/interfaces.rst | 3 | ||||
| -rw-r--r-- | docs/narr/router.rst | 25 | ||||
| -rw-r--r-- | pyramid/events.py | 24 | ||||
| -rw-r--r-- | pyramid/interfaces.py | 8 | ||||
| -rw-r--r-- | pyramid/router.py | 15 | ||||
| -rw-r--r-- | pyramid/tests/test_events.py | 35 | ||||
| -rw-r--r-- | pyramid/tests/test_router.py | 4 |
9 files changed, 101 insertions, 20 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index fd8c636a0..488c38c7b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,11 @@ unreleased ========== +- A new event and interface (BeforeTraversal) has been introduced that will + notify listeners before traversal starts in the router. See + https://github.com/Pylons/pyramid/pull/2469 and + https://github.com/Pylons/pyramid/pull/1876 + - Python 2.6 is no longer supported by Pyramid. See https://github.com/Pylons/pyramid/issues/2368 diff --git a/docs/api/events.rst b/docs/api/events.rst index 31a0e22c1..0a8463740 100644 --- a/docs/api/events.rst +++ b/docs/api/events.rst @@ -21,6 +21,8 @@ Event Types .. autoclass:: ContextFound +.. autoclass:: BeforeTraversal + .. autoclass:: NewResponse .. autoclass:: BeforeRender diff --git a/docs/api/interfaces.rst b/docs/api/interfaces.rst index 635d3c5b6..272820a91 100644 --- a/docs/api/interfaces.rst +++ b/docs/api/interfaces.rst @@ -17,6 +17,9 @@ Event-Related Interfaces .. autointerface:: IContextFound :members: + .. autointerface:: IBeforeTraversal + :members: + .. autointerface:: INewResponse :members: diff --git a/docs/narr/router.rst b/docs/narr/router.rst index e02142e6e..e45e6f4a8 100644 --- a/docs/narr/router.rst +++ b/docs/narr/router.rst @@ -41,19 +41,24 @@ request enters a :app:`Pyramid` application through to the point that user-defined :term:`route` matches the current WSGI environment. The :term:`router` passes the request as an argument to the mapper. -#. If any route matches, the route mapper adds attributes to the request: - ``matchdict`` and ``matched_route`` attributes are added to the request - object. The former contains a dictionary representing the matched dynamic - elements of the request's ``PATH_INFO`` value, and the latter contains the +#. If any route matches, the route mapper adds the attributes ``matchdict`` + and ``matched_route`` to the request object. The former contains a + dictionary representing the matched dynamic elements of the request's + ``PATH_INFO`` value, and the latter contains the :class:`~pyramid.interfaces.IRoute` object representing the route which - matched. The root object associated with the route found is also generated: - if the :term:`route configuration` which matched has an associated - ``factory`` argument, this factory is used to generate the root object, - otherwise a default :term:`root factory` is used. + matched. -#. If a route match was *not* found, and a ``root_factory`` argument was passed +#. A :class:`~pyramid.events.BeforeTraversal` :term:`event` is sent to any + subscribers. + +#. Continuing, if any route matches, the root object associated with the found + route is generated. If the :term:`route configuration` which matched has an + associated ``factory`` argument, then this factory is used to generate the + root object; otherwise a default :term:`root factory` is used. + + However, if no route matches, and if a ``root_factory`` argument was passed to the :term:`Configurator` constructor, that callable is used to generate - the root object. If the ``root_factory`` argument passed to the + the root object. If the ``root_factory`` argument passed to the Configurator constructor was ``None``, a default root factory is used to generate a root object. diff --git a/pyramid/events.py b/pyramid/events.py index 97375638e..35da2fa6f 100644 --- a/pyramid/events.py +++ b/pyramid/events.py @@ -11,6 +11,7 @@ from pyramid.interfaces import ( INewResponse, IApplicationCreated, IBeforeRender, + IBeforeTraversal, ) class subscriber(object): @@ -129,6 +130,26 @@ class NewResponse(object): self.request = request self.response = response +@implementer(IBeforeTraversal) +class BeforeTraversal(object): + """ + An instance of this class is emitted as an :term:`event` after the + :app:`Pyramid` :term:`router` has attempted to find a :term:`route` object + but before any traversal or view code is executed. The instance has an + attribute, ``request``, which is the request object generated by + :app:`Pyramid`. + + Notably, the request object **may** have an attribute named + ``matched_route``, which is the matched route if found. If no route + matched, this attribute is not available. + + This class implements the :class:`pyramid.interfaces.IBeforeTraversal` + interface. + """ + + def __init__(self, request): + self.request = request + @implementer(IContextFound) class ContextFound(object): """ An instance of this class is emitted as an :term:`event` after @@ -156,7 +177,7 @@ class ContextFound(object): AfterTraversal = ContextFound # b/c as of 1.0 @implementer(IApplicationCreated) -class ApplicationCreated(object): +class ApplicationCreated(object): """ An instance of this class is emitted as an :term:`event` when the :meth:`pyramid.config.Configurator.make_wsgi_app` is called. The instance has an attribute, ``app``, which is an @@ -243,4 +264,3 @@ class BeforeRender(dict): dict.__init__(self, system) self.rendering_val = rendering_val - diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index 64bb4b50c..2b00752cf 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -25,6 +25,14 @@ class IContextFound(Interface): IAfterTraversal = IContextFound +class IBeforeTraversal(Interface): + """ + An event type that is emitted after :app:`Pyramid` attempted to find a + route but before it calls any traversal or view code. See the documentation + attached to :class:`pyramid.events.Routefound` for more information. + """ + request = Attribute('The request object') + class INewRequest(Interface): """ An event type that is emitted whenever :app:`Pyramid` begins to process a new request. See the documentation attached diff --git a/pyramid/router.py b/pyramid/router.py index 4054ef52e..19773cf62 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -20,6 +20,7 @@ from pyramid.events import ( ContextFound, NewRequest, NewResponse, + BeforeTraversal, ) from pyramid.httpexceptions import HTTPNotFound @@ -114,10 +115,19 @@ class Router(object): root_factory = route.factory or self.root_factory + # Notify anyone listening that we are about to start traversal + # + # Notify before creating root_factory in case we want to do something + # special on a route we may have matched. See + # https://github.com/Pylons/pyramid/pull/1876 for ideas of what is + # possible. + has_listeners and notify(BeforeTraversal(request)) + + # Create the root factory root = root_factory(request) attrs['root'] = root - # find a context + # We are about to traverse and find a context traverser = adapters.queryAdapter(root, ITraverser) if traverser is None: traverser = ResourceTreeTraverser(root) @@ -133,6 +143,9 @@ class Router(object): ) attrs.update(tdict) + + # Notify anyone listening that we have a context and traversal is + # complete has_listeners and notify(ContextFound(request)) # find a view callable diff --git a/pyramid/tests/test_events.py b/pyramid/tests/test_events.py index 2c72c07e8..52e53c34e 100644 --- a/pyramid/tests/test_events.py +++ b/pyramid/tests/test_events.py @@ -14,7 +14,7 @@ class NewRequestEventTests(unittest.TestCase): from zope.interface.verify import verifyClass klass = self._getTargetClass() verifyClass(INewRequest, klass) - + def test_instance_conforms_to_INewRequest(self): from pyramid.interfaces import INewRequest from zope.interface.verify import verifyObject @@ -40,7 +40,7 @@ class NewResponseEventTests(unittest.TestCase): from zope.interface.verify import verifyClass klass = self._getTargetClass() verifyClass(INewResponse, klass) - + def test_instance_conforms_to_INewResponse(self): from pyramid.interfaces import INewResponse from zope.interface.verify import verifyObject @@ -103,7 +103,7 @@ class ContextFoundEventTests(unittest.TestCase): from zope.interface.verify import verifyClass from pyramid.interfaces import IContextFound verifyClass(IContextFound, self._getTargetClass()) - + def test_instance_conforms_to_IContextFound(self): from zope.interface.verify import verifyObject from pyramid.interfaces import IContextFound @@ -118,12 +118,33 @@ class AfterTraversalEventTests(ContextFoundEventTests): from zope.interface.verify import verifyClass from pyramid.interfaces import IAfterTraversal verifyClass(IAfterTraversal, self._getTargetClass()) - + def test_instance_conforms_to_IAfterTraversal(self): from zope.interface.verify import verifyObject from pyramid.interfaces import IAfterTraversal verifyObject(IAfterTraversal, self._makeOne()) +class BeforeTraversalEventTests(unittest.TestCase): + def _getTargetClass(self): + from pyramid.events import BeforeTraversal + return BeforeTraversal + + def _makeOne(self, request=None): + if request is None: + request = DummyRequest() + return self._getTargetClass()(request) + + def test_class_conforms_to_IBeforeTraversal(self): + from zope.interface.verify import verifyClass + from pyramid.interfaces import IBeforeTraversal + verifyClass(IBeforeTraversal, self._getTargetClass()) + + def test_instance_conforms_to_IBeforeTraversal(self): + from zope.interface.verify import verifyObject + from pyramid.interfaces import IBeforeTraversal + verifyObject(IBeforeTraversal, self._makeOne()) + + class TestSubscriber(unittest.TestCase): def setUp(self): self.config = testing.setUp() @@ -221,7 +242,7 @@ class TestBeforeRender(unittest.TestCase): result = event.setdefault('a', 1) self.assertEqual(result, 1) self.assertEqual(event, {'a':1}) - + def test_setdefault_success(self): event = self._makeOne({}) event['a'] = 1 @@ -282,7 +303,7 @@ class DummyConfigurator(object): class DummyRegistry(object): pass - + class DummyVenusian(object): def __init__(self): self.attached = [] @@ -292,7 +313,7 @@ class DummyVenusian(object): class Dummy: pass - + class DummyRequest: pass diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py index 1cdc4abaa..7aa42804c 100644 --- a/pyramid/tests/test_router.py +++ b/pyramid/tests/test_router.py @@ -591,6 +591,7 @@ class TestRouter(unittest.TestCase): def test_call_eventsends(self): from pyramid.interfaces import INewRequest from pyramid.interfaces import INewResponse + from pyramid.interfaces import IBeforeTraversal from pyramid.interfaces import IContextFound from pyramid.interfaces import IViewClassifier context = DummyContext() @@ -601,6 +602,7 @@ class TestRouter(unittest.TestCase): environ = self._makeEnviron() self._registerView(view, '', IViewClassifier, None, None) request_events = self._registerEventListener(INewRequest) + beforetraversal_events = self._registerEventListener(IBeforeTraversal) context_found_events = self._registerEventListener(IContextFound) response_events = self._registerEventListener(INewResponse) router = self._makeOne() @@ -608,6 +610,8 @@ class TestRouter(unittest.TestCase): result = router(environ, start_response) self.assertEqual(len(request_events), 1) self.assertEqual(request_events[0].request.environ, environ) + self.assertEqual(len(beforetraversal_events), 1) + self.assertEqual(beforetraversal_events[0].request.environ, environ) self.assertEqual(len(context_found_events), 1) self.assertEqual(context_found_events[0].request.environ, environ) self.assertEqual(context_found_events[0].request.context, context) |
