diff options
| -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() |
