Merge pull request #2469 from Pylons/feature/BeforeTraversal
Feature: BeforeTraversal
| | |
| | | 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 |
| | | |
| | |
| | | |
| | | .. autoclass:: ContextFound |
| | | |
| | | .. autoclass:: BeforeTraversal |
| | | |
| | | .. autoclass:: NewResponse |
| | | |
| | | .. autoclass:: BeforeRender |
| | |
| | | .. autointerface:: IContextFound |
| | | :members: |
| | | |
| | | .. autointerface:: IBeforeTraversal |
| | | :members: |
| | | |
| | | .. autointerface:: INewResponse |
| | | :members: |
| | | |
| | |
| | | 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. |
| | | |
| | |
| | | INewResponse, |
| | | IApplicationCreated, |
| | | IBeforeRender, |
| | | IBeforeTraversal, |
| | | ) |
| | | |
| | | class subscriber(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 |
| | |
| | | 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 |
| | |
| | | def __init__(self, system, rendering_val=None): |
| | | dict.__init__(self, system) |
| | | self.rendering_val = rendering_val |
| | | |
| | | |
| | |
| | | |
| | | 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 |
| | |
| | | ContextFound, |
| | | NewRequest, |
| | | NewResponse, |
| | | BeforeTraversal, |
| | | ) |
| | | |
| | | from pyramid.httpexceptions import HTTPNotFound |
| | |
| | | |
| | | 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) |
| | |
| | | ) |
| | | |
| | | 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 |
| | |
| | | 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 |
| | |
| | | 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 |
| | |
| | | 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 |
| | |
| | | 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): |
| | |
| | | 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 |
| | |
| | | |
| | | class DummyRegistry(object): |
| | | pass |
| | | |
| | | |
| | | class DummyVenusian(object): |
| | | def __init__(self): |
| | | self.attached = [] |
| | |
| | | |
| | | class Dummy: |
| | | pass |
| | | |
| | | |
| | | class DummyRequest: |
| | | pass |
| | | |
| | |
| | | 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() |
| | |
| | | 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() |
| | |
| | | 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) |