# 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:

- provides a centralized service for delivering messages to registered objects (in the local process). It allows you to register any number of functions (callable objects) which can receive signals from senders.

- registration can be for all senders, particular sending objects, or "anonymous" messages (messages where the sender is None)
- registration can be for any signal, or particular signals
- a single signal will be delivered to all appropriate registered receivers, so that multiple registrations do not interfere with each other

- there is no requirement for the sender or receiver to be dispatcher-aware. Any Python object save the None object can act as a sender, and any callable object can act as a receiver. There is no need to inherit from a particular class or provide a particular interface on the object.
- the system uses weak references to receivers wherever possible

- object lifetimes are not affected by PyDispatcher registrations (that is, when your object goes away, the registrations related to the object also go away).
- references to common transient objects (in particular instance methods) are stored as compound weak references.
- weak references can be disabled on a registration-by-registration basis

- allows rich signal types, signals are simply hashable objects used to store and retrieve sub-tables, they are otherwise opaque to the dispatcher mechanism
- allows sending more information when sending than any particular receiver can handle, dispatcher automatically culls those arguments which are not appropriate for the particular receiver. This allows registering very simple functions dealing with general messages, while still allowing natural passing of arguments to higher level functions.

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 [git project on GitHub](https://github.com/mcfletch/pydispatcher). To help develop, check out the project like so:

    git clone https://github.com/mcfletch/pydispatcher.git

You can send a pull request via GitHub

## 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

- Louie

- Reworked pydispatcher providing plugin infrastructure including Twisted and PyQt specific support

- django.dispatch (Signals)

- Rewritten with a more limited interface, but higher performance; requires that signals be objects of a given type, and eliminates Any registrations. Registrations are on the signal objects.

## Release Notes

- Version 2.0.6
  - Packaging fixes, updates to documentation and metadata.
- Version 2.0.5

- Python 3.x support via shared code-base (not 2to3). Python 2.x is still the primary development target

- Version 2.0.3

- Support for Python 3.2 (via 2to3) added, Python 2.x is still the primary development target

- Version 2.0.2

- Further packaging fixes.

- Version 2.0.1 (this version and all previous versions are available from the old SourceForge project)

- Packaging fixes to allow for easy_install based installation

- Version 2.0.0

- Renames the top-level package to "pydispatch" to avoid conflicts with common conflicting "dispatch" module.

- Version 1.0.3

- Add "robust" module with single function sendRobust, which catches errors during callbacks and returns the error instances instead of propagating the error
- Patch bug in SafeRef deletion where traceback module has already been deleted by interpreter shutdown
- Patch bug in _removeReceiver where sendersBack has already been deleted by interpreter shutdown
- Make SafeRef pre-cache method name to allow for repr after cleanup of the method

- Version 1.0.2

- Fixes another memory leak, again wrt the back-reference table

- Version 1.0.1

- Fixes 2 memory leaks, one regarding the back-reference table for receivers, the other being a failure to register all receivers beyond the first for deletion

- Version 1.0.0

- Initial SourceForge release with restructured codebase