diff options
| -rw-r--r-- | CHANGES.txt | 15 | ||||
| -rw-r--r-- | TODO.txt | 7 | ||||
| -rw-r--r-- | docs/api/events.rst | 14 | ||||
| -rw-r--r-- | docs/narr/events.rst | 42 | ||||
| -rw-r--r-- | repoze/bfg/decorator.py | 1 | ||||
| -rw-r--r-- | repoze/bfg/events.py | 64 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_events.py | 54 |
7 files changed, 190 insertions, 7 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 6cb89f378..e0a0771c7 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -7,6 +7,12 @@ Features - The ``repoze.bfg.configuration.Configurator.add_route`` API now returns the route object that was added. +- A ``repoze.bfg.events.subscriber`` decorator was added. This + decorator decorates module-scope functions, which are then treated + as event listeners after a scan() is performed. See the Events + narrative documentation chapter and the ``repoze.bfg.events`` module + documentation for more information. + Bug Fixes --------- @@ -17,6 +23,15 @@ Bug Fixes wrong view matches when using URL dispatch and custom view predicates together. +Documentation +-------------- + +- Added description of the ``repoze.bfg.events.subscriber`` decorator + to the Events narrative chapter. + +- Added ``repoze.bfg.events.subscriber`` API documentation to + ``repoze.bfg.events`` API docs. + 1.3a6 (2010-07-25) ================== @@ -11,8 +11,6 @@ - Change docs about creating a venusian decorator to not use ZCA. -- Add a ``susbcriber`` decorator and docs. - - ``decorator=`` parameter to bfg_view. - Try to better explain the relationship between a renderer and a @@ -20,6 +18,11 @@ documentation for reference to a renderer as *only* view configuration (it's a larger concept now). +- Create a ``render_view`` that works by using config.derive_view + against an existing view instead of querying the registry. + +- Create a function which performs a recursive request. + - Subscriber decorator. - These methods of Configurator should allow the arguments it receives diff --git a/docs/api/events.rst b/docs/api/events.rst index bb1ba1cf5..cad199be3 100644 --- a/docs/api/events.rst +++ b/docs/api/events.rst @@ -5,11 +5,19 @@ .. automodule:: repoze.bfg.events - .. autoclass:: NewRequest +Functions +~~~~~~~~~ - .. autoclass:: NewResponse +.. autofunction:: subscriber - .. autoclass:: WSGIApplicationCreatedEvent +Event Types +~~~~~~~~~~~ + +.. autoclass:: NewRequest + +.. autoclass:: NewResponse + +.. autoclass:: WSGIApplicationCreatedEvent See :ref:`events_chapter` for more information about how to register code which subscribes to these events. diff --git a/docs/narr/events.rst b/docs/narr/events.rst index 6ef2e2d5d..7f78139bb 100644 --- a/docs/narr/events.rst +++ b/docs/narr/events.rst @@ -34,8 +34,8 @@ The mere existence of a subscriber function, however, is not sufficient to arrange for it to be called. To arrange for the subscriber to be called, you'll need to use the :meth:`repoze.bfg.configurator.Configurator.add_subscriber` method to -register the subscriber imperatively, or you'll need to use ZCML for -the same purpose: +register the subscriber imperatively, or via a decorator, or you'll +need to use ZCML for the same purpose: .. topic:: Configuring an Event Listener Imperatively @@ -78,6 +78,44 @@ the same purpose: See also :ref:`subscriber_directive`. +.. topic:: Configuring an Event Listener Using a Decorator + + You can configure a subscriber function to be called for some event + type via the :func:`repoze.bfg.events.subscriber` function. + + .. code-block:: python + :linenos: + + from repoze.bfg.interfaces import INewRequest + from repoze.bfg.events import subscriber + + @subscriber(INewRequest) + def mysubscriber(event): + event.request.foo = 1 + + When the :func:`repoze.bfg.subscriber` decorator is used a + :term:`scan` must be performed against the package containing the + decorated function for the decorator to have any effect. See + :func:`repoze.bfg.subscriber` for more information. + +.. topic:: Configuring an Event Listener Through ZCML + + You can configure an event listener by modifying your application's + ``configure.zcml``. Here's an example of a bit of XML you can add + to the ``configure.zcml`` file which registers the above + ``mysubscriber`` function, which we assume lives in a + ``subscribers.py`` module within your application: + + .. code-block:: xml + :linenos: + + <subscriber + for="repoze.bfg.interfaces.INewRequest" + handler=".subscribers.mysubscriber" + /> + + See also :ref:`subscriber_directive`. + Either of the above registration examples implies that every time the :mod:`repoze.bfg` framework emits an event object that supplies an :class:`repoze.bfg.interfaces.INewRequest` interface, the diff --git a/repoze/bfg/decorator.py b/repoze/bfg/decorator.py index 69c1e65e2..d30a06658 100644 --- a/repoze/bfg/decorator.py +++ b/repoze/bfg/decorator.py @@ -13,3 +13,4 @@ class reify(object): val = self.wrapped(inst) setattr(inst, self.wrapped.__name__, val) return val + diff --git a/repoze/bfg/events.py b/repoze/bfg/events.py index bdccfba9c..22822eeeb 100644 --- a/repoze/bfg/events.py +++ b/repoze/bfg/events.py @@ -1,3 +1,5 @@ +import venusian + from zope.interface import implements from repoze.bfg.interfaces import IAfterTraversal @@ -5,6 +7,68 @@ from repoze.bfg.interfaces import INewRequest from repoze.bfg.interfaces import INewResponse from repoze.bfg.interfaces import IWSGIApplicationCreatedEvent +class subscriber(object): + """ Decorator activated via a :term:`scan` which treats the + function being decorated as an event subscriber for the set of + interfaces passed as ``*ifaces`` to the decorator constructor. + + For example: + + .. code-block:: python + + from repoze.bfg.interfaces import INewRequest + from repoze.bfg.events import subscriber + + @subscriber(INewRequest) + def mysubscriber(event): + event.request.foo = 1 + + More than one event type can be passed as a construtor argument: + + .. code-block:: python + + from repoze.bfg.interfaces import INewRequest + from repoze.bfg.events import subscriber + + @subscriber(INewRequest, INewResponse) + def mysubscriber(event): + print event + + When the ``subscriber`` decorator is used without passing an arguments, + the function it decorates is called for every event sent: + + .. code-block:: python + + from repoze.bfg.interfaces import INewRequest + from repoze.bfg.events import subscriber + + @subscriber() + def mysubscriber(event): + print event + + This method will have no effect until a :term:`scan` is performed + against the package or module which contains it, ala: + + .. code-block:: python + + from repoze.bfg.configuration import Configurator + config = Configurator() + config.scan('somepackage_containing_subscribers') + + """ + venusian = venusian # for unit testing + + def __init__(self, *ifaces): + self.ifaces = ifaces + + def register(self, scanner, name, wrapped): + config = scanner.config + config.add_subscriber(wrapped, self.ifaces) + + def __call__(self, wrapped): + self.venusian.attach(wrapped, self.register, category='bfg') + return wrapped + class NewRequest(object): """ An instance of this class is emitted as an :term:`event` whenever :mod:`repoze.bfg` begins to process a new request. The diff --git a/repoze/bfg/tests/test_events.py b/repoze/bfg/tests/test_events.py index f1cbfff5b..c258c4ad2 100644 --- a/repoze/bfg/tests/test_events.py +++ b/repoze/bfg/tests/test_events.py @@ -92,6 +92,60 @@ class AfterTraversalEventTests(unittest.TestCase): inst = self._makeOne(request) self.assertEqual(inst.request, request) +class TestSubscriber(unittest.TestCase): + def setUp(self): + registry = DummyRegistry() + from repoze.bfg.configuration import Configurator + self.config = Configurator(registry) + self.config.begin() + + def tearDown(self): + self.config.end() + + def _makeOne(self, *ifaces): + from repoze.bfg.events import subscriber + return subscriber(*ifaces) + + def test_register(self): + from zope.interface import Interface + class IFoo(Interface): pass + class IBar(Interface): pass + dec = self._makeOne(IFoo, IBar) + def foo(): pass + config = DummyConfigurator() + scanner = Dummy() + scanner.config = config + dec.register(scanner, None, foo) + self.assertEqual(config.subscribed, [(foo, (IFoo, IBar))]) + + def test___call__(self): + dec = self._makeOne() + dummy_venusian = DummyVenusian() + dec.venusian = dummy_venusian + def foo(): pass + dec(foo) + self.assertEqual(dummy_venusian.attached, [(foo, dec.register, 'bfg')]) + +class DummyConfigurator(object): + def __init__(self): + self.subscribed = [] + + def add_subscriber(self, wrapped, ifaces): + self.subscribed.append((wrapped, ifaces)) + +class DummyRegistry(object): + pass + +class DummyVenusian(object): + def __init__(self): + self.attached = [] + + def attach(self, wrapped, fn, category=None): + self.attached.append((wrapped, fn, category)) + +class Dummy: + pass + class DummyRequest: pass |
