diff options
| -rw-r--r-- | CHANGES.txt | 5 | ||||
| -rw-r--r-- | docs/api/events.rst | 49 | ||||
| -rw-r--r-- | docs/conf.py | 4 | ||||
| -rw-r--r-- | docs/index.rst | 1 | ||||
| -rw-r--r-- | repoze/bfg/events.py | 23 | ||||
| -rw-r--r-- | repoze/bfg/interfaces.py | 9 | ||||
| -rw-r--r-- | repoze/bfg/router.py | 6 | ||||
| -rw-r--r-- | repoze/bfg/sampleapp/configure.zcml | 12 | ||||
| -rw-r--r-- | repoze/bfg/sampleapp/listeners.py | 6 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_events.py | 60 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_router.py | 33 | ||||
| -rw-r--r-- | setup.py | 2 |
12 files changed, 208 insertions, 2 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 2ae332b0c..111be6294 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,8 @@ +0.2.6 + + - Add event sends for INewRequest and INewResponse. See the + events.rst chapter in the documentation's ``api`` directory. + 0.2.5 - Add ``model_url`` API. diff --git a/docs/api/events.rst b/docs/api/events.rst new file mode 100644 index 000000000..25bb9841b --- /dev/null +++ b/docs/api/events.rst @@ -0,0 +1,49 @@ +.. _events_module: + +:mod:`repoze.bfg.events` +-------------------------- + +.. automodule:: repoze.bfg.events + + .. autoclass:: NewRequest + + .. autoclass:: NewResponse + +You can write *listeners* for these event types and subsequently +register the listeners to be called when the events occur. For +example, if you create event listener functions in a ``listeners.py`` +file in your application like so: + +.. code-block:: python + :linenos: + + def handle_new_request(event): + print 'request', event.request + + def handle_new_response(event): + print 'response', event.response + +You may configure these functions to be called at the appropriate +times by adding the following to your application's ``configure.zcml`` +file: + +.. code-block:: xml + :linenos: + + <subscriber + for="repoze.bfg.interfaces.INewRequest" + handler=".listeners.handle_new_request" + /> + + <subscriber + for="repoze.bfg.interfaces.INewResponse" + handler=".listeners.handle_new_response" + /> + +This causes the functions as to be registered as event listeners +within the :term:`application registry` . Under this configuration, +when the application is run, every new request and every response will +be printed to the console. + +The return value of a listener function is ignored. + diff --git a/docs/conf.py b/docs/conf.py index 8d19cf4db..2a0d53381 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -51,9 +51,9 @@ copyright = '2008, Agendaless Consulting' # other places throughout the built documents. # # The short X.Y version. -version = '0.2.5' +version = '0.2.6' # The full version, including alpha/beta/rc tags. -release = '0.2.5' +release = '0.2.6' # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: diff --git a/docs/index.rst b/docs/index.rst index 5825a6129..63f8b96e4 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -50,6 +50,7 @@ Per-module :mod:`repoze.bfg` API documentation. .. toctree:: :maxdepth: 2 + api/events api/push api/router api/security diff --git a/repoze/bfg/events.py b/repoze/bfg/events.py new file mode 100644 index 000000000..3253f1380 --- /dev/null +++ b/repoze/bfg/events.py @@ -0,0 +1,23 @@ +from zope.interface import implements + +from repoze.bfg.interfaces import INewRequest +from repoze.bfg.interfaces import INewResponse + +class NewRequest(object): + """ An instance of this class is emitted as an event whenever + repoze.bfg begins to process a new request. The instance has an + attribute, ``request``, which is the request object. This class + implements the ``repoze.bfg.interfaces.INewRequest`` interface.""" + implements(INewRequest) + def __init__(self, request): + self.request = request + +class NewResponse(object): + """ An instance of this class is emitted as an event whenever any + repoze.bfg view returns a response.. The instance has an + attribute, ``response``, which is the response object returned by + the view. This class implements the + ``repoze.bfg.interfaces.INewResponse`` interface.""" + implements(INewResponse) + def __init__(self, response): + self.response = response diff --git a/repoze/bfg/interfaces.py b/repoze/bfg/interfaces.py index 489fe0559..4b2b626e6 100644 --- a/repoze/bfg/interfaces.py +++ b/repoze/bfg/interfaces.py @@ -69,3 +69,12 @@ class IViewPermissionFactory(Interface): class IURLDispatchModel(Interface): """ A model that is created as a result of URL dispatching """ +class INewRequest(Interface): + """ An event type that is emitted whenever repoze.bfg begins to + process a new request """ + request = Attribute('The request object') + +class INewResponse(Interface): + """ An event type that is emitted whenever any repoze.bfg view + returns a response.""" + response = Attribute('The response object') diff --git a/repoze/bfg/router.py b/repoze/bfg/router.py index d889c15a0..0333c8a99 100644 --- a/repoze/bfg/router.py +++ b/repoze/bfg/router.py @@ -1,12 +1,15 @@ from zope.component import getMultiAdapter from zope.component import queryMultiAdapter from zope.component import queryUtility +from zope.component.event import dispatch from zope.interface import directlyProvides from webob import Request from webob.exc import HTTPNotFound from webob.exc import HTTPUnauthorized +from repoze.bfg.events import NewRequest +from repoze.bfg.events import NewResponse from repoze.bfg.interfaces import ITraverserFactory from repoze.bfg.interfaces import IView from repoze.bfg.interfaces import IViewPermission @@ -28,6 +31,7 @@ class Router: registry_manager.set(self.registry) request = Request(environ) directlyProvides(request, IRequest) + dispatch(NewRequest(request)) root = self.root_policy(environ) traverser = getMultiAdapter((root, request), ITraverserFactory) context, name, subpath = traverser(environ) @@ -55,6 +59,8 @@ class Router: if not isResponse(response): raise ValueError('response was not IResponse: %s' % response) + dispatch(NewResponse(response)) + start_response(response.status, response.headerlist) return response.app_iter diff --git a/repoze/bfg/sampleapp/configure.zcml b/repoze/bfg/sampleapp/configure.zcml index 121566520..a5f27595e 100644 --- a/repoze/bfg/sampleapp/configure.zcml +++ b/repoze/bfg/sampleapp/configure.zcml @@ -39,4 +39,16 @@ permission="manage" /> + <!-- event listener for a new request --> + <subscriber + for="repoze.bfg.interfaces.INewRequest" + handler=".listeners.handle_new_request" + /> + + <!-- event listener for a new response --> + <subscriber + for="repoze.bfg.interfaces.INewResponse" + handler=".listeners.handle_new_response" + /> + </configure> diff --git a/repoze/bfg/sampleapp/listeners.py b/repoze/bfg/sampleapp/listeners.py new file mode 100644 index 000000000..7c80d314f --- /dev/null +++ b/repoze/bfg/sampleapp/listeners.py @@ -0,0 +1,6 @@ +def handle_new_request(event): + assert(hasattr(event, 'request')) + +def handle_new_response(event): + assert(hasattr(event, 'response')) + diff --git a/repoze/bfg/tests/test_events.py b/repoze/bfg/tests/test_events.py new file mode 100644 index 000000000..770f1afeb --- /dev/null +++ b/repoze/bfg/tests/test_events.py @@ -0,0 +1,60 @@ +import unittest + +class NewRequestEventTests(unittest.TestCase): + def _getTargetClass(self): + from repoze.bfg.events import NewRequest + return NewRequest + + def _makeOne(self, request): + return self._getTargetClass()(request) + + def test_class_implements(self): + from repoze.bfg.interfaces import INewRequest + from zope.interface.verify import verifyClass + klass = self._getTargetClass() + verifyClass(INewRequest, klass) + + def test_instance_implements(self): + from repoze.bfg.interfaces import INewRequest + from zope.interface.verify import verifyObject + request = DummyRequest() + inst = self._makeOne(request) + verifyObject(INewRequest, inst) + + def test_ctor(self): + request = DummyRequest() + inst = self._makeOne(request) + self.assertEqual(inst.request, request) + +class NewResponseEventTests(unittest.TestCase): + def _getTargetClass(self): + from repoze.bfg.events import NewResponse + return NewResponse + + def _makeOne(self, response): + return self._getTargetClass()(response) + + def test_class_implements(self): + from repoze.bfg.interfaces import INewResponse + from zope.interface.verify import verifyClass + klass = self._getTargetClass() + verifyClass(INewResponse, klass) + + def test_instance_implements(self): + from repoze.bfg.interfaces import INewResponse + from zope.interface.verify import verifyObject + response = DummyResponse() + inst = self._makeOne(response) + verifyObject(INewResponse, inst) + + def test_ctor(self): + response = DummyResponse() + inst = self._makeOne(response) + self.assertEqual(inst.response, response) + +class DummyRequest: + pass + +class DummyResponse: + pass + diff --git a/repoze/bfg/tests/test_router.py b/repoze/bfg/tests/test_router.py index 4b1e914b7..d2907ef8a 100644 --- a/repoze/bfg/tests/test_router.py +++ b/repoze/bfg/tests/test_router.py @@ -33,6 +33,11 @@ class RouterTests(unittest.TestCase, PlacelessSetup): from repoze.bfg.interfaces import ISecurityPolicy gsm.registerUtility(secpol, ISecurityPolicy) + def _registerEventListener(self, listener, iface): + import zope.component + gsm = zope.component.getGlobalSiteManager() + gsm.registerHandler(listener, (iface,)) + def _getTargetClass(self): from repoze.bfg.router import Router return Router @@ -232,6 +237,34 @@ class RouterTests(unittest.TestCase, PlacelessSetup): self.failUnless('permission' in result[0]) self.assertEqual(permissionfactory.checked_with, secpol) + def test_call_eventsends(self): + rootpolicy = make_rootpolicy(None) + context = DummyContext() + traversalfactory = make_traversal_factory(context, '', []) + response = DummyResponse() + response.app_iter = ['Hello world'] + view = make_view(response) + environ = self._makeEnviron() + self._registerTraverserFactory(traversalfactory, '', None, None) + self._registerView(view, '', None, None) + from repoze.bfg.interfaces import INewRequest + from repoze.bfg.interfaces import INewResponse + request_events = [] + response_events = [] + def handle_request(event): + request_events.append(event) + def handle_response(event): + response_events.append(event) + self._registerEventListener(handle_request, INewRequest) + self._registerEventListener(handle_response, INewResponse) + router = self._makeOne(rootpolicy, None) + 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(response_events), 1) + self.assertEqual(response_events[0].response, response) + class MakeAppTests(unittest.TestCase, PlacelessSetup): def setUp(self): PlacelessSetup.setUp(self) @@ -52,6 +52,7 @@ setup(name='repoze.bfg', 'zope.component', 'zope.testing', 'zope.hookable', + 'zope.event', 'WebOb', 'Paste', 'z3c.pt', @@ -64,6 +65,7 @@ setup(name='repoze.bfg', 'zope.component', 'zope.testing', 'zope.hookable', + 'zope.event', 'WebOb', 'Paste', 'z3c.pt', |
