PyDispatcher

PyDispatcher provides the Python programmer with a multiple-producer-multiple-consumer signal-registration and routing infrastructure for use in multiple contexts.  The mechanism of PyDispatcher started life as a highly rated recipe in the Python Cookbook.  The project aims to include various enhancements to the recipe developed during use in various applications.  It is primarily maintained by Mike Fletcher.  A derivative of the project provides the Django web framework's "signal" system.

To be more concrete about what PyDispatcher does for you:

The dispatcher mechanism is particularly useful when constructing Model-View-Controller style applications where it is not desirable to have the Model objects aware of the event model.

Acquisition and Installation

PyDispatcher is available as a standard Python distutils installation package from the Python Package Index (PyPI).  To install, run:

pip install PyDispatcher

PyDispatcher does not include any binary packages, so there should be no issues in installation.  PyDispatcher is maintained on the LaunchPad project in bzr.  To help develop, check out the project like so:

bzr branch lp:~mcfletch/pydispatcher/working

You can either send a pull request via LaunchPad or email a patch-set via:

bzr send --mail-to=mcfletch@vrplumber.com

Documentation

You can find usage samples in the examples directory of the distribution.  The dispatcher module's reference documentation is currently the major source of information regarding usage.

PyDispatcher welcomes contributions, suggestions, and feedback from users in the pydispatcher-dev mailing list.

Usage

To set up a function to receive signals:

from pydispatch import dispatcher
SIGNAL = 'my-first-signal'

def handle_event( sender ):
"""Simple event handler"""
print 'Signal was sent by', sender
dispatcher.connect( handle_event, signal=SIGNAL, sender=dispatcher.Any )

The use of the Any object allows the handler to listen for messages from any Sender or to listen to Any message being sent.  To send messages:

first_sender = object()
second_sender = {}
def main( ):
dispatcher.send( signal=SIGNAL, sender=first_sender )
dispatcher.send( signal=SIGNAL, sender=second_sender )

Which causes the following to be printed:

Signal was sent by <object object at 0x196a090>
Signal was sent by {}

Handler Functions

Handler functions in PyDispatcher are relatively loose in their definition.  A handler can simply declare the parameters it would like to receive and receive only those parameters when the signal is sent.  The sender can include extra parameters for those handlers which require them without worrying about whether a more generic handler can accept them:

def handle_specific_event( sender, moo ):
"""Handle a simple event, requiring a "moo" parameter"""
print 'Specialized event for %(sender)s moo=%(moo)r'%locals()
dispatcher.connect( handle_specific_event, signal=SIGNAL2, sender=dispatcher.Any )

This connection requires that all senders of the particular signal send a "moo" parameter, but a handler that listens for all events and does not provide a "moo" parameter would silently ignore the sender having passed a "moo".

2 parameters are always available to handler functions if they would like to use them:

Parameter
Value
sender
Object from/for which the event was sent, can be dispatcher.Anonymous for anonymous signals
signal
Signal object used when sending

Positional arguments and named arguments are passed through, but if positional arguments are used, they will fill in the parameters of the receiver in order and cause conflicts if named parameters are specified which match their names.  Generally it is advisable to use named arguments when defining sending messages.

Real World Examples

OpenGLContext uses PyDispatcher to provide a link between PyVRML97 (model level) and OpenGLContext (the controller and view levels).  Changes to fields in the model send events, as do changes to object lists.  A cache observes these changes and allows the view/model level to register dependencies on particular fields of particular nodes.  The result is that rendering code can cache data extensively and have the caches consistently invalidated when their data-dependencies are changed.

Related Software

Release Notes