tool v0.5.0 documentation

Signals

«  Debugging utilities   ::   Contents   ::   Plugins  »

Signals

Overview

The maintainability of a project largely depends on how loosely its components are coupled. The mechanism of signals allows different components to:

  1. declare possible events,
  2. subscribe to possible events, and
  3. notify all subscribers of actual events.

The signals dispatcher will take care of that. Thus, there is no need to (monkey-)patch external code to inject extra logic, you can just split that logic into pieces and tell the dispatcher to call them at certain points determined by the external code. This helps keep the components truly separate.

Tool makes use of PyDispatcher to send and receive signals. That’s an excellent “multi-producer-multi-consumer signal dispatching mechanism”.

Tool adds two things for convenience’s sake:

Both are optional. PyDispatcher will accept any objects (including strings) as signals. However, it is considered good practice to define them is a uniform way like this:

from tool.signals import Signal

post_save = Signal()

Note

on signal naming: think of the phrase “function is called on (signal name)”. Decent examples: “post_save”, “app_ready”. In most cases it’s a short description of an event that has just happened.

Then you import the signal and declare a reciever (some function):

from xyz import post_save

def log_saving_event(**kwargs):
    print '%(sender)s has been saved' % kwargs

Now you’ll want to connect the receiver to the signal.

If the signal is a Signal instance, the receiver can be connected using the signal method:

post_save.connect(log_saving_event)

In most cases the called_on() decorator would be more suitable:

@called_on(post_save)
def log_saving_event(**kwargs):
    print '%(sender)s has been saved' % kwargs

Finally, you can use the generic PyDispatcher function connect() to achieve the same result:

connect(log_saving_event, post_save)

Note

No matter how exactly you define, connect and send signals, they do not depend on Tool. Any Tool-specific signals will work with non-tool receivers; any non-Tool signals can be subscribed to with Tool decorators. The only requirement is the PyDispatcher library.

Note

The Signal class is optional but really useful for logging.

API reference

tool.signals.called_on(signal=_Any, sender=_Any, weak=True)

Decorator, connects given function to given signal. Semantic sugar for PyDispatcher’s connect.

Usage:

from tool.signals import called_on

@called_on(pre_save, SomeModel)
def log_saving_event(**kwargs):
    print '{sender} has been saved'.format(**kwargs)

This is semantically equivalent to:

from tool.signals import connect

def log_saving_event(**kwargs):
    print '{sender} has been saved'.format(**kwargs)
connect(log_saving_event, pre_save, SomeModel)
tool.signals.connect(receiver, signal=_Any, sender=_Any, weak=True)

Connect receiver to sender for signal

receiver – a callable Python object which is to receive

messages/signals/events. Receivers must be hashable objects.

if weak is True, then receiver must be weak-referencable (more precisely saferef.safeRef() must be able to create a reference to the receiver).

Receivers are fairly flexible in their specification, as the machinery in the robustApply module takes care of most of the details regarding figuring out appropriate subsets of the sent arguments to apply to a given receiver.

Note:
if receiver is itself a weak reference (a callable), it will be de-referenced by the system’s machinery, so generally weak references are not suitable as receivers, though some use might be found for the facility whereby a higher-level library passes in pre-weakrefed receiver references.

signal – the signal to which the receiver should respond

if Any, receiver will receive any signal from the indicated sender (which might also be Any, but is not necessarily Any).

Otherwise must be a hashable Python object other than None (DispatcherError raised on None).

sender – the sender to which the receiver should respond

if Any, receiver will receive the indicated signals from any sender.

if Anonymous, receiver will only receive indicated signals from send/sendExact which do not specify a sender, or specify Anonymous explicitly as the sender.

Otherwise can be any python object.

weak – whether to use weak references to the receiver
By default, the module will attempt to use weak references to the receiver objects. If this parameter is false, then strong references will be used.

returns None, may raise DispatcherTypeError

tool.signals.called_on(signal=_Any, sender=_Any, weak=True)

Decorator, connects given function to given signal. Semantic sugar for PyDispatcher’s connect.

Usage:

from tool.signals import called_on

@called_on(pre_save, SomeModel)
def log_saving_event(**kwargs):
    print '{sender} has been saved'.format(**kwargs)

This is semantically equivalent to:

from tool.signals import connect

def log_saving_event(**kwargs):
    print '{sender} has been saved'.format(**kwargs)
connect(log_saving_event, pre_save, SomeModel)
tool.signals.disconnect(receiver, signal=_Any, sender=_Any, weak=True)

Disconnect receiver from sender for signal

receiver – the registered receiver to disconnect signal – the registered signal to disconnect sender – the registered sender to disconnect weak – the weakref state to disconnect

disconnect reverses the process of connect, the semantics for the individual elements are logically equivalent to a tuple of (receiver, signal, sender, weak) used as a key to be deleted from the internal routing tables. (The actual process is slightly more complex but the semantics are basically the same).

Note:
Using disconnect is not required to cleanup routing when an object is deleted, the framework will remove routes for deleted objects automatically. It’s only necessary to disconnect if you want to stop routing to a live object.
returns None, may raise DispatcherTypeError or
DispatcherKeyError
tool.signals.send(signal=_Any, sender=_Anonymous, *arguments, **named)

Send signal from sender to all connected receivers.

signal – (hashable) signal value, see connect for details

sender – the sender of the signal

if Any, only receivers registered for Any will receive the message.

if Anonymous, only receivers registered to receive messages from Anonymous or Any will receive the message

Otherwise can be any python object (normally one registered with a connect if you actually want something to occur).

arguments – positional arguments which will be passed to
all receivers. Note that this may raise TypeErrors if the receivers do not allow the particular arguments. Note also that arguments are applied before named arguments, so they should be used with care.
named – named arguments which will be filtered according
to the parameters of the receivers to only provide those acceptable to the receiver.

Return a list of tuple pairs [(receiver, response), ... ]

if any receiver raises an error, the error propagates back through send, terminating the dispatch loop, so it is quite possible to not have all receivers called if a raises an error.

class tool.signals.Signal(name=None)

Base class for signals. An instance of this class serves as a unique event representation to which external functions can be subscribed. Usage:

something_saved = Signal()

The only problem with the above example is that the Signal instance itself does not know the name of the variable it has been assigned to. This problem can be safely ignored until you want to read the debug-level logs where the signals report when a subscriber is (dis)connected or the signal is emitted. To make the logs much more readable, the following optional notation is recommended:

something_saved = Signal('something_saved')

This is not DRY but more informative and does not involve an auxiliary registry which would complicate things too much.

connect(receiver, sender=_Any, weak=True)

Connect receiver to sender for this signal. Wrapper for connect(). Usage:

from xyz import post_save

def log_saving_event(**kwargs):
    ...
# call log_saving_event each time a Note is saved
post_save.connect(log_saving_event, Note)

Publishes a debug log message via Python’s logging module.

disconnect(receiver, sender=_Any, weak=True)

Disconnects given receiver from sender for this signal. Wrapper for disconnect(). Usage:

from xyz import post_save

# do not trigger log_saving_event when a Note is saved
post_save.disconnect(log_saving_event, sender=Note)

Publishes a debug log message via Python’s logging module.

send(sender=_Anonymous, *arguments, **named)

Send this signal from sender to all connected receivers. Wrapper for send(). Usage:

from xyz import post_save

class Note(Model):
    ...
    def save(self):
        ...
        post_save.send(Note)

Publishes a debug log message via Python’s logging module.

«  Debugging utilities   ::   Contents   ::   Plugins  »