From 49304cecdf2c51888ee4ff42ec6496207186ad9b Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 2 Nov 2009 12:23:26 +0000 Subject: - Add a new event type: ``repoze.bfg.events.AfterTraversal``. Events of this type will be sent after traversal is completed, but before any view code is invoked. Like ``repoze.bfg.events.NewRequest``, This event will have a single attribute: ``request`` representing the current request. Unlike the request attribute of ``repoze.bfg.events.NewRequest`` however, during an AfterTraversal event, the request object will possess attributes set by the traverser, most notably ``context``, which will be the context used when a view is found and invoked. The interface ``repoze.bfg.events.IAfterTraversal`` can be used to subscribe to the event. For example:: Like any framework event, a subscriber function should expect one parameter: ``event``. --- CHANGES.txt | 21 +++++++++++++++++++++ docs/whatsnew-1.1.rst | 17 +++++++++++++++++ repoze/bfg/events.py | 19 ++++++++++++++++--- repoze/bfg/interfaces.py | 5 +++++ repoze/bfg/router.py | 7 +++++-- repoze/bfg/tests/test_events.py | 25 +++++++++++++++++++++++++ repoze/bfg/tests/test_router.py | 8 ++++++-- 7 files changed, 95 insertions(+), 7 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index edd3170ff..b1d84c4d1 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -12,6 +12,27 @@ Documentation - "What's New in ``repoze.bfg`` 1.1" document added to narrative documentation. +Features +-------- + +- Add a new event type: ``repoze.bfg.events.AfterTraversal``. Events + of this type will be sent after traversal is completed, but before + any view code is invoked. Like ``repoze.bfg.events.NewRequest``, + This event will have a single attribute: ``request`` representing + the current request. Unlike the request attribute of + ``repoze.bfg.events.NewRequest`` however, during an AfterTraversal + event, the request object will possess attributes set by the + traverser, most notably ``context``, which will be the context used + when a view is found and invoked. The interface + ``repoze.bfg.events.IAfterTraversal`` can be used to subscribe to + the event. For example:: + + + + Like any framework event, a subscriber function should expect one + parameter: ``event``. + Dependencies ------------ diff --git a/docs/whatsnew-1.1.rst b/docs/whatsnew-1.1.rst index e84d38a13..655e73882 100644 --- a/docs/whatsnew-1.1.rst +++ b/docs/whatsnew-1.1.rst @@ -595,6 +595,23 @@ Minor Miscellaneous Feature Additions registered forbidden view. When the router catches a ``NotFound``, it returns the registered notfound view. +- Add a new event type: ``repoze.bfg.events.AfterTraversal``. Events + of this type will be sent after traversal is completed, but before + any view code is invoked. Like ``repoze.bfg.events.NewRequest``, + This event will have a single attribute: ``request`` representing + the current request. Unlike the request attribute of + ``repoze.bfg.events.NewRequest`` however, during an AfterTraversal + event, the request object will possess attributes set by the + traverser, most notably ``context``, which will be the context used + when a view is found and invoked. The interface + ``repoze.bfg.events.IAfterTraversal`` can be used to subscribe to + the event. For example:: + + + + Like any framework event, a subscriber function should expect one + parameter: ``event``. Backwards Incompatibilities --------------------------- diff --git a/repoze/bfg/events.py b/repoze/bfg/events.py index 54f60c294..14e47ca3f 100644 --- a/repoze/bfg/events.py +++ b/repoze/bfg/events.py @@ -1,5 +1,6 @@ from zope.interface import implements +from repoze.bfg.interfaces import IAfterTraversal from repoze.bfg.interfaces import INewRequest from repoze.bfg.interfaces import INewResponse from repoze.bfg.interfaces import IWSGIApplicationCreatedEvent @@ -23,6 +24,20 @@ class NewResponse(object): def __init__(self, response): self.response = response +class AfterTraversal(object): + implements(IAfterTraversal) + """ An instance of this class is emitted as an event after the + repoze.bfg router performs traversal (but before any view code is + executed). The instance has an attribute, ``request``, which is + the request object returned by the view. Notably, the request + object will have an attribute named ``context``, which is the + context that will be provided to the view which will eventually be + called, as well as other attributes defined by the traverser. + This class implements the + ``repoze.bfg.interfaces.IAfterTraversal`` interface.""" + def __init__(self, request): + self.request = request + class WSGIApplicationCreatedEvent(object): """ An instance of this class is emitted as an event whenever a ``repoze.bfg`` application starts. The instance has an attribute, @@ -32,7 +47,5 @@ class WSGIApplicationCreatedEvent(object): implements(IWSGIApplicationCreatedEvent) def __init__(self, app): self.app = app + self.object = app - @property - def object(self): - return self.app diff --git a/repoze/bfg/interfaces.py b/repoze/bfg/interfaces.py index 53b01bfd6..582e8984d 100644 --- a/repoze/bfg/interfaces.py +++ b/repoze/bfg/interfaces.py @@ -120,6 +120,11 @@ class IRouter(Interface): registry = Attribute( """Component architecture registry local to this application.""") +class IAfterTraversal(Interface): + """ An event type that is emitted after repoze.bfg completes + traversal but before it calls any view code.""" + request = Attribute('The request object') + class INewRequest(Interface): """ An event type that is emitted whenever repoze.bfg begins to process a new request """ diff --git a/repoze/bfg/router.py b/repoze/bfg/router.py index c3580a8da..ca0f8e449 100644 --- a/repoze/bfg/router.py +++ b/repoze/bfg/router.py @@ -14,6 +14,7 @@ from repoze.bfg.interfaces import IView from repoze.bfg.configuration import make_registry from repoze.bfg.configuration import DefaultRootFactory +from repoze.bfg.events import AfterTraversal from repoze.bfg.events import NewRequest from repoze.bfg.events import NewResponse from repoze.bfg.events import WSGIApplicationCreatedEvent @@ -54,6 +55,7 @@ class Router(object): iterable. """ registry = self.registry + has_listeners = registry.has_listeners logger = self.logger manager = self.threadlocal_manager threadlocals = {'registry':registry, 'request':None} @@ -65,7 +67,7 @@ class Router(object): threadlocals['request'] = request attrs = request.__dict__ attrs['registry'] = registry - registry.has_listeners and registry.notify(NewRequest(request)) + has_listeners and registry.notify(NewRequest(request)) # view lookup root = self.root_factory(request) @@ -79,6 +81,7 @@ class Router(object): tdict['traversed'], tdict['virtual_root'], tdict['virtual_root_path']) attrs.update(tdict) + has_listeners and registry.notify(AfterTraversal(request)) provides = map(providedBy, (context, request)) view_callable = registry.adapters.lookup( provides, IView, name=view_name, default=None) @@ -111,7 +114,7 @@ class Router(object): response = self.notfound_view(context, request) # response handling - registry.has_listeners and registry.notify(NewResponse(response)) + has_listeners and registry.notify(NewResponse(response)) try: headers = response.headerlist diff --git a/repoze/bfg/tests/test_events.py b/repoze/bfg/tests/test_events.py index d7ab772f5..f1cbfff5b 100644 --- a/repoze/bfg/tests/test_events.py +++ b/repoze/bfg/tests/test_events.py @@ -66,6 +66,31 @@ class WSGIAppEventTests(unittest.TestCase): from zope.interface.verify import verifyClass verifyClass(IWSGIApplicationCreatedEvent, WSGIApplicationCreatedEvent) +class AfterTraversalEventTests(unittest.TestCase): + def _getTargetClass(self): + from repoze.bfg.events import AfterTraversal + return AfterTraversal + + def _makeOne(self, request): + return self._getTargetClass()(request) + + def test_class_implements(self): + from repoze.bfg.interfaces import IAfterTraversal + from zope.interface.verify import verifyClass + klass = self._getTargetClass() + verifyClass(IAfterTraversal, klass) + + def test_instance_implements(self): + from repoze.bfg.interfaces import IAfterTraversal + from zope.interface.verify import verifyObject + request = DummyRequest() + inst = self._makeOne(request) + verifyObject(IAfterTraversal, inst) + + def test_ctor(self): + request = DummyRequest() + inst = self._makeOne(request) + self.assertEqual(inst.request, request) class DummyRequest: pass diff --git a/repoze/bfg/tests/test_router.py b/repoze/bfg/tests/test_router.py index 1e5382dde..d5bc8cd66 100644 --- a/repoze/bfg/tests/test_router.py +++ b/repoze/bfg/tests/test_router.py @@ -382,6 +382,9 @@ class TestRouter(unittest.TestCase): self.assertEqual(start_response.headers, [('a', 1), ('b', 2)]) def test_call_eventsends(self): + from repoze.bfg.interfaces import INewRequest + from repoze.bfg.interfaces import INewResponse + from repoze.bfg.interfaces import IAfterTraversal context = DummyContext() self._registerTraverserFactory(context) response = DummyResponse() @@ -389,15 +392,16 @@ class TestRouter(unittest.TestCase): view = DummyView(response) environ = self._makeEnviron() self._registerView(view, '', None, None) - from repoze.bfg.interfaces import INewRequest - from repoze.bfg.interfaces import INewResponse request_events = self._registerEventListener(INewRequest) + aftertraversal_events = self._registerEventListener(IAfterTraversal) response_events = self._registerEventListener(INewResponse) router = self._makeOne() start_response = DummyStartResponse() result = router(environ, start_response) self.assertEqual(len(request_events), 1) self.assertEqual(request_events[0].request.environ, environ) + self.assertEqual(len(aftertraversal_events), 1) + self.assertEqual(aftertraversal_events[0].request.environ, environ) self.assertEqual(len(response_events), 1) self.assertEqual(response_events[0].response, response) -- cgit v1.2.3