Permalink
Browse files

- 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.
  • Loading branch information...
1 parent be6f3b9 commit f077653f208f6f7c89c78c87c2abb0ea7031dbc0 Chris McDonough committed Jul 28, 2010
Showing with 190 additions and 7 deletions.
  1. +15 −0 CHANGES.txt
  2. +5 −2 TODO.txt
  3. +11 −3 docs/api/events.rst
  4. +40 −2 docs/narr/events.rst
  5. +1 −0 repoze/bfg/decorator.py
  6. +64 −0 repoze/bfg/events.py
  7. +54 −0 repoze/bfg/tests/test_events.py
View
@@ -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)
==================
View
@@ -11,15 +11,18 @@
- 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
template in the templates chapter and elsewhere. Scan the
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
View
@@ -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.
View
@@ -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
View
@@ -13,3 +13,4 @@ def __get__(self, inst, objtype=None):
val = self.wrapped(inst)
setattr(inst, self.wrapped.__name__, val)
return val
+
View
@@ -1,10 +1,74 @@
+import venusian
+
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
+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
@@ -92,6 +92,60 @@ def test_ctor(self):
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

0 comments on commit f077653

Please sign in to comment.