diff options
Diffstat (limited to 'CHANGES.txt')
| -rw-r--r-- | CHANGES.txt | 150 |
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 --------- |
