summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt23
-rw-r--r--TODO.txt2
-rw-r--r--docs/api/events.rst3
-rw-r--r--docs/api/interfaces.rst2
-rw-r--r--docs/narr/hooks.rst41
-rw-r--r--pyramid/events.py62
-rw-r--r--pyramid/interfaces.py39
-rw-r--r--pyramid/renderers.py3
-rw-r--r--pyramid/tests/test_events.py64
-rw-r--r--pyramid/tests/test_renderers.py4
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)
-----------------------------------
diff --git a/TODO.txt b/TODO.txt
index 56038e3c0..dfb0a1712 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -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()