summaryrefslogtreecommitdiff
path: root/CHANGES.txt
diff options
context:
space:
mode:
Diffstat (limited to 'CHANGES.txt')
-rw-r--r--CHANGES.txt150
1 files changed, 150 insertions, 0 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 5344cb7d1..01fd60161 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -15,6 +15,156 @@ Features
values in parameterized ``.ini`` file, e.g. ``pshell etc/development.ini
http_port=8080``. See https://github.com/Pylons/pyramid/pull/714
+- A somewhat advanced and obscure feature of Pyramid event handlers is their
+ ability to handle "multi-interface" notifications. These notifications have
+ traditionally presented multiple objects to the subscriber callable. For
+ instance, if an event was sent by code like this::
+
+ registry.notify(event, context)
+
+ In the past, in order to catch such an event, you were obligated to write and
+ register an event subscriber that mentioned both the event and the context in
+ its argument list::
+
+ @subscriber([SomeEvent, SomeContextType])
+ def asubscriber(event, context):
+ pass
+
+ In many subscriber callables registered this way, it was common for the logic
+ in the subscriber callable to completely ignore the second and following
+ arguments (e.g. ``context`` in the above example might be ignored), because
+ they usually existed as attributes of the event anyway. You could usually
+ get the same value by doing ``event.context`` or similar.
+
+ The fact that you needed to put an extra argument which you usually ignored
+ in the subscriber callable body was only a minor annoyance until we added
+ "subscriber predicates", used to narrow the set of circumstances under which
+ a subscriber will be executed, in a prior 1.4 alpha release. Once those were
+ added, the annoyance was escalated, because subscriber predicates needed to
+ accept the same argument list and arity as the subscriber callables that they
+ were configured against. So, for example, if you had these two subscriber
+ registrations in your code::
+
+ @subscriber([SomeEvent, SomeContextType])
+ def asubscriber(event, context):
+ pass
+
+ @subscriber(SomeOtherEvent)
+ def asubscriber(event):
+ pass
+
+ And you wanted to use a subscriber predicate::
+
+ @subscriber([SomeEvent, SomeContextType], mypredicate=True)
+ def asubscriber1(event, context):
+ pass
+
+ @subscriber(SomeOtherEvent, mypredicate=True)
+ def asubscriber2(event):
+ pass
+
+ If an existing ``mypredicate`` subscriber predicate had been written in such
+ a way that it accepted only one argument in its ``__call__``, you could not
+ use it against a subscription which named more than one interface in its
+ subscriber interface list. Similarly, if you had written a subscriber
+ predicate that accepted two arguments, you couldn't use it against a
+ registration that named only a single interface type.
+
+ For example, if you created this predicate::
+
+ class MyPredicate(object):
+ # portions elided...
+ def __call__(self, event):
+ return self.val == event.context.foo
+
+ It would not work against a multi-interface-registered subscription, so in
+ the above example, when you attempted to use it against ``asubscriber1``, it
+ would fail at runtime with a TypeError, claiming something was attempting to
+ call it with too many arguments.
+
+ To hack around this limitation, you were obligated to design the
+ ``mypredicate`` predicate to expect to receive in its ``__call__`` either a
+ single ``event`` argument (a SomeOtherEvent object) *or* a pair of arguments
+ (a SomeEvent object and a SomeContextType object), presumably by doing
+ something like this::
+
+ class MyPredicate(object):
+ # portions elided...
+ def __call__(self, event, context=None):
+ return self.val == event.context.foo
+
+ This was confusing and bad.
+
+ In order to allow people to ignore unused arguments to subscriber callables
+ and to normalize the relationship between event subscribers and subscriber
+ predicates, we now allow both subscribers and subscriber predicates to accept
+ only a single ``event`` argument even if they've been subscribed for
+ notifications that involve multiple interfaces. Subscribers and subscriber
+ predicates that accept only one argument will receive the first object passed
+ to ``notify``; this is typically (but not always) the event object. The
+ other objects involved in the subscription lookup will be discarded. You can
+ now write an event subscriber that accepts only ``event`` even if it
+ subscribes to multiple interfaces::
+
+ @subscriber([SomeEvent, SomeContextType])
+ def asubscriber(event):
+ # this will work!
+
+ This prevents you from needing to match the subscriber callable parameters to
+ the subscription type unnecessarily, especially when you don't make use of
+ any argument in your subscribers except for the event object itself.
+
+ Note, however, that if the event object is not the first
+ object in the call to ``notify``, you'll run into trouble. For example, if
+ notify is called with the context argument first::
+
+ registry.notify(context, event)
+
+ You won't be able to take advantage of the event-only feature. It will
+ "work", but the object received by your event handler won't be the event
+ object, it will be the context object, which won't be very useful::
+
+ @subscriber([SomeContextType, SomeEvent])
+ def asubscriber(event):
+ # bzzt! you'll be getting the context here as ``event``, and it'll
+ # be useless
+
+ Existing multiple-argument subscribers continue to work without issue, so you
+ should continue use those if your system notifies using multiple interfaces
+ and the first interface is not the event interface. For example::
+
+ @subscriber([SomeContextType, SomeEvent])
+ def asubscriber(context, event):
+ # this will still work!
+
+ The event-only feature makes it possible to use a subscriber predicate that
+ accepts only a request argument within both multiple-interface subscriber
+ registrations and single-interface subscriber registrations. You needn't
+ make slightly different variations of predicates depending on the
+ subscription type arguments. Instead, just write all your subscriber
+ predicates so they only accept ``event`` in their ``__call__`` and they'll be
+ useful across all registrations for subscriptions that use an event as their
+ first argument, even ones which accept more than just ``event``.
+
+ However, the same caveat applies to predicates as to subscriber callables: if
+ you're subscribing to a multi-interface event, and the first interface is not
+ the event interface, the predicate won't work properly. In such a case,
+ you'll need to match the predicate ``__call__`` argument ordering and
+ composition to the ordering of the interfaces. For example, if the
+ registration for the subscription uses ``[SomeContext, SomeEvent]``, you'll
+ need to reflect that in the ordering of the parameters of the predicate's
+ ``__call__`` method::
+
+ def __call__(self, context, event):
+ return event.request.path.startswith(self.val)
+
+ tl;dr: 1) When using multi-interface subscriptions, always use the event type
+ as the first subscription registration argument and 2) When 1 is true, use
+ only ``event`` in your subscriber and subscriber predicate parameter lists,
+ no matter how many interfaces the subscriber is notified with. This
+ combination will result in the maximum amount of reusability of subscriber
+ predicates and the least amount of thought on your part. Drink responsibly.
+
Bug Fixes
---------