Signals¶
Overview¶
The maintainability of a project largely depends on how loosely its components are coupled. The mechanism of signals allows different components to:
- declare possible events,
- subscribe to possible events, and
- 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:
- decorator called_on();
- class Signal.
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.