diff options
| author | Chris McDonough <chrism@plope.com> | 2012-08-25 00:10:46 -0400 |
|---|---|---|
| committer | Chris McDonough <chrism@plope.com> | 2012-08-25 00:10:46 -0400 |
| commit | 95f766bc7c380797c569da464f1f41d12b05bdbe (patch) | |
| tree | e89b3566d189aebd26d4fdb93bfd720817608f32 | |
| parent | bf64f1e841fe39597402dea8346abd2cd900d0dd (diff) | |
| download | pyramid-95f766bc7c380797c569da464f1f41d12b05bdbe.tar.gz pyramid-95f766bc7c380797c569da464f1f41d12b05bdbe.tar.bz2 pyramid-95f766bc7c380797c569da464f1f41d12b05bdbe.zip | |
Subscriber predicates:
- Add ``add_subscriber_predicate`` method to Configurator.
- Allow ``add_subscriber`` and ``subscriber`` venusian decorator to accept ``**predicates`` arguments.
- Document subscriber predicate feature.
- Share more code between view, route, and subscriber related method wrt predicates.
| -rw-r--r-- | CHANGES.txt | 13 | ||||
| -rw-r--r-- | docs/glossary.rst | 3 | ||||
| -rw-r--r-- | docs/narr/hooks.rst | 122 | ||||
| -rw-r--r-- | pyramid/config/__init__.py | 29 | ||||
| -rw-r--r-- | pyramid/config/adapters.py | 94 | ||||
| -rw-r--r-- | pyramid/config/routes.py | 41 | ||||
| -rw-r--r-- | pyramid/config/views.py | 39 | ||||
| -rw-r--r-- | pyramid/events.py | 17 | ||||
| -rw-r--r-- | pyramid/tests/test_config/test_adapters.py | 127 | ||||
| -rw-r--r-- | pyramid/tests/test_events.py | 21 |
10 files changed, 405 insertions, 101 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index d20257679..369e9d74d 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -40,9 +40,11 @@ Bug Fixes Features -------- -- Third-party custom view and route predicates can now be added for use by - view authors via ``pyramid.config.Configurator.add_view_predicate`` and - ``pyramid.config.Configurator.add_route_predicate``. So, for example, +- Third-party custom view, route, and subscriber predicates can now be added + for use by view authors via + ``pyramid.config.Configurator.add_view_predicate``, + ``pyramid.config.Configurator.add_route_predicate`` and + ``pyramid.config.Configurator.add_subscriber_predicate``. So, for example, doing this:: config.add_view_predicate('abc', my.package.ABCPredicate) @@ -52,8 +54,9 @@ Features @view_config(abc=1) - See "Adding A Third Party View or Route Predicate" in the Hooks chapter for - more information. + Similar features exist for ``add_route``, and ``add_subscriber``. See + "Adding A Third Party View, Route, or Subscriber Predicate" in the Hooks + chapter for more information. Note that changes made to support the above feature now means that only actions registered using the same "order" can conflict with one another. diff --git a/docs/glossary.rst b/docs/glossary.rst index ba3203f89..34cf1b078 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -997,6 +997,7 @@ Glossary predicate factory A callable which is used by a third party during the registration of a - route or view predicates to extend the view and route configuration + route, view, or subscriber predicates to extend the configuration system. See :ref:`registering_thirdparty_predicates` for more information. + diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index 2c15cd690..96fa77a07 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -1235,17 +1235,23 @@ implict and explicit tween chains used by an application. See .. _registering_thirdparty_predicates: -Adding A Third Party View or Route Predicate --------------------------------------------- +Adding A Third Party View, Route, or Subscriber Predicate +--------------------------------------------------------- .. note:: - Third-party predicates are a feature new as of Pyramid 1.4. + Third-party view, route, and subscriber predicates are a feature new as of + Pyramid 1.4. -View and route predicates used during view configuration allow you to narrow -the set of circumstances under which a view or route will match. For -example, the ``request_method`` view predicate can be used to ensure a view -callable is only invoked when the request's method is ``POST``: +.. _view_and_route_predicates: + +View and Route Predicates +~~~~~~~~~~~~~~~~~~~~~~~~~ + +View and route predicates used during configuration allow you to narrow the +set of circumstances under which a view or route will match. For example, +the ``request_method`` view predicate can be used to ensure a view callable +is only invoked when the request's method is ``POST``: .. code-block:: python @@ -1286,9 +1292,9 @@ The first argument to :meth:`pyramid.config.Configurator.add_view_predicate`, the name, is a string representing the name that is expected to be passed to ``view_config`` (or its imperative analogue ``add_view``). -The second argument is a predicate factory. A predicate factory is most -often a class with a constructor (``__init__``), a ``text`` method, a -``phash`` method and a ``__call__`` method. For example: +The second argument is a view or route predicate factory. A view or route +predicate factory is most often a class with a constructor (``__init__``), a +``text`` method, a ``phash`` method and a ``__call__`` method. For example: .. code-block:: python :linenos: @@ -1330,3 +1336,99 @@ You can use the same predicate factory as both a view predicate and as a route predicate, but you'll need to call ``add_view_predicate`` and ``add_route_predicate`` separately with the same factory. +.. _subscriber_predicates: + +Subscriber Predicates +~~~~~~~~~~~~~~~~~~~~~ + +Subscriber predicates work almost exactly like view and route predicates. +They narrow the set of circumstances in which a subscriber will be called. +There are several minor differences between a subscriber predicate and a +view/route predicate: + +- There are no default subscriber predicates. You must register one to use + one. + +- The ``__call__`` method of a subscriber predicate accepts a single + ``event`` object instead of a ``context`` and a ``request``. + +- Not every subscriber predicate can be used with every event type. Some + subscriber predicates will assume a certain event type. + +Here's an example of a subscriber predicate that can be used in conjunction +with a subscriber that subscribes to the :class:`pyramid.events.NewReqest` +event type. + +.. code-block:: python + :linenos: + + class RequestPathStartsWith(object): + def __init__(self, val, config): + self.val = val + + def text(self): + return 'path_startswith = %s' % (self.val,) + + phash = text + + def __call__(self, event): + return event.request.path.startswith(self.val) + +Once you've created a subscriber predicate, it may registered via +:meth:`pyramid.config.Configurator.add_subscriber_predicate`. For example: + +.. code-block:: python + + config.add_subscriber_predicate( + 'request_path_startswith', RequestPathStartsWith) + +Once a subscriber predicate is registered, you can use it in a call to +:meth:`pyramid.config.Configurator.add_subscriber` or to +:class:`pyramid.events.subscriber`. Here's an example of using the +previously registered ``request_path_startswith`` predicate in a call to +:meth:`~pyramid.config.Configurator.add_subscriber`: + +.. code-block:: python + :linenos: + + # define a subscriber in your code + + def yosubscriber(event): + event.request.yo = 'YO!' + + # and at configuration time + + config.add_subscriber(yosubscriber, NewRequest, + request_path_startswith='/add_yo') + +Here's the same subscriber/predicate/event-type combination used via +:class:`~pyramid.events.subscriber`. + +.. code-block:: python + :linenos: + + from pyramid.events import subscriber + + @subscriber(NewRequest, request_path_startswith='/add_yo') + def yosubscriber(event): + event.request.yo = 'YO!' + +In either of the above configurations, the ``yosubscriber`` callable will +only be called if the request path starts with ``/add_yo``. Otherwise the +event subscriber will not be called. + +Note that the ``request_path_startswith`` subscriber you defined can be used +with events that have a ``request`` attribute, but not ones that do not. So, +for example, the predicate can be used with subscribers registered for +:class:`pyramid.events.NewRequest` and :class:`pyramid.events.ContextFound` +events, but it cannot be used with subscribers registered for +:class:`pyramid.events.ApplicationCreated` because the latter type of event +has no ``request`` attribute. The point being: unlike route and view +predicates, not every type of subscriber predicate will necessarily be +applicable for use in every subscriber registration. It is not the +responsibility of the predicate author to make every predicate make sense for +every event type; it is the responsibility of the predicate consumer to use +predicates that make sense for a particular event type registration. + + + diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py index 6010740ca..de09cfbcf 100644 --- a/pyramid/config/__init__.py +++ b/pyramid/config/__init__.py @@ -12,6 +12,8 @@ from webob.exc import WSGIHTTPException as WebobWSGIHTTPException from pyramid.interfaces import ( IDebugLogger, IExceptionResponse, + IPredicateList, + PHASE1_CONFIG, ) from pyramid.asset import resolve_asset_spec @@ -71,6 +73,7 @@ from pyramid.config.tweens import TweensConfiguratorMixin from pyramid.config.util import ( action_method, ActionInfo, + PredicateList, ) from pyramid.config.views import ViewsConfiguratorMixin from pyramid.config.zca import ZCAConfiguratorMixin @@ -489,6 +492,32 @@ class Configurator( _get_introspector, _set_introspector, _del_introspector ) + def _get_predlist(self, name): + predlist = self.registry.queryUtility(IPredicateList, name=name) + if predlist is None: + predlist = PredicateList() + self.registry.registerUtility(predlist, IPredicateList, name=name) + return predlist + + def _add_predicate(self, type, name, factory, weighs_more_than=None, + weighs_less_than=None): + discriminator = ('%s predicate' % type, name) + intr = self.introspectable( + '%s predicates' % type, + discriminator, + '%s predicate named %s' % (type, name), + '%s predicate' % type) + intr['name'] = name + intr['factory'] = factory + intr['weighs_more_than'] = weighs_more_than + intr['weighs_less_than'] = weighs_less_than + def register(): + predlist = self._get_predlist(type) + predlist.add(name, factory, weighs_more_than=weighs_more_than, + weighs_less_than=weighs_less_than) + self.action(discriminator, register, introspectables=(intr,), + order=PHASE1_CONFIG) # must be registered early + @property def action_info(self): info = self.info # usually a ZCML action (ParserInfo) if self.info diff --git a/pyramid/config/adapters.py b/pyramid/config/adapters.py index 5f15f2e46..3af810990 100644 --- a/pyramid/config/adapters.py +++ b/pyramid/config/adapters.py @@ -1,3 +1,5 @@ +from functools import update_wrapper + from zope.interface import Interface from pyramid.interfaces import ( @@ -6,40 +8,103 @@ from pyramid.interfaces import ( IResourceURL, ) -from pyramid.config.util import action_method +from pyramid.config.util import ( + action_method, + ) + class AdaptersConfiguratorMixin(object): @action_method - def add_subscriber(self, subscriber, iface=None): + def add_subscriber(self, subscriber, iface=None, **predicates): """Add an event :term:`subscriber` for the event stream - implied by the supplied ``iface`` interface. The - ``subscriber`` argument represents a callable object (or a - :term:`dotted Python name` which identifies a callable); it - will be called with a single object ``event`` whenever - :app:`Pyramid` emits an :term:`event` associated with the - ``iface``, which may be an :term:`interface` or a class or a - :term:`dotted Python name` to a global object representing an - interface or a class. Using the default ``iface`` value, - ``None`` will cause the subscriber to be registered for all - event types. See :ref:`events_chapter` for more information - about events and subscribers.""" + implied by the supplied ``iface`` interface. + + The ``subscriber`` argument represents a callable object (or a + :term:`dotted Python name` which identifies a callable); it will be + called with a single object ``event`` whenever :app:`Pyramid` emits + an :term:`event` associated with the ``iface``, which may be an + :term:`interface` or a class or a :term:`dotted Python name` to a + global object representing an interface or a class. + + Using the default ``iface`` value, ``None`` will cause the subscriber + to be registered for all event types. See :ref:`events_chapter` for + more information about events and subscribers. + + Any number of predicate keyword arguments may be passed in + ``**predicates``. Each predicate named will narrow the set of + circumstances that the subscriber will be invoked. Each named + predicate must have been registered via + :meth:`pyramid.config.Configurator.add_subscriber_predicate` before it + can be used. See :ref:`subscriber_predicates` for more information. + + .. note:: + + THe ``**predicates`` argument is new as of Pyramid 1.4. + """ dotted = self.maybe_dotted subscriber, iface = dotted(subscriber), dotted(iface) if iface is None: iface = (Interface,) if not isinstance(iface, (tuple, list)): iface = (iface,) + def register(): - self.registry.registerHandler(subscriber, iface) + predlist = self._get_predlist('subscriber') + order, preds, phash = predlist.make(self, **predicates) + intr.update({'phash':phash, 'order':order, 'predicates':preds}) + derived_subscriber = self._derive_subscriber(subscriber, preds) + self.registry.registerHandler(derived_subscriber, iface) + intr = self.introspectable('subscribers', id(subscriber), self.object_description(subscriber), 'subscriber') + intr['subscriber'] = subscriber intr['interfaces'] = iface + self.action(None, register, introspectables=(intr,)) return subscriber + def _derive_subscriber(self, subscriber, predicates): + if not predicates: + return subscriber + def subscriber_wrapper(event): + if all((predicate(event) for predicate in predicates)): + subscriber(event) + if hasattr(subscriber, '__name__'): + update_wrapper(subscriber_wrapper, subscriber) + return subscriber_wrapper + + @action_method + def add_subscriber_predicate(self, name, factory, weighs_more_than=None, + weighs_less_than=None): + """ + Adds a subscriber predicate factory. The associated subscriber + predicate can later be named as a keyword argument to + :meth:`pyramid.config.Configurator.add_subscriber` in the + ``**predicates`` anonyous keyword argument dictionary. + + ``name`` should be the name of the predicate. It must be a valid + Python identifier (it will be used as a ``**predicates`` keyword + argument to :meth:`~pyramid.config.Configurator.add_subscriber`). + + ``factory`` should be a :term:`predicate factory`. + + See :ref:`subscriber_predicates` for more information. + + .. note:: + + This method is new as of Pyramid 1.4. + """ + self._add_predicate( + 'subscriber', + name, + factory, + weighs_more_than=weighs_more_than, + weighs_less_than=weighs_less_than + ) + @action_method def add_response_adapter(self, adapter, type_or_iface): """ When an object of type (or interface) ``type_or_iface`` is @@ -203,4 +268,3 @@ class AdaptersConfiguratorMixin(object): intr['resource_iface'] = resource_iface self.action(discriminator, register, introspectables=(intr,)) - diff --git a/pyramid/config/routes.py b/pyramid/config/routes.py index 18fe39e45..6796a1c5d 100644 --- a/pyramid/config/routes.py +++ b/pyramid/config/routes.py @@ -1,7 +1,6 @@ import warnings from pyramid.interfaces import ( - IPredicateList, IRequest, IRouteRequest, IRoutesMapper, @@ -17,7 +16,6 @@ from pyramid.urldispatch import RoutesMapper from pyramid.config.util import ( action_method, as_sorted_tuple, - PredicateList, ) import pyramid.config.predicates @@ -265,7 +263,7 @@ class RoutesConfiguratorMixin(object): registered via :meth:`pyramid.config.Configurator.add_view_predicate`. More than one key/value pair can be used at the same time. See - :ref:`registering_thirdparty_predicates` for more information about + :ref:`view_and_route_predicates` for more information about third-party predicates. This argument is new as of Pyramid 1.4. View-Related Arguments @@ -434,7 +432,7 @@ class RoutesConfiguratorMixin(object): ) ) - predlist = self.route_predlist + predlist = self._get_predlist('route') _, preds, _ = predlist.make(self, **pvals) route = mapper.connect( name, pattern, factory, predicates=preds, @@ -466,15 +464,6 @@ class RoutesConfiguratorMixin(object): attr=view_attr, ) - @property - def route_predlist(self): - predlist = self.registry.queryUtility(IPredicateList, name='route') - if predlist is None: - predlist = PredicateList() - self.registry.registerUtility(predlist, IPredicateList, - name='route') - return predlist - @action_method def add_route_predicate(self, name, factory, weighs_more_than=None, weighs_less_than=None): @@ -488,29 +477,19 @@ class RoutesConfiguratorMixin(object): ``factory`` should be a :term:`predicate factory`. - See :ref:`registering_thirdparty_predicates` for more information. + See :ref:`view_and_route_predicates` for more information. .. note:: This method is new as of Pyramid 1.4. """ - discriminator = ('route predicate', name) - intr = self.introspectable( - 'route predicates', - discriminator, - 'route predicate named %s' % name, - 'route predicate') - intr['name'] = name - intr['factory'] = factory - intr['weighs_more_than'] = weighs_more_than - intr['weighs_less_than'] = weighs_less_than - def register(): - predlist = self.route_predlist - predlist.add(name, factory, weighs_more_than=weighs_more_than, - weighs_less_than=weighs_less_than) - # must be registered before routes connected - self.action(discriminator, register, introspectables=(intr,), - order=PHASE1_CONFIG) + self._add_predicate( + 'route', + name, + factory, + weighs_more_than=weighs_more_than, + weighs_less_than=weighs_less_than + ) def add_default_route_predicates(self): p = pyramid.config.predicates diff --git a/pyramid/config/views.py b/pyramid/config/views.py index b61a71914..eaaaebdaf 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -20,7 +20,6 @@ from pyramid.interfaces import ( IException, IExceptionViewClassifier, IMultiView, - IPredicateList, IRendererFactory, IRequest, IResponse, @@ -79,7 +78,6 @@ from pyramid.config.util import ( DEFAULT_PHASH, MAX_ORDER, action_method, - PredicateList, ) urljoin = urlparse.urljoin @@ -1008,7 +1006,7 @@ class ViewsConfiguratorMixin(object): registered via :meth:`pyramid.config.Configurator.add_view_predicate`. More than one key/value pair can be used at the same time. See - :ref:`registering_thirdparty_predicates` for more information about + :ref:`view_and_route_predicates` for more information about third-party predicates. This argument is new as of Pyramid 1.4. """ @@ -1107,7 +1105,7 @@ class ViewsConfiguratorMixin(object): ) view_intr.update(**predicates) introspectables.append(view_intr) - predlist = self.view_predlist + predlist = self._get_predlist('view') def register(permission=permission, renderer=renderer): # the discrim_func above is guaranteed to have been called already @@ -1302,14 +1300,6 @@ class ViewsConfiguratorMixin(object): introspectables.append(perm_intr) self.action(discriminator, register, introspectables=introspectables) - @property - def view_predlist(self): - predlist = self.registry.queryUtility(IPredicateList, name='view') - if predlist is None: - predlist = PredicateList() - self.registry.registerUtility(predlist, IPredicateList, name='view') - return predlist - @action_method def add_view_predicate(self, name, factory, weighs_more_than=None, weighs_less_than=None): @@ -1324,28 +1314,19 @@ class ViewsConfiguratorMixin(object): ``factory`` should be a :term:`predicate factory`. - See :ref:`registering_thirdparty_predicates` for more information. + See :ref:`view_and_route_predicates` for more information. .. note:: This method is new as of Pyramid 1.4. """ - discriminator = ('view predicate', name) - intr = self.introspectable( - 'view predicates', - discriminator, - 'view predicate named %s' % name, - 'view predicate') - intr['name'] = name - intr['factory'] = factory - intr['weighs_more_than'] = weighs_more_than - intr['weighs_less_than'] = weighs_less_than - def register(): - predlist = self.view_predlist - predlist.add(name, factory, weighs_more_than=weighs_more_than, - weighs_less_than=weighs_less_than) - self.action(discriminator, register, introspectables=(intr,), - order=PHASE1_CONFIG) # must be registered before views added + self._add_predicate( + 'view', + name, + factory, + weighs_more_than=weighs_more_than, + weighs_less_than=weighs_less_than + ) def add_default_view_predicates(self): p = pyramid.config.predicates diff --git a/pyramid/events.py b/pyramid/events.py index db274823c..836466ba2 100644 --- a/pyramid/events.py +++ b/pyramid/events.py @@ -14,9 +14,10 @@ from pyramid.interfaces import ( ) 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. + """ Decorator activated via a :term:`scan` which treats the function + being decorated as an event subscriber for the set of interfaces passed + as ``*ifaces`` and the set of predicate terms passed as ``**predicates`` + to the decorator constructor. For example: @@ -61,16 +62,22 @@ class subscriber(object): config = Configurator() config.scan('somepackage_containing_subscribers') + Any ``**predicate`` arguments will be passed along to + :meth:`pyramid.config.Configurator.add_subscriber`. See + :ref:`subscriber_predicates` for a description of how predicates can + narrow the set of circumstances in which a subscriber will be called. + """ venusian = venusian # for unit testing - def __init__(self, *ifaces): + def __init__(self, *ifaces, **predicates): self.ifaces = ifaces + self.predicates = predicates def register(self, scanner, name, wrapped): config = scanner.config for iface in self.ifaces or (Interface,): - config.add_subscriber(wrapped, iface) + config.add_subscriber(wrapped, iface, **self.predicates) def __call__(self, wrapped): self.venusian.attach(wrapped, self.register, category='pyramid') diff --git a/pyramid/tests/test_config/test_adapters.py b/pyramid/tests/test_config/test_adapters.py index 83ea0f05b..56d2acc2f 100644 --- a/pyramid/tests/test_config/test_adapters.py +++ b/pyramid/tests/test_config/test_adapters.py @@ -81,6 +81,121 @@ class AdaptersConfiguratorMixinTests(unittest.TestCase): config.registry.subscribers((event.object, IDummy), None) self.assertEqual(len(L), 1) + def test_add_subscriber_with_specific_type_and_predicates_True(self): + from zope.interface import implementer + from zope.interface import Interface + class IEvent(Interface): + pass + @implementer(IEvent) + class Event: + pass + L = [] + def subscriber(event): + L.append(event) + config = self._makeOne(autocommit=True) + predlist = config._get_predlist('subscriber') + jam_predicate = predicate_maker('jam') + jim_predicate = predicate_maker('jim') + predlist.add('jam', jam_predicate) + predlist.add('jim', jim_predicate) + config.add_subscriber(subscriber, IEvent, jam=True, jim=True) + event = Event() + event.jam = True + event.jim = True + config.registry.notify(event) + self.assertEqual(len(L), 1) + self.assertEqual(L[0], event) + config.registry.notify(object()) + self.assertEqual(len(L), 1) + + def test_add_subscriber_with_default_type_predicates_True(self): + from zope.interface import implementer + from zope.interface import Interface + class IEvent(Interface): + pass + @implementer(IEvent) + class Event: + pass + L = [] + def subscriber(event): + L.append(event) + config = self._makeOne(autocommit=True) + predlist = config._get_predlist('subscriber') + jam_predicate = predicate_maker('jam') + jim_predicate = predicate_maker('jim') + predlist.add('jam', jam_predicate) + predlist.add('jim', jim_predicate) + config.add_subscriber(subscriber, jam=True, jim=True) + event = Event() + event.jam = True + event.jim = True + config.registry.notify(event) + self.assertEqual(len(L), 1) + self.assertEqual(L[0], event) + config.registry.notify(object()) + self.assertEqual(len(L), 1) + + def test_add_subscriber_with_specific_type_and_predicates_False(self): + from zope.interface import implementer + from zope.interface import Interface + class IEvent(Interface): + pass + @implementer(IEvent) + class Event: + pass + L = [] + def subscriber(event): L.append(event) + config = self._makeOne(autocommit=True) + predlist = config._get_predlist('subscriber') + jam_predicate = predicate_maker('jam') + jim_predicate = predicate_maker('jim') + predlist.add('jam', jam_predicate) + predlist.add('jim', jim_predicate) + config.add_subscriber(subscriber, IEvent, jam=True, jim=True) + event = Event() + event.jam = True + event.jim = False + config.registry.notify(event) + self.assertEqual(len(L), 0) + + def test_add_subscriber_with_default_type_predicates_False(self): + from zope.interface import implementer + from zope.interface import Interface + class IEvent(Interface): + pass + @implementer(IEvent) + class Event: + pass + L = [] + def subscriber(event): L.append(event) + config = self._makeOne(autocommit=True) + predlist = config._get_predlist('subscriber') + jam_predicate = predicate_maker('jam') + jim_predicate = predicate_maker('jim') + predlist.add('jam', jam_predicate) + predlist.add('jim', jim_predicate) + config.add_subscriber(subscriber, jam=True, jim=True) + event = Event() + event.jam = False + event.jim = True + config.registry.notify(event) + self.assertEqual(len(L), 0) + + def test_add_subscriber_predicate(self): + config = self._makeOne() + L = [] + def add_predicate(type, name, factory, weighs_less_than=None, + weighs_more_than=None): + self.assertEqual(type, 'subscriber') + self.assertEqual(name, 'name') + self.assertEqual(factory, 'factory') + self.assertEqual(weighs_more_than, 1) + self.assertEqual(weighs_less_than, 2) + L.append(1) + config._add_predicate = add_predicate + config.add_subscriber_predicate('name', 'factory', 1, 2) + self.assertTrue(L) + def test_add_response_adapter(self): from pyramid.interfaces import IResponse config = self._makeOne(autocommit=True) @@ -228,4 +343,14 @@ class DummyResourceURL(object): self.resource = resource self.request = request - +def predicate_maker(name): + class Predicate(object): + def __init__(self, val, config): + self.val = val + def phash(self): + return 'phash' + text = phash + def __call__(self, event): + return getattr(event, name, None) == self.val + return Predicate + diff --git a/pyramid/tests/test_events.py b/pyramid/tests/test_events.py index 3e9c959d9..2c72c07e8 100644 --- a/pyramid/tests/test_events.py +++ b/pyramid/tests/test_events.py @@ -131,9 +131,9 @@ class TestSubscriber(unittest.TestCase): def tearDown(self): testing.tearDown() - def _makeOne(self, *ifaces): + def _makeOne(self, *ifaces, **predicates): from pyramid.events import subscriber - return subscriber(*ifaces) + return subscriber(*ifaces, **predicates) def test_register_single(self): from zope.interface import Interface @@ -190,6 +190,16 @@ class TestSubscriber(unittest.TestCase): self.assertEqual(dummy_venusian.attached, [(foo, dec.register, 'pyramid')]) + def test_regsister_with_predicates(self): + from zope.interface import Interface + dec = self._makeOne(a=1) + def foo(): pass + config = DummyConfigurator() + scanner = Dummy() + scanner.config = config + dec.register(scanner, None, foo) + self.assertEqual(config.subscribed, [(foo, Interface, {'a':1})]) + class TestBeforeRender(unittest.TestCase): def _makeOne(self, system, val=None): from pyramid.events import BeforeRender @@ -264,8 +274,11 @@ class DummyConfigurator(object): def __init__(self): self.subscribed = [] - def add_subscriber(self, wrapped, ifaces): - self.subscribed.append((wrapped, ifaces)) + def add_subscriber(self, wrapped, ifaces, **predicates): + if not predicates: + self.subscribed.append((wrapped, ifaces)) + else: + self.subscribed.append((wrapped, ifaces, predicates)) class DummyRegistry(object): pass |
