From 82efa44c0d8f4b18b4f341519f54ecad68b56364 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 12 Jul 2011 03:52:42 -0400 Subject: - Previously, If a ``BeforeRender`` event subscriber added a value via the ``__setitem__`` or ``update`` methods of the event object with a key that already existed in the renderer globals dictionary, a ``KeyError`` was raised. With the deprecation of the "add_renderer_globals" feature of the configurator, there was no way to override an existing value in the renderer globals dictionary that already existed. Now, the event object will overwrite an older value that is already in the globals dictionary when its ``__setitem__`` or ``update`` is called (as well as the new ``setdefault`` method), just like a plain old dictionary. As a result, for maximum interoperability with other third-party subscribers, if you write an event subscriber meant to be used as a BeforeRender subscriber, your subscriber code will now need to (using ``.get`` or ``__contains__`` of the event object) ensure no value already exists in the renderer globals dictionary before setting an overriding value. --- CHANGES.txt | 21 +++++++++++++++++++++ docs/whatsnew-1.1.rst | 16 ++++++++++++++++ pyramid/events.py | 45 +++++++++++++++++++++++++------------------- pyramid/interfaces.py | 13 +++++++++---- pyramid/tests/test_events.py | 21 +++++++++++++-------- 5 files changed, 85 insertions(+), 31 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 2dead04b4..b9e645a38 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,24 @@ +Next Release +============ + +Behavior Changes +---------------- + +- Previously, If a ``BeforeRender`` event subscriber added a value via the + ``__setitem__`` or ``update`` methods of the event object with a key that + already existed in the renderer globals dictionary, a ``KeyError`` was + raised. With the deprecation of the "add_renderer_globals" feature of the + configurator, there was no way to override an existing value in the + renderer globals dictionary that already existed. Now, the event object + will overwrite an older value that is already in the globals dictionary + when its ``__setitem__`` or ``update`` is called (as well as the new + ``setdefault`` method), just like a plain old dictionary. As a result, for + maximum interoperability with other third-party subscribers, if you write + an event subscriber meant to be used as a BeforeRender subscriber, your + subscriber code will now need to (using ``.get`` or ``__contains__`` of the + event object) ensure no value already exists in the renderer globals + dictionary before setting an overriding value. + 1.1b1 (2011-07-10) ================== diff --git a/docs/whatsnew-1.1.rst b/docs/whatsnew-1.1.rst index 8cf6c715c..345cbfa30 100644 --- a/docs/whatsnew-1.1.rst +++ b/docs/whatsnew-1.1.rst @@ -481,6 +481,22 @@ Deprecations and Behavior Differences def expects_object_event(object, event): print object, event +- In 1.0, if a :class:`pyramid.events.BeforeRender` event subscriber added a + value via the ``__setitem__`` or ``update`` methods of the event object + with a key that already existed in the renderer globals dictionary, a + ``KeyError`` was raised. With the deprecation of the + "add_renderer_globals" feature of the configurator, there was no way to + override an existing value in the renderer globals dictionary that already + existed. Now, the event object will overwrite an older value that is + already in the globals dictionary when its ``__setitem__`` or ``update`` is + called (as well as the new ``setdefault`` method), just like a plain old + dictionary. As a result, for maximum interoperability with other + third-party subscribers, if you write an event subscriber meant to be used + as a BeforeRender subscriber, your subscriber code will now need to (using + ``.get`` or ``__contains__`` of the event object) ensure no value already + exists in the renderer globals dictionary before setting an overriding + value. + Dependency Changes ------------------ diff --git a/pyramid/events.py b/pyramid/events.py index 22cbf0cb2..b536bfd67 100644 --- a/pyramid/events.py +++ b/pyramid/events.py @@ -179,35 +179,42 @@ class BeforeRender(dict): 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.config.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. """ + is invoked (but *after* the -- deprecated -- application-level renderer + globals factory added via + :class:`pyramid.config.Configurator.set_renderer_globals_factory`, if + any, has injected its own keys into the renderer globals dictionary). + + If a subscriber adds a key via ``__setitem__`` or that already exists in + the renderer globals dictionary, it will overwrite an older value that is + already in the globals dictionary. This can be problematic because event + subscribers to the BeforeRender event do not possess any relative + ordering. For maximum interoperability with other third-party + subscribers, if you write an event subscriber meant to be used as a + BeforeRender subscriber, your subscriber code will need to (using + ``.get`` or ``__contains__`` of the event object) ensure no value already + exists in the renderer globals dictionary before setting an overriding + value.""" 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) + renderer as the renderer globals dictionary.""" self._system[name] = value + def setdefault(self, name, default=None): + """ Return the existing value for ``name`` in the renderers globals + dictionary. If no value with ``name`` exists in the dictionary, set + the ``default`` value into the renderer globals dictionary under the + name passed. If a value already existed in the dictionary, return + it. If a value did not exist in the dictionary, return the default""" + return self._system.setdefault(name, default) + 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 + ``d``.""" + return self._system.update(d) def __contains__(self, k): """ Return ``True`` if ``k`` exists in the renderer globals diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index fee8d549d..4ef58846b 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -284,13 +284,18 @@ class IBeforeRender(Interface): """ 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:`KeyError` will be raised.""" + renderer as the renderer globals dictionary. """ + + def setdefault(name, default=None): + """ Return the existing value for ``name`` in the renderers globals + dictionary. If no value with ``name`` exists in the dictionary, set + the ``default`` value into the renderer globals dictionary under the + name passed. If a value already existed in the dictionary, return + it. If a value did not exist in the dictionary, return the default""" 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:`KeyError` will be raised""" + ``d``. """ def __contains__(k): """ Return ``True`` if ``k`` exists in the renderer globals diff --git a/pyramid/tests/test_events.py b/pyramid/tests/test_events.py index 09f5f17ab..e3a10ccad 100644 --- a/pyramid/tests/test_events.py +++ b/pyramid/tests/test_events.py @@ -195,10 +195,20 @@ class TestBeforeRender(unittest.TestCase): event['a'] = 1 self.assertEqual(system, {'a':1}) - def test_setitem_fail(self): - system = {'a':1} + def test_setdefault_fail(self): + system = {} + event = self._makeOne(system) + result = event.setdefault('a', 1) + self.assertEqual(result, 1) + self.assertEqual(system, {'a':1}) + + def test_setdefault_success(self): + system = {} event = self._makeOne(system) - self.assertRaises(KeyError, event.__setitem__, 'a', 1) + event['a'] = 1 + result = event.setdefault('a', 2) + self.assertEqual(result, 1) + self.assertEqual(system, {'a':1}) def test_update_success(self): system = {'a':1} @@ -206,11 +216,6 @@ class TestBeforeRender(unittest.TestCase): 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) -- cgit v1.2.3