diff options
| author | Chris McDonough <chrism@plope.com> | 2010-11-02 02:32:02 -0400 |
|---|---|---|
| committer | Chris McDonough <chrism@plope.com> | 2010-11-02 02:32:02 -0400 |
| commit | a76e99959ecc3220c317cb2942332a9fc342f3a4 (patch) | |
| tree | cc421c38084090d522f6d411aa8afc8df57c4617 | |
| parent | 809744e041f845ca7bd6586fb882e9bb01e03182 (diff) | |
| download | pyramid-a76e99959ecc3220c317cb2942332a9fc342f3a4.tar.gz pyramid-a76e99959ecc3220c317cb2942332a9fc342f3a4.tar.bz2 pyramid-a76e99959ecc3220c317cb2942332a9fc342f3a4.zip | |
- New event type: ``pyramid.interfaces.IBeforeRender``. An object of this type
is sent as an event before a renderer is invoked (but after the
application-level renderer globals factory added via
``pyramid.configurator.configuration.set_renderer_globals_factory``, if any,
has injected its own keys). Applications may now subscribe to the
``IBeforeRender`` event type in order to introspect the and modify the set of
renderer globals before they are passed to a renderer. The event object
iself has a dictionary-like interface that can be used for this purpose. For
example::
from repoze.events import subscriber
from pyramid.interfaces import IRendererGlobalsEvent
@subscriber(IRendererGlobalsEvent)
def add_global(event):
event['mykey'] = 'foo'
If a subscriber attempts to add a key that already exist in the renderer
globals dictionary, a ``KeyError`` is raised. This limitation is due to the
fact that subscribers cannot be ordered relative to each other. The set of
keys added to the renderer globals dictionary by all subscribers and
app-level globals factories must be unique.
| -rw-r--r-- | CHANGES.txt | 23 | ||||
| -rw-r--r-- | TODO.txt | 2 | ||||
| -rw-r--r-- | docs/api/events.rst | 3 | ||||
| -rw-r--r-- | docs/api/interfaces.rst | 2 | ||||
| -rw-r--r-- | docs/narr/hooks.rst | 41 | ||||
| -rw-r--r-- | pyramid/events.py | 62 | ||||
| -rw-r--r-- | pyramid/interfaces.py | 39 | ||||
| -rw-r--r-- | pyramid/renderers.py | 3 | ||||
| -rw-r--r-- | pyramid/tests/test_events.py | 64 | ||||
| -rw-r--r-- | pyramid/tests/test_renderers.py | 4 |
10 files changed, 241 insertions, 2 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 37fc92f75..aee4f64de 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -49,6 +49,29 @@ Features (delta from BFG 1.3.X) is passed to renderer factory constructors (see "Backwards Incompatibilities"). +- New event type: ``pyramid.interfaces.IBeforeRender``. An object of this type + is sent as an event before a renderer is invoked (but after the + application-level renderer globals factory added via + ``pyramid.configurator.configuration.set_renderer_globals_factory``, if any, + has injected its own keys). Applications may now subscribe to the + ``IBeforeRender`` event type in order to introspect the and modify the set of + renderer globals before they are passed to a renderer. The event object + iself has a dictionary-like interface that can be used for this purpose. For + example:: + + from repoze.events import subscriber + from pyramid.interfaces import IRendererGlobalsEvent + + @subscriber(IRendererGlobalsEvent) + def add_global(event): + event['mykey'] = 'foo' + + If a subscriber attempts to add a key that already exist in the renderer + globals dictionary, a ``KeyError`` is raised. This limitation is due to the + fact that subscribers cannot be ordered relative to each other. The set of + keys added to the renderer globals dictionary by all subscribers and + app-level globals factories must be unique. + Documentation (delta from BFG 1.3) ----------------------------------- @@ -76,8 +76,6 @@ - Try to get rid of Mako Beaker dependency. -- Maybe make renderer globals lookup send an event? - - ``docs`` directory for each paster template. - bfg.routes.matchdict and bfg.routes.route diff --git a/docs/api/events.rst b/docs/api/events.rst index 8371ba61d..59657a820 100644 --- a/docs/api/events.rst +++ b/docs/api/events.rst @@ -23,6 +23,9 @@ Event Types .. autoclass:: NewResponse +.. autoclass:: BeforeRender + :members: + See :ref:`events_chapter` for more information about how to register code which subscribes to these events. diff --git a/docs/api/interfaces.rst b/docs/api/interfaces.rst index db610308d..b27428d89 100644 --- a/docs/api/interfaces.rst +++ b/docs/api/interfaces.rst @@ -16,6 +16,8 @@ Event-Related Interfaces .. autointerface:: INewResponse + .. autointerface:: IBeforeRender + Other Interfaces ++++++++++++++++ diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index 12c1cd0aa..130dd8acd 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -446,6 +446,47 @@ method: config = Configurator() config.set_renderer_globals_factory(renderer_globals_factory) +Another mechanism which allows event subscribers to add renderer global values +exists in :ref:`beforerender_event`. + +.. _beforerender_event: + +Using The Before Render Event +----------------------------- + +Subscribers to the :class:`repoze.interfaces.IBeforeRender` event may +introspect the and modify the set of :term:`renderer globals` before they are +passed to a :term:`renderer`. This event object iself has a dictionary-like +interface that can be used for this purpose. For example: + +.. code-block:: python + :linenos: + + from repoze.events import subscriber + from pyramid.interfaces import IBeforeRender + + @subscriber(IBeforeRender) + def add_global(event): + event['mykey'] = 'foo' + +An object of this type is sent as an event just before a :term:`renderer` is +invoked (but *after* the application-level renderer globals factory added via +:class:`pyramid.configuration.Configurator.set_renderer_globals_factory`, if +any, has injected its own keys into the renderer globals dictionary). + +If a subscriber attempts to add a key that already exist in the renderer +globals dictionary, a :exc:`KeyError` is raised. This limitation is enforced +because event subscribers do not possess any relative ordering. The set of +keys added to the renderer globals dictionary by all +:class:`pyramid.interfaces.IBeforeRender` subscribers and renderer globals +factories must be unique. + +See the API documentation for the event interface +:class:`pyramid.interfaces.IBeforeRender`. + +Another mechanism which allows event subscribers more control when adding +renderer global values exists in :ref:`adding_renderer_globals`. + .. _using_response_callbacks: Using Response Callbacks diff --git a/pyramid/events.py b/pyramid/events.py index 527707fb1..043631539 100644 --- a/pyramid/events.py +++ b/pyramid/events.py @@ -6,6 +6,7 @@ from pyramid.interfaces import IContextFound from pyramid.interfaces import INewRequest from pyramid.interfaces import INewResponse from pyramid.interfaces import IApplicationCreated +from pyramid.interfaces import IBeforeRender class subscriber(object): """ Decorator activated via a :term:`scan` which treats the @@ -161,3 +162,64 @@ class ApplicationCreated(object): WSGIApplicationCreatedEvent = ApplicationCreated # b/c (as of 1.0) +class BeforeRender(dict): + implements(IBeforeRender) + """ + Subscribers to this event may introspect the and modify the set of + :term:`renderer globals` before they are passed to a :term:`renderer`. + This event object iself has a dictionary-like interface that can be used + for this purpose. For example:: + + from repoze.events import subscriber + from pyramid.interfaces import IBeforeRender + + @subscriber(IBeforeRender) + def add_global(event): + event['mykey'] = 'foo' + + An object of this type is sent as an event just before a :term:`renderer` + is invoked (but *after* the application-level renderer globals factory + added via + :class:`pyramid.configuration.Configurator.set_renderer_globals_factory`, + if any, has injected its own keys into the renderer globals dictionary). + + If a subscriber attempts to add a key that already exist in the renderer + globals dictionary, a :exc:`KeyError` is raised. This limitation is + enforced because event subscribers do not possess any relative ordering. + The set of keys added to the renderer globals dictionary by all + :class:`pyramid.events.BeforeRender` subscribers and renderer globals + factories must be unique. """ + + def __init__(self, system): + self._system = system + + def __setitem__(self, name, value): + """ Set a name/value pair into the dictionary which is passed to a + renderer as the renderer globals dictionary. If the ``name`` already + exists in the target dictionary, a :exc:`KeyError` will be raised.""" + if name in self._system: + raise KeyError('%s is already a renderer globals value' % name) + self._system[name] = value + + def update(self, d): + """ Update the renderer globals dictionary with another dictionary + ``d``. If any of the key names in the source dictionary already exist + in the target dictionary, a :exc:`KeyError` will be raised""" + for k, v in d.items(): + self[k] = v + + def __contains__(self, k): + """ Return ``True`` if ``k`` exists in the renderer globals + dictionary.""" + return k in self._system + + def __getitem__(self, k): + """ Return the value for key ``k`` from the renderer globals + dictionary.""" + return self._system[k] + + def get(self, k, default=None): + """ Return the value for key ``k`` from the renderer globals + dictionary, or the default if no such value exists.""" + return self._system.get(k) + diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index 3b818c3cd..461a0d82c 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -62,6 +62,44 @@ class IExceptionResponse(IException, IResponse): (:class:`pyramid.exceptions.NotFound` and :class:`pyramid.exceptions.Forbidden`).""" +class IBeforeRender(Interface): + """ + Subscribers to this event may introspect the and modify the set of + :term:`renderer globals` before they are passed to a :term:`renderer`. + This event object iself has a dictionary-like interface that can be used + for this purpose. For example:: + + from repoze.events import subscriber + from pyramid.interfaces import IBeforeRender + + @subscriber(IBeforeRender) + def add_global(event): + event['mykey'] = 'foo' + + See also :ref:`beforerender_event`. + """ + def __setitem__(name, value): + """ Set a name/value pair into the dictionary which is passed to a + renderer as the renderer globals dictionary. If the ``name`` already + exists in the target dictionary, a :exc:`NameError` will be raised.""" + + def update(d): + """ Update the renderer globals dictionary with another dictionary + ``d``. If any of the key names in the source dictionary already exist + in the target dictionary, a :exc:`NameError` will be raised""" + + def __contains__(k): + """ Return ``True`` if ``k`` exists in the renderer globals + dictionary.""" + + def __getitem__(k): + """ Return the value for key ``k`` from the renderer globals + dictionary.""" + + def get(k, default=None): + """ Return the value for key ``k`` from the renderer globals + dictionary, or the default if no such value exists.""" + # internal interfaces class IRequest(Interface): @@ -503,3 +541,4 @@ class IRendererInfo(Interface): 'renderer was created') settings = Attribute('The ISettings dictionary related to the current app') + diff --git a/pyramid/renderers.py b/pyramid/renderers.py index 92efc7b0e..4854041c8 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -15,6 +15,7 @@ from pyramid.interfaces import IRendererInfo from pyramid.compat import json from pyramid.decorator import reify +from pyramid.events import BeforeRender from pyramid.path import caller_package from pyramid.path import package_path from pyramid.resource import resource_spec_from_abspath @@ -264,6 +265,8 @@ class RendererHelper(object): if renderer_globals: system_values.update(renderer_globals) + registry.notify(BeforeRender(system_values)) + result = renderer(value, system_values) return result diff --git a/pyramid/tests/test_events.py b/pyramid/tests/test_events.py index 7d763b3d6..0cf0cc6dd 100644 --- a/pyramid/tests/test_events.py +++ b/pyramid/tests/test_events.py @@ -154,6 +154,70 @@ class TestSubscriber(unittest.TestCase): self.assertEqual(dummy_venusian.attached, [(foo, dec.register, 'pyramid')]) +class TestBeforeRender(unittest.TestCase): + def _makeOne(self, system): + from pyramid.events import BeforeRender + return BeforeRender(system) + + def test_instance_conforms(self): + from zope.interface.verify import verifyObject + from pyramid.interfaces import IBeforeRender + event = self._makeOne({}) + verifyObject(IBeforeRender, event) + + def test_setitem_success(self): + system = {} + event = self._makeOne(system) + event['a'] = 1 + self.assertEqual(system, {'a':1}) + + def test_setitem_fail(self): + system = {'a':1} + event = self._makeOne(system) + self.assertRaises(KeyError, event.__setitem__, 'a', 1) + + def test_update_success(self): + system = {'a':1} + event = self._makeOne(system) + event.update({'b':2}) + self.assertEqual(system, {'a':1, 'b':2}) + + def test_update_fail(self): + system = {'a':1} + event = self._makeOne(system) + self.assertRaises(KeyError, event.update, {'a':1}) + + def test__contains__True(self): + system = {'a':1} + event = self._makeOne(system) + self.failUnless('a' in event) + + def test__contains__False(self): + system = {} + event = self._makeOne(system) + self.failIf('a' in event) + + def test__getitem__success(self): + system = {'a':1} + event = self._makeOne(system) + self.assertEqual(event['a'], 1) + + def test__getitem__fail(self): + system = {} + event = self._makeOne(system) + self.assertRaises(KeyError, event.__getitem__, 'a') + + def test_get_success(self): + system = {'a':1} + event = self._makeOne(system) + self.assertEqual(event.get('a'), 1) + + def test_get_fail(self): + system = {} + event = self._makeOne(system) + self.assertEqual(event.get('a'), None) + + class DummyConfigurator(object): def __init__(self): self.subscribed = [] diff --git a/pyramid/tests/test_renderers.py b/pyramid/tests/test_renderers.py index deaa039a2..38219b242 100644 --- a/pyramid/tests/test_renderers.py +++ b/pyramid/tests/test_renderers.py @@ -338,11 +338,15 @@ class TestRendererHelper(unittest.TestCase): def queryUtility(self, iface, name=None): self.queried = True return self.responses.pop(0) + def notify(self, event): + self.event = event reg = DummyRegistry() helper = self._makeOne('loo.foo', registry=reg) result = helper.render('value', {}) self.assertEqual(result, ('value', {})) self.failUnless(reg.queried) + self.assertEqual(reg.event._system, {}) + self.assertEqual(reg.event.__class__.__name__, 'BeforeRender') def test_render_system_values_is_None(self): self._registerRendererFactory() |
