From 28fc3d575107a95a977a049eb38f55c6c422813a Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 21 Nov 2012 06:26:40 -0500 Subject: - In order 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. For instance, if an event is 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 subscriber(event, context): pass With the event-only feature you can now write an event subscriber that accepts only ``event`` even if it subscribes to multiple interfaces:: @subscriber([SomeEvent, SomeContextType]) def subscriber(event): # this will work! 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 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 subscriber(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 subscriber(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. In the past, if you had a subscriber predicate like this:: 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) If you attempted to use the above predicate to condition a subscription that involved multiple interfaces, it would not work. You had to change it to accept the same arguments as the subscription itself. For example, you might have had to change its ``__call__`` method like so, adding a ``context`` argument:: def __call__(self, event, context): return event.request.path.startswith(self.val) With the event-only feature, you needn't make the change. Instead, you can write all 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 subscriptions: 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:: def __call__(self, context, event): return event.request.path.startswith(self.val) tl;dr: 1) Always use the event as the first argument to a multi-interface subscription and 2) Use only ``event`` in your subscriber and subscriber predicate parameter lists, no matter how many interfaces the subscriber is notified with, as long as the event object is the first argument passed to ``registry.notify``. This will result in the maximum amount of reusability of subscriber predicates. --- CHANGES.txt | 98 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) (limited to 'CHANGES.txt') diff --git a/CHANGES.txt b/CHANGES.txt index 5344cb7d1..a625af3f9 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -15,6 +15,104 @@ Features values in parameterized ``.ini`` file, e.g. ``pshell etc/development.ini http_port=8080``. See https://github.com/Pylons/pyramid/pull/714 +- In order 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. + + For instance, if an event is 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 subscriber(event, context): + pass + + With the event-only feature you can now write an event subscriber that + accepts only ``event`` even if it subscribes to multiple interfaces:: + + @subscriber([SomeEvent, SomeContextType]) + def subscriber(event): + # this will work! + + 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 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 subscriber(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 subscriber(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. In the past, if + you had a subscriber predicate like this:: + + 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) + + If you attempted to use the above predicate to condition a subscription that + involved multiple interfaces, it would not work. You had to change it to + accept the same arguments as the subscription itself. For example, you might + have had to change its ``__call__`` method like so, adding a ``context`` + argument:: + + def __call__(self, event, context): + return event.request.path.startswith(self.val) + + With the event-only feature, you needn't make the change. Instead, you can + write all 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 + subscriptions: 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:: + + def __call__(self, context, event): + return event.request.path.startswith(self.val) + + tl;dr: 1) Always use the event as the first argument to a multi-interface + subscription and 2) Use only ``event`` in your subscriber and subscriber + predicate parameter lists, no matter how many interfaces the subscriber is + notified with, as long as the event object is the first argument passed to + ``registry.notify``. This will result in the maximum amount of reusability + of subscriber predicates. + Bug Fixes --------- -- cgit v1.2.3 From f700d374e903a501bc32a4b10774e87c74edc4d8 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 21 Nov 2012 07:01:00 -0500 Subject: garden --- CHANGES.txt | 172 ++++++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 110 insertions(+), 62 deletions(-) (limited to 'CHANGES.txt') diff --git a/CHANGES.txt b/CHANGES.txt index a625af3f9..4e3e45a79 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -15,16 +15,10 @@ Features values in parameterized ``.ini`` file, e.g. ``pshell etc/development.ini http_port=8080``. See https://github.com/Pylons/pyramid/pull/714 -- In order 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. - - For instance, if an event is sent by code like this:: +- 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) @@ -33,28 +27,101 @@ Features its argument list:: @subscriber([SomeEvent, SomeContextType]) - def subscriber(event, context): + 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 in most cases. + + 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" in a prior 1.4 alpha release. Subscriber predicates + are used to narrow the set of circumstances under which a subscriber will be + executed. 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 asubscriber(event, context): pass - With the event-only feature you can now write an event subscriber that - accepts only ``event`` even if it subscribes to multiple interfaces:: + @subscriber(SomeOtherEvent, mypredicate=True) + def asubscriber(event): + pass + + If you had previously written your ``mypredicate`` subscriber predicate that + accepted 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. + + To hack around this limitation, you needed to design a ``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 subscriber(event): + def asubscriber(event): # this will work! - 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:: + 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 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:: + 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 subscriber(event): + def asubscriber(event): # bzzt! you'll be getting the context here as ``event``, and it'll # be useless @@ -63,55 +130,36 @@ Features and the first interface is not the event interface. For example:: @subscriber([SomeContextType, SomeEvent]) - def subscriber(context, event): + 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. In the past, if - you had a subscriber predicate like this:: - - 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) - - If you attempted to use the above predicate to condition a subscription that - involved multiple interfaces, it would not work. You had to change it to - accept the same arguments as the subscription itself. For example, you might - have had to change its ``__call__`` method like so, adding a ``context`` - argument:: - - def __call__(self, event, context): - return event.request.path.startswith(self.val) - - With the event-only feature, you needn't make the change. Instead, you can - write all 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 - subscriptions: 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:: + 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) Always use the event as the first argument to a multi-interface - subscription and 2) Use only ``event`` in your subscriber and subscriber - predicate parameter lists, no matter how many interfaces the subscriber is - notified with, as long as the event object is the first argument passed to - ``registry.notify``. This will result in the maximum amount of reusability - of subscriber predicates. + 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 --------- -- cgit v1.2.3 From a3810e706bce12c0195737c2713362795613e2ae Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 21 Nov 2012 07:05:04 -0500 Subject: garden --- CHANGES.txt | 48 ++++++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 22 deletions(-) (limited to 'CHANGES.txt') diff --git a/CHANGES.txt b/CHANGES.txt index 4e3e45a79..01fd60161 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -34,16 +34,16 @@ Features 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 in most cases. + 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" in a prior 1.4 alpha release. Subscriber predicates - are used to narrow the set of circumstances under which a subscriber will be - executed. 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 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): @@ -56,33 +56,37 @@ Features And you wanted to use a subscriber predicate:: @subscriber([SomeEvent, SomeContextType], mypredicate=True) - def asubscriber(event, context): + def asubscriber1(event, context): pass @subscriber(SomeOtherEvent, mypredicate=True) - def asubscriber(event): + def asubscriber2(event): pass - If you had previously written your ``mypredicate`` subscriber predicate that - accepted 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:: + 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. + 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 needed to design a ``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:: + 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... -- cgit v1.2.3