summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Merickel <mmerickel@users.noreply.github.com>2016-04-12 19:12:00 -0500
committerMichael Merickel <mmerickel@users.noreply.github.com>2016-04-12 19:12:00 -0500
commitb1527e793bc101327050370c17e1be698f7192ff (patch)
tree7e63848bdfc7ac35af2f92c5c395c304add251af
parentefaf7300c9c3ef2a795aae7f724bf5b02e96cd75 (diff)
parent17905a39040b8a2f4b57341909eef9d0fac218f5 (diff)
downloadpyramid-b1527e793bc101327050370c17e1be698f7192ff.tar.gz
pyramid-b1527e793bc101327050370c17e1be698f7192ff.tar.bz2
pyramid-b1527e793bc101327050370c17e1be698f7192ff.zip
Merge pull request #2469 from Pylons/feature/BeforeTraversal
Feature: BeforeTraversal
-rw-r--r--CHANGES.txt5
-rw-r--r--docs/api/events.rst2
-rw-r--r--docs/api/interfaces.rst3
-rw-r--r--docs/narr/router.rst25
-rw-r--r--pyramid/events.py24
-rw-r--r--pyramid/interfaces.py8
-rw-r--r--pyramid/router.py15
-rw-r--r--pyramid/tests/test_events.py35
-rw-r--r--pyramid/tests/test_router.py4
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)