Commits

Anonymous committed c0ce999

Major refactoring of django.dispatch with an eye towards speed. The net result is that signals are up to 90% faster.

Though some attempts and backwards-compatibility were made, speed trumped compatibility. Thus, as usual, check BackwardsIncompatibleChanges for the complete list of backwards-incompatible changes.

Thanks to Jeremy Dunck and Keith Busell for the bulk of the work; some ideas from Brian Herring's previous work (refs #4561) were incorporated.

Documentation is, sigh, still forthcoming.

Fixes #6814 and #3951 (with the new dispatch_uid argument to connect).

Comments (0)

Files changed (33)

django/contrib/auth/management/__init__.py

 Creates permissions for all installed apps that need permissions.
 """
 
-from django.dispatch import dispatcher
 from django.db.models import get_models, signals
 from django.contrib.auth import models as auth_app
 
         perms.append((_get_permission_codename(action, opts), u'Can %s %s' % (action, opts.verbose_name_raw)))
     return perms + list(opts.permissions)
 
-def create_permissions(app, created_models, verbosity):
+def create_permissions(app, created_models, verbosity, **kwargs):
     from django.contrib.contenttypes.models import ContentType
     from django.contrib.auth.models import Permission
     app_models = get_models(app)
                 call_command("createsuperuser", interactive=True)
             break
 
-if 'create_permissions' not in [i.__name__ for i in dispatcher.getAllReceivers(signal=signals.post_syncdb)]:
-    dispatcher.connect(create_permissions, signal=signals.post_syncdb)
-if 'create_superuser' not in [i.__name__ for i in dispatcher.getAllReceivers(signal=signals.post_syncdb, sender=auth_app)]:
-    dispatcher.connect(create_superuser, sender=auth_app, signal=signals.post_syncdb)
+signals.post_syncdb.connect(create_permissions,
+    dispatch_uid = "django.contrib.auth.management.create_permissions")
+signals.post_syncdb.connect(create_superuser,
+    sender=auth_app, dispatch_uid = "django.contrib.auth.management.create_superuser")

django/contrib/contenttypes/generic.py

 from django.db.models import signals
 from django.db.models.fields.related import RelatedField, Field, ManyToManyRel
 from django.db.models.loading import get_model
-from django.dispatch import dispatcher
 from django.utils.functional import curry
 
 class GenericForeignKey(object):
         self.cache_attr = "_%s_cache" % name
 
         # For some reason I don't totally understand, using weakrefs here doesn't work.
-        dispatcher.connect(self.instance_pre_init, signal=signals.pre_init, sender=cls, weak=False)
+        signals.pre_init.connect(self.instance_pre_init, sender=cls, weak=False)
 
         # Connect myself as the descriptor for this field
         setattr(cls, name, self)
 
-    def instance_pre_init(self, signal, sender, args, kwargs):
+    def instance_pre_init(self, signal, sender, args, kwargs, **_kwargs):
         """
         Handles initializing an object with the generic FK instaed of
         content-type/object-id fields.

django/contrib/contenttypes/management.py

 from django.contrib.contenttypes.models import ContentType
-from django.dispatch import dispatcher
 from django.db.models import get_apps, get_models, signals
 from django.utils.encoding import smart_unicode
 
-def update_contenttypes(app, created_models, verbosity=2):
+def update_contenttypes(app, created_models, verbosity=2, **kwargs):
     """
     Creates content types for models in the given app, removing any model
     entries that no longer have a matching model class.
     for app in get_apps():
         update_contenttypes(app, None, verbosity)
 
-dispatcher.connect(update_contenttypes, signal=signals.post_syncdb)
+signals.post_syncdb.connect(update_contenttypes)
 
 if __name__ == "__main__":
     update_all_contenttypes()

django/contrib/sites/management.py

 Creates the default Site object.
 """
 
-from django.dispatch import dispatcher
 from django.db.models import signals
 from django.contrib.sites.models import Site
 from django.contrib.sites import models as site_app
 
-def create_default_site(app, created_models, verbosity):
+def create_default_site(app, created_models, verbosity, **kwargs):
     if Site in created_models:
         if verbosity >= 2:
             print "Creating example.com Site object"
         s.save()
     Site.objects.clear_cache()
 
-dispatcher.connect(create_default_site, sender=site_app, signal=signals.post_syncdb)
+signals.post_syncdb.connect(create_default_site, sender=site_app)

django/core/handlers/base.py

 
 from django import http
 from django.core import signals
-from django.dispatch import dispatcher
 from django.utils.encoding import force_unicode
 
 class BaseHandler(object):
         except: # Handle everything else, including SuspiciousOperation, etc.
             # Get the exception info now, in case another exception is thrown later.
             exc_info = sys.exc_info()
-            receivers = dispatcher.send(signal=signals.got_request_exception, request=request)
+            receivers = signals.got_request_exception.send(sender=self.__class__, request=request)
             return self.handle_uncaught_exception(request, resolver, exc_info)
 
     def handle_uncaught_exception(self, request, resolver, exc_info):

django/core/handlers/modpython.py

 from django.core import signals
 from django.core.handlers.base import BaseHandler
 from django.core.urlresolvers import set_script_prefix
-from django.dispatch import dispatcher
 from django.utils import datastructures
 from django.utils.encoding import force_unicode, smart_str
 
             self.load_middleware()
 
         set_script_prefix(req.get_options().get('django.root', ''))
-        dispatcher.send(signal=signals.request_started)
+        signals.request_started.send(sender=self.__class__)
         try:
             try:
                 request = self.request_class(req)
                     response = middleware_method(request, response)
                 response = self.apply_response_fixes(request, response)
         finally:
-            dispatcher.send(signal=signals.request_finished)
+            signals.request_finished.send(sender=self.__class__)
 
         # Convert our custom HttpResponse object back into the mod_python req.
         req.content_type = response['Content-Type']

django/core/handlers/wsgi.py

 from django.core import signals
 from django.core.handlers import base
 from django.core.urlresolvers import set_script_prefix
-from django.dispatch import dispatcher
 from django.utils import datastructures
 from django.utils.encoding import force_unicode
 
             self.initLock.release()
 
         set_script_prefix(base.get_script_name(environ))
-        dispatcher.send(signal=signals.request_started)
+        signals.request_started.send(sender=self.__class__)
         try:
             try:
                 request = self.request_class(environ)
                     response = middleware_method(request, response)
                 response = self.apply_response_fixes(request, response)
         finally:
-            dispatcher.send(signal=signals.request_finished)
+            signals.request_finished.send(sender=self.__class__)
 
         try:
             status_text = STATUS_CODE_TEXT[response.status_code]

django/core/management/commands/flush.py

     def handle_noargs(self, **options):
         from django.conf import settings
         from django.db import connection, transaction, models
-        from django.dispatch import dispatcher
         from django.core.management.sql import sql_flush, emit_post_sync_signal
 
         verbosity = int(options.get('verbosity', 1))

django/core/management/sql.py

         app_name = app.__name__.split('.')[-2]
         if verbosity >= 2:
             print "Running post-sync handlers for application", app_name
-        dispatcher.send(signal=models.signals.post_syncdb, sender=app,
-            app=app, created_models=created_models,
-            verbosity=verbosity, interactive=interactive)
+        models.signals.post_syncdb.send(sender=app, app=app,
+            created_models=created_models, verbosity=verbosity,
+            interactive=interactive)

django/core/signals.py

-request_started = object()
-request_finished = object()
-got_request_exception = object()
+from django.dispatch import Signal
+
+request_started = Signal()
+request_finished = Signal()
+got_request_exception = Signal(providing_args=["request"])

django/db/__init__.py

 from django.conf import settings
 from django.core import signals
 from django.core.exceptions import ImproperlyConfigured
-from django.dispatch import dispatcher
 from django.utils.functional import curry
 
 __all__ = ('backend', 'connection', 'DatabaseError', 'IntegrityError')
 
 # Register an event that closes the database connection
 # when a Django request is finished.
-dispatcher.connect(connection.close, signal=signals.request_finished)
+def close_connection(**kwargs):
+    connection.close()
+signals.request_finished.connect(close_connection)
 
 # Register an event that resets connection.queries
 # when a Django request is started.
-def reset_queries():
+def reset_queries(**kwargs):
     connection.queries = []
-dispatcher.connect(reset_queries, signal=signals.request_started)
+signals.request_started.connect(reset_queries)
 
 # Register an event that rolls back the connection
 # when a Django request has an exception.
-def _rollback_on_exception():
+def _rollback_on_exception(**kwargs):
     from django.db import transaction
     transaction.rollback_unless_managed()
-dispatcher.connect(_rollback_on_exception, signal=signals.got_request_exception)
+signals.got_request_exception.connect(_rollback_on_exception)

django/db/models/base.py

 from django.db import connection, transaction
 from django.db.models import signals
 from django.db.models.loading import register_models, get_model
-from django.dispatch import dispatcher
 from django.utils.functional import curry
 from django.utils.encoding import smart_str, force_unicode, smart_unicode
 from django.core.files.move import file_move_safe
         if hasattr(cls, 'get_absolute_url'):
             cls.get_absolute_url = curry(get_absolute_url, opts, cls.get_absolute_url)
 
-        dispatcher.send(signal=signals.class_prepared, sender=cls)
+        signals.class_prepared.send(sender=cls)
 
 
 class Model(object):
     __metaclass__ = ModelBase
 
     def __init__(self, *args, **kwargs):
-        dispatcher.send(signal=signals.pre_init, sender=self.__class__, args=args, kwargs=kwargs)
+        signals.pre_init.send(sender=self.__class__, args=args, kwargs=kwargs)
 
         # There is a rather weird disparity here; if kwargs, it's set, then args
         # overrides it. It should be one or the other; don't duplicate the work
                     pass
             if kwargs:
                 raise TypeError, "'%s' is an invalid keyword argument for this function" % kwargs.keys()[0]
-        dispatcher.send(signal=signals.post_init, sender=self.__class__, instance=self)
+        signals.post_init.send(sender=self.__class__, instance=self)
 
     def __repr__(self):
         return smart_str(u'<%s: %s>' % (self.__class__.__name__, unicode(self)))
             cls = self.__class__
             meta = self._meta
             signal = True
-            dispatcher.send(signal=signals.pre_save, sender=self.__class__,
-                    instance=self, raw=raw)
+            signals.pre_save.send(sender=self.__class__, instance=self, raw=raw)
         else:
             meta = cls._meta
             signal = False
         transaction.commit_unless_managed()
 
         if signal:
-            dispatcher.send(signal=signals.post_save, sender=self.__class__,
-                    instance=self, created=(not record_exists), raw=raw)
+            signals.post_save.send(sender=self.__class__, instance=self,
+                created=(not record_exists), raw=raw)
 
     save_base.alters_data = True
 

django/db/models/fields/__init__.py

 from django.db import connection, get_creation_module
 from django.db.models import signals
 from django.db.models.query_utils import QueryWrapper
-from django.dispatch import dispatcher
 from django.conf import settings
 from django.core import validators
 from django import oldforms
         setattr(cls, 'get_%s_url' % self.name, curry(cls._get_FIELD_url, field=self))
         setattr(cls, 'get_%s_size' % self.name, curry(cls._get_FIELD_size, field=self))
         setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_field, save=True: instance._save_FIELD_file(self, filename, raw_field, save))
-        dispatcher.connect(self.delete_file, signal=signals.post_delete, sender=cls)
+        signals.post_delete.connect(self.delete_file, sender=cls)
 
-    def delete_file(self, instance):
+    def delete_file(self, instance, **kwargs):
         if getattr(instance, self.attname):
             file_name = getattr(instance, 'get_%s_filename' % self.name)()
             # If the file exists and no other object of this type references it,

django/db/models/fields/related.py

 from django.core import validators
 from django import oldforms
 from django import forms
-from django.dispatch import dispatcher
 
 try:
     set
         value = (cls, field, operation)
         pending_lookups.setdefault(key, []).append(value)
 
-def do_pending_lookups(sender):
+def do_pending_lookups(sender, **kwargs):
     """
     Handle any pending relations to the sending model. Sent from class_prepared.
     """
     for cls, field, operation in pending_lookups.pop(key, []):
         operation(field, sender, cls)
 
-dispatcher.connect(do_pending_lookups, signal=signals.class_prepared)
+signals.class_prepared.connect(do_pending_lookups)
 
 def manipulator_valid_rel_key(f, self, field_data, all_data):
     "Validates that the value is a valid foreign key"

django/db/models/manager.py

 import copy
 
 from django.db.models.query import QuerySet, EmptyQuerySet, insert_query
-from django.dispatch import dispatcher
 from django.db.models import signals
 from django.db.models.fields import FieldDoesNotExist
 
-def ensure_default_manager(sender):
+def ensure_default_manager(sender, **kwargs):
     cls = sender
     if not getattr(cls, '_default_manager', None) and not cls._meta.abstract:
         # Create the default manager, if needed.
             pass
         cls.add_to_class('objects', Manager())
 
-dispatcher.connect(ensure_default_manager, signal=signals.class_prepared)
+signals.class_prepared.connect(ensure_default_manager)
 
 class Manager(object):
     # Tracks each time a Manager instance is created. Used to retain order.

django/db/models/manipulators.py

 from django import oldforms
 from django.core import validators
 from django.db.models.fields import FileField, AutoField
-from django.dispatch import dispatcher
 from django.db.models import signals
 from django.utils.functional import curry
 from django.utils.datastructures import DotExpandedDict
 from django.utils.translation import ugettext as _
 from django.utils import datetime_safe
 
-def add_manipulators(sender):
+def add_manipulators(sender, **kwargs):
     cls = sender
     cls.add_to_class('AddManipulator', AutomaticAddManipulator)
     cls.add_to_class('ChangeManipulator', AutomaticChangeManipulator)
 
-dispatcher.connect(add_manipulators, signal=signals.class_prepared)
+signals.class_prepared.connect(add_manipulators)
 
 class ManipulatorDescriptor(object):
     # This class provides the functionality that makes the default model

django/db/models/query.py

 from django.db.models.fields import DateField
 from django.db.models.query_utils import Q, select_related_descend
 from django.db.models import signals, sql
-from django.dispatch import dispatcher
 from django.utils.datastructures import SortedDict
 
 
 
         # Pre-notify all instances to be deleted.
         for pk_val, instance in items:
-            dispatcher.send(signal=signals.pre_delete, sender=cls,
-                    instance=instance)
+            signals.pre_delete.send(sender=cls, instance=instance)
 
         pk_list = [pk for pk,instance in items]
         del_query = sql.DeleteQuery(cls, connection)
                 if field.rel and field.null and field.rel.to in seen_objs:
                     setattr(instance, field.attname, None)
 
-            dispatcher.send(signal=signals.post_delete, sender=cls,
-                    instance=instance)
+            signals.post_delete.send(sender=cls, instance=instance)
             setattr(instance, cls._meta.pk.attname, None)
 
     transaction.commit_unless_managed()

django/db/models/signals.py

-class_prepared = object()
+from django.dispatch import Signal
 
-pre_init= object()
-post_init = object()
+class_prepared = Signal(providing_args=["class"])
 
-pre_save = object()
-post_save = object()
+pre_init = Signal(providing_args=["instance", "args", "kwargs"])
+post_init = Signal(providing_args=["instance"])
 
-pre_delete = object()
-post_delete = object()
+pre_save = Signal(providing_args=["instance", "raw"])
+post_save = Signal(providing_args=["instance", "raw", "created"])
 
-post_syncdb = object()
+pre_delete = Signal(providing_args=["instance"])
+post_delete = Signal(providing_args=["instance"])
+
+post_syncdb = Signal(providing_args=["class", "app", "created_models", "verbosity", "interactive"])

django/db/models/sql/query.py

 
 from django.utils.tree import Node
 from django.utils.datastructures import SortedDict
-from django.dispatch import dispatcher
 from django.db import connection
 from django.db.models import signals
 from django.db.models.fields import FieldDoesNotExist
             sentinel):
         yield [r[:-trim] for r in rows]
 
-def setup_join_cache(sender):
+def setup_join_cache(sender, **kwargs):
     """
     The information needed to join between model fields is something that is
     invariant over the life of the model, so we cache it in the model's Options
     """
     sender._meta._join_cache = {}
 
-dispatcher.connect(setup_join_cache, signal=signals.class_prepared)
+signals.class_prepared.connect(setup_join_cache)
 

django/dispatch/__init__.py

 """Multi-consumer multi-producer dispatching mechanism
+
+Originally based on pydispatch (BSD) http://pypi.python.org/pypi/PyDispatcher/2.0.1
+See license.txt for original license.
+
+Heavily modified for Django's purposes.
 """
-__version__ = "1.0.0"
-__author__ = "Patrick K. O'Brien"
-__license__ = "BSD-style, see license.txt for details"
 
+from django.dispatch.dispatcher import Signal

django/dispatch/dispatcher.py

-"""Multiple-producer-multiple-consumer signal-dispatching
+import weakref
+import warnings
+try:
+    set
+except NameError:
+    from sets import Set as set # Python 2.3 fallback
 
-dispatcher is the core of the PyDispatcher system,
-providing the primary API and the core logic for the
-system.
-
-Module attributes of note:
-
-    Any -- Singleton used to signal either "Any Sender" or
-        "Any Signal".  See documentation of the _Any class.
-    Anonymous -- Singleton used to signal "Anonymous Sender"
-        See documentation of the _Anonymous class.
-
-Internal attributes:
-    WEAKREF_TYPES -- tuple of types/classes which represent
-        weak references to receivers, and thus must be de-
-        referenced on retrieval to retrieve the callable
-        object
-    connections -- { senderkey (id) : { signal : [receivers...]}}
-    senders -- { senderkey (id) : weakref(sender) }
-        used for cleaning up sender references on sender
-        deletion
-    sendersBack -- { receiverkey (id) : [senderkey (id)...] }
-        used for cleaning up receiver references on receiver
-        deletion, (considerably speeds up the cleanup process
-        vs. the original code.)
-"""
-import weakref
-from django.dispatch import saferef, robustapply, errors
-
-__author__ = "Patrick K. O'Brien <pobrien@orbtech.com>"
-__cvsid__ = "$Id: dispatcher.py,v 1.9 2005/09/17 04:55:57 mcfletch Exp $"
-__version__ = "$Revision: 1.9 $"[11:-2]
-
-
-class _Parameter:
-    """Used to represent default parameter values."""
-    def __repr__(self):
-        return self.__class__.__name__
-
-class _Any(_Parameter):
-    """Singleton used to signal either "Any Sender" or "Any Signal"
-
-    The Any object can be used with connect, disconnect,
-    send, or sendExact to signal that the parameter given
-    Any should react to all senders/signals, not just
-    a particular sender/signal.
-    """
-Any = _Any()
-
-class _Anonymous(_Parameter):
-    """Singleton used to signal "Anonymous Sender"
-
-    The Anonymous object is used to signal that the sender
-    of a message is not specified (as distinct from being
-    "any sender").  Registering callbacks for Anonymous
-    will only receive messages sent without senders.  Sending
-    with anonymous will only send messages to those receivers
-    registered for Any or Anonymous.
-
-    Note:
-        The default sender for connect is Any, while the
-        default sender for send is Anonymous.  This has
-        the effect that if you do not specify any senders
-        in either function then all messages are routed
-        as though there was a single sender (Anonymous)
-        being used everywhere.
-    """
-Anonymous = _Anonymous()
+from django.dispatch import saferef
 
 WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref)
 
-connections = {}
-senders = {}
-sendersBack = {}
+def _make_id(target):
+    if hasattr(target, 'im_func'):
+        return (id(target.im_self), id(target.im_func))
+    return id(target)
 
+class Signal(object):
+    """Base class for all signals
+    
+    Internal attributes:
+        receivers -- { receriverkey (id) : weakref(receiver) }
+    """
+    
+    def __init__(self, providing_args=None):
+        """providing_args -- A list of the arguments this signal can pass along in
+                       a send() call.
+        """
+        self.receivers = []
+        if providing_args is None:
+            providing_args = []
+        self.providing_args = set(providing_args)
 
-def connect(receiver, signal=Any, sender=Any, weak=True):
-    """Connect receiver to sender for signal
+    def connect(self, receiver, sender=None, weak=True, dispatch_uid=None):
+        """Connect receiver to sender for signal
+    
+        receiver -- a function or an instance method which is to
+            receive signals.  Receivers must be
+            hashable objects.
 
-    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 must be able to accept keyword arguments.
 
-        if weak is True, then receiver must be weak-referencable
-        (more precisely saferef.safeRef() must be able to create
-        a reference to the receiver).
+            If receivers have a dispatch_uid attribute, the receiver will
+              not be added if another receiver already exists with that
+              dispatch_uid.
+
+        sender -- the sender to which the receiver should respond
+            Must either be of type Signal, or None to receive events
+            from any sender.
+
+        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.
+        
+        dispatch_uid -- an identifier used to uniquely identify a particular
+            instance of a receiver. This will usually be a string, though it
+            may be anything hashable.
+
+        returns None
+        """
+        from django.conf import settings
+        
+        if settings.DEBUG:
+            import inspect
+            assert inspect.getargspec(receiver)[2] is not None, \
+                "Signal receivers must accept keyword arguments (**kwargs)."
+        
+        if dispatch_uid:
+            lookup_key = (dispatch_uid, _make_id(sender))
+        else:
+            lookup_key = (_make_id(receiver), _make_id(sender))
+
+        if weak:
+            receiver = saferef.safeRef(receiver, onDelete=self._remove_receiver)
+
+        for r_key, _ in self.receivers:
+            if r_key == lookup_key:
+                break
+        else:
+            self.receivers.append((lookup_key, receiver))
+
+    def disconnect(self, receiver=None, sender=None, weak=True, dispatch_uid=None):
+        """Disconnect receiver from sender for signal
     
-        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.
+        receiver -- the registered receiver to disconnect. May be none if
+            dispatch_uid is specified.
+        sender -- the registered sender to disconnect
+        weak -- the weakref state to disconnect
+        dispatch_uid -- the unique identifier of the receiver to disconnect
+    
+        disconnect reverses the process of connect.
 
-        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.
+        If weak references are used, disconnect need not be called.
+          The receiver will be remove from dispatch automatically.
 
-    signal -- the signal to which the receiver should respond
+        returns None
+        """
+
+        if dispatch_uid:
+            lookup_key = (dispatch_uid, _make_id(sender))
+        else:
+            lookup_key = (_make_id(receiver), _make_id(sender))
+
+        for idx, (r_key, _) in enumerate(self.receivers):
+            if r_key == lookup_key:
+                del self.receivers[idx]
+
+    def send(self, sender, **named):
+        """Send signal from sender to all connected receivers.
+
+        sender -- the sender of the signal
+            Either a specific object or None.
     
-        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.
+        named -- named arguments which will be passed to receivers.
 
-        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 a list of tuple pairs [(receiver, response), ... ].
 
-    returns None, may raise DispatcherTypeError
+        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.
+        """
+
+        responses = []
+        if not self.receivers:
+            return responses
+
+        for receiver in self._live_receivers(_make_id(sender)):
+            response = receiver(signal=self, sender=sender, **named)
+            responses.append((receiver, response))
+        return responses
+
+    def send_robust(self, sender, **named):
+        """Send signal from sender to all connected receivers catching errors
+
+        sender -- the sender of the signal
+            Can be any python object (normally one registered with
+            a connect if you actually want something to occur).
+
+        named -- named arguments which will be passed to receivers.
+            These arguments must be a subset of the argument names
+            defined in providing_args.
+
+        Return a list of tuple pairs [(receiver, response), ... ],
+        may raise DispatcherKeyError
+
+        if any receiver raises an error (specifically any subclass of Exception),
+        the error instance is returned as the result for that receiver.
+        """
+
+        responses = []
+        if not self.receivers:
+            return responses
+
+        # Call each receiver with whatever arguments it can accept.
+        # Return a list of tuple pairs [(receiver, response), ... ].
+        for receiver in self._live_receivers(_make_id(sender)):
+            try:
+                response = receiver(signal=self, sender=sender, **named)
+            except Exception, err:
+                responses.append((receiver, err))
+            else:
+                responses.append((receiver, response))
+        return responses
+
+    def _live_receivers(self, senderkey):
+        """Filter sequence of receivers to get resolved, live receivers
+
+        This checks for weak references
+        and resolves them, then returning only live
+        receivers.
+        """
+        none_senderkey = _make_id(None)
+
+        for (receiverkey, r_senderkey), receiver in self.receivers:
+            if r_senderkey == none_senderkey or r_senderkey == senderkey:
+                if isinstance(receiver, WEAKREF_TYPES):
+                    # Dereference the weak reference.
+                    receiver = receiver()
+                    if receiver is not None:
+                        yield receiver
+                else:
+                    yield receiver
+
+    def _remove_receiver(self, receiver):
+        """Remove dead receivers from connections."""
+
+        to_remove = []
+        for key, connected_receiver in self.receivers:
+            if connected_receiver == receiver:
+                to_remove.append(key)
+        for key in to_remove:
+            for idx, (r_key, _) in enumerate(self.receivers):
+                if r_key == key:
+                    del self.receivers[idx]
+
+def connect(receiver, signal, sender=None, weak=True):
     """
-    if signal is None:
-        raise errors.DispatcherTypeError(
-            'Signal cannot be None (receiver=%r sender=%r)' % (receiver, sender)
-        )
-    if weak:
-        receiver = saferef.safeRef(receiver, onDelete=_removeReceiver)
-    senderkey = id(sender)
+    For backward compatibility only. See Signal.connect()
+    """
+    warnings.warn(
+        category   = DeprecationWarning,
+        message    = "dispatcher.connect() is deprecated; use Signal.connect() instead.",
+        stacklevel = 2
+    )
+    return signal.connect(receiver, sender, weak)
 
-    signals = connections.setdefault(senderkey, {})
+def disconnect(receiver, signal, sender=None, weak=True):
+    """
+    For backward compatibility only. See Signal.disconnect()
+    """
+    warnings.warn(
+        category   = DeprecationWarning,
+        message    = "dispatcher.disconnect() is deprecated; use Signal.disconnect() instead.",
+        stacklevel = 2
+    )
+    signal.disconnect(receiver, sender, weak)
 
-    # Keep track of senders for cleanup.
-    # Is Anonymous something we want to clean up?
-    if sender not in (None, Anonymous, Any):
-        def remove(object, senderkey=senderkey):
-            _removeSender(senderkey=senderkey)
-        # Skip objects that can not be weakly referenced, which means
-        # they won't be automatically cleaned up, but that's too bad.
-        try:
-            weakSender = weakref.ref(sender, remove)
-            senders[senderkey] = weakSender
-        except:
-            pass
-        
-    receiverID = id(receiver)
-    # get current set, remove any current references to
-    # this receiver in the set, including back-references
-    if signals.has_key(signal):
-        receivers = signals[signal]
-        _removeOldBackRefs(senderkey, signal, receiver, receivers)
-    else:
-        receivers = signals[signal] = []
-    try:
-        current = sendersBack.get(receiverID)
-        if current is None:
-            sendersBack[ receiverID ] = current = []
-        if senderkey not in current:
-            current.append(senderkey)
-    except:
-        pass
+def send(signal, sender=None, **named):
+    """
+    For backward compatibility only. See Signal.send()
+    """
+    warnings.warn(
+        category   = DeprecationWarning,
+        message    = "dispatcher.send() is deprecated; use Signal.send() instead.",
+        stacklevel = 2
+    )
+    return signal.send(sender=sender, **named)
 
-    receivers.append(receiver)
-
-
-
-def 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
+def sendExact(signal, sender, **named ):
     """
-    if signal is None:
-        raise errors.DispatcherTypeError(
-            'Signal cannot be None (receiver=%r sender=%r)' % (receiver, sender)
-        )
-    if weak: receiver = saferef.safeRef(receiver)
-    senderkey = id(sender)
-    try:
-        signals = connections[senderkey]
-        receivers = signals[signal]
-    except KeyError:
-        raise errors.DispatcherKeyError(
-            """No receivers found for signal %r from sender %r""" %(
-                signal,
-                sender
-            )
-        )
-    try:
-        # also removes from receivers
-        _removeOldBackRefs(senderkey, signal, receiver, receivers)
-    except ValueError:
-        raise errors.DispatcherKeyError(
-            """No connection to receiver %s for signal %s from sender %s""" %(
-                receiver,
-                signal,
-                sender
-            )
-        )
-    _cleanupConnections(senderkey, signal)
-
-def getReceivers(sender=Any, signal=Any):
-    """Get list of receivers from global tables
-
-    This utility function allows you to retrieve the
-    raw list of receivers from the connections table
-    for the given sender and signal pair.
-
-    Note:
-        there is no guarantee that this is the actual list
-        stored in the connections table, so the value
-        should be treated as a simple iterable/truth value
-        rather than, for instance a list to which you
-        might append new records.
-
-    Normally you would use liveReceivers(getReceivers(...))
-    to retrieve the actual receiver objects as an iterable
-    object.
+    This function is deprecated, as it now has the same meaning as send.
     """
-    existing = connections.get(id(sender))
-    if existing is not None:
-        return existing.get(signal, [])
-    return []
-
-def liveReceivers(receivers):
-    """Filter sequence of receivers to get resolved, live receivers
-
-    This is a generator which will iterate over
-    the passed sequence, checking for weak references
-    and resolving them, then returning all live
-    receivers.
-    """
-    for receiver in receivers:
-        if isinstance(receiver, WEAKREF_TYPES):
-            # Dereference the weak reference.
-            receiver = receiver()
-            if receiver is not None:
-                yield receiver
-        else:
-            yield receiver
-
-
-
-def getAllReceivers(sender=Any, signal=Any):
-    """Get list of all receivers from global tables
-
-    This gets all dereferenced receivers which should receive
-    the given signal from sender, each receiver should
-    be produced only once by the resulting generator
-    """
-    receivers = {}
-    # Get receivers that receive *this* signal from *this* sender.
-    # Add receivers that receive *any* signal from *this* sender.
-    # Add receivers that receive *this* signal from *any* sender.
-    # Add receivers that receive *any* signal from *any* sender.
-    l = []
-    i = id(sender)
-    if i in connections:
-        sender_receivers = connections[i]
-        if signal in sender_receivers:
-            l.extend(sender_receivers[signal])
-        if signal is not Any and Any in sender_receivers:
-            l.extend(sender_receivers[Any])
-
-    if sender is not Any:
-        i = id(Any)
-        if i in connections:
-            sender_receivers = connections[i]
-            if sender_receivers is not None:
-                if signal in sender_receivers:
-                    l.extend(sender_receivers[signal])
-                if signal is not Any and Any in sender_receivers:
-                    l.extend(sender_receivers[Any])
-
-    for receiver in l:
-        try:
-            if not receiver in receivers:
-                if isinstance(receiver, WEAKREF_TYPES):
-                    receiver = receiver()
-                    # this should only (rough guess) be possible if somehow, deref'ing
-                    # triggered a wipe.
-                    if receiver is None:
-                        continue
-                receivers[receiver] = 1
-                yield receiver
-        except TypeError:
-            # dead weakrefs raise TypeError on hash...
-            pass
-
-def 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.
-    """
-    # Call each receiver with whatever arguments it can accept.
-    # Return a list of tuple pairs [(receiver, response), ... ].
-    responses = []
-    for receiver in getAllReceivers(sender, signal):
-        response = robustapply.robustApply(
-            receiver,
-            signal=signal,
-            sender=sender,
-            *arguments,
-            **named
-        )
-        responses.append((receiver, response))
-    return responses
-
-
-def sendExact(signal=Any, sender=Anonymous, *arguments, **named ):
-    """Send signal only to those receivers registered for exact message
-
-    sendExact allows for avoiding Any/Anonymous registered
-    handlers, sending only to those receivers explicitly
-    registered for a particular signal on a particular
-    sender.
-    """
-    responses = []
-    for receiver in liveReceivers(getReceivers(sender, signal)):
-        response = robustapply.robustApply(
-            receiver,
-            signal=signal,
-            sender=sender,
-            *arguments,
-            **named
-        )
-        responses.append((receiver, response))
-    return responses
-    
-
-def _removeReceiver(receiver):
-    """Remove receiver from connections."""
-    if not sendersBack:
-        # During module cleanup the mapping will be replaced with None
-        return False
-    backKey = id(receiver)
-    for senderkey in sendersBack.get(backKey,()):
-        try:
-            signals = connections[senderkey].keys()
-        except KeyError,err:
-            pass
-        else:
-            for signal in signals:
-                try:
-                    receivers = connections[senderkey][signal]
-                except KeyError:
-                    pass
-                else:
-                    try:
-                        receivers.remove(receiver)
-                    except Exception, err:
-                        pass
-                _cleanupConnections(senderkey, signal)
-    try:
-        del sendersBack[ backKey ]
-    except KeyError:
-        pass
-            
-def _cleanupConnections(senderkey, signal):
-    """Delete any empty signals for senderkey. Delete senderkey if empty."""
-    try:
-        receivers = connections[senderkey][signal]
-    except:
-        pass
-    else:
-        if not receivers:
-            # No more connected receivers. Therefore, remove the signal.
-            try:
-                signals = connections[senderkey]
-            except KeyError:
-                pass
-            else:
-                del signals[signal]
-                if not signals:
-                    # No more signal connections. Therefore, remove the sender.
-                    _removeSender(senderkey)
-
-def _removeSender(senderkey):
-    """Remove senderkey from connections."""
-    _removeBackrefs(senderkey)
-
-    connections.pop(senderkey, None)
-    senders.pop(senderkey, None)
-
-
-def _removeBackrefs(senderkey):
-    """Remove all back-references to this senderkey"""
-    for receiver_list in connections.pop(senderkey, {}).values():
-        for receiver in receiver_list:
-            _killBackref(receiver, senderkey)
-
-
-def _removeOldBackRefs(senderkey, signal, receiver, receivers):
-    """Kill old sendersBack references from receiver
-
-    This guards against multiple registration of the same
-    receiver for a given signal and sender leaking memory
-    as old back reference records build up.
-
-    Also removes old receiver instance from receivers
-    """
-    try:
-        index = receivers.index(receiver)
-        # need to scan back references here and remove senderkey
-    except ValueError:
-        return False
-    else:
-        oldReceiver = receivers[index]
-        del receivers[index]
-        found = 0
-        signals = connections.get(signal)
-        if signals is not None:
-            for sig,recs in connections.get(signal,{}).iteritems():
-                if sig != signal:
-                    for rec in recs:
-                        if rec is oldReceiver:
-                            found = 1
-                            break
-        if not found:
-            _killBackref(oldReceiver, senderkey)
-            return True
-        return False
-        
-        
-def _killBackref(receiver, senderkey):
-    """Do the actual removal of back reference from receiver to senderkey"""
-    receiverkey = id(receiver)
-    receivers_list = sendersBack.get(receiverkey, ())
-    while senderkey in receivers_list:
-        try:
-            receivers_list.remove(senderkey)
-        except:
-            break
-    if not receivers_list:
-        try:
-            del sendersBack[ receiverkey ]
-        except KeyError:
-            pass
-    return True
+    warnings.warn(
+        category   = DeprecationWarning,
+        message    = "dispatcher.sendExact() is deprecated; use Signal.send() instead.",
+        stacklevel = 2
+    )
+    return signal.send(sender=sender, **named)

django/dispatch/errors.py

-"""Error types for dispatcher mechanism
-"""
-
-class DispatcherError(Exception):
-    """Base class for all Dispatcher errors"""
-class DispatcherKeyError(KeyError, DispatcherError):
-    """Error raised when unknown (sender,signal) set specified"""
-class DispatcherTypeError(TypeError, DispatcherError):
-    """Error raised when inappropriate signal-type specified (None)"""
-

django/dispatch/license.txt

-PyDispatcher License
+django.dispatch was originally forked from PyDispatcher.
+
+PyDispatcher License:
 
     Copyright (c) 2001-2003, Patrick K. O'Brien and Contributors
     All rights reserved.

django/dispatch/robust.py

-"""Module implementing error-catching version of send (sendRobust)"""
-from django.dispatch.dispatcher import Any, Anonymous, liveReceivers, getAllReceivers
-from django.dispatch.robustapply import robustApply
-
-def sendRobust(
-    signal=Any, 
-    sender=Anonymous, 
-    *arguments, **named
-):
-    """Send signal from sender to all connected receivers catching errors
-    
-    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 (specifically any subclass of Exception),
-    the error instance is returned as the result for that receiver.
-    """
-    # Call each receiver with whatever arguments it can accept.
-    # Return a list of tuple pairs [(receiver, response), ... ].
-    responses = []
-    for receiver in liveReceivers(getAllReceivers(sender, signal)):
-        try:
-            response = robustApply(
-                receiver,
-                signal=signal,
-                sender=sender,
-                *arguments,
-                **named
-            )
-        except Exception, err:
-            responses.append((receiver, err))
-        else:
-            responses.append((receiver, response))
-    return responses

django/dispatch/robustapply.py

-"""Robust apply mechanism
-
-Provides a function "call", which can sort out
-what arguments a given callable object can take,
-and subset the given arguments to match only
-those which are acceptable.
-"""
-
-def function( receiver ):
-    """Get function-like callable object for given receiver
-
-    returns (function_or_method, codeObject, fromMethod)
-
-    If fromMethod is true, then the callable already
-    has its first argument bound
-    """
-    if hasattr(receiver, '__call__'):
-        # receiver is a class instance; assume it is callable.
-        # Reassign receiver to the actual method that will be called.
-        if hasattr( receiver.__call__, 'im_func') or hasattr( receiver.__call__, 'im_code'):
-            receiver = receiver.__call__
-    if hasattr( receiver, 'im_func' ):
-        # an instance-method...
-        return receiver, receiver.im_func.func_code, 1
-    elif not hasattr( receiver, 'func_code'):
-        raise ValueError('unknown reciever type %s %s'%(receiver, type(receiver)))
-    return receiver, receiver.func_code, 0
-
-def robustApply(receiver, *arguments, **named):
-    """Call receiver with arguments and an appropriate subset of named
-    """
-    receiver, codeObject, startIndex = function( receiver )
-    acceptable = codeObject.co_varnames[startIndex+len(arguments):codeObject.co_argcount]
-    for name in codeObject.co_varnames[startIndex:startIndex+len(arguments)]:
-        if named.has_key( name ):
-            raise TypeError(
-                """Argument %r specified both positionally and as a keyword for calling %r"""% (
-                    name, receiver,
-                )
-            )
-    if not (codeObject.co_flags & 8):
-        # fc does not have a **kwds type parameter, therefore 
-        # remove unacceptable arguments.
-        for arg in named.keys():
-            if arg not in acceptable:
-                del named[arg]
-    return receiver(*arguments, **named)

django/dispatch/saferef.py

-"""Refactored "safe reference" from dispatcher.py"""
+"""
+"Safe weakrefs", originally from pyDispatcher.
+
+Provides a way to safely weakref any function, including bound methods (which
+aren't handled by the core weakref module).
+"""
+
 import weakref, traceback
 
 def safeRef(target, onDelete = None):
             same BoundMethodWeakref instance.
 
     """
+    
     _allInstances = weakref.WeakValueDictionary()
+    
     def __new__( cls, target, onDelete=None, *arguments,**named ):
         """Create new instance or return current instance
 
             cls._allInstances[key] = base
             base.__init__( target, onDelete, *arguments,**named)
             return base
+    
     def __init__(self, target, onDelete=None):
         """Return a weak-reference-like instance for a bound method
 
         self.weakFunc = weakref.ref(target.im_func, remove)
         self.selfName = str(target.im_self)
         self.funcName = str(target.im_func.__name__)
+    
     def calculateKey( cls, target ):
         """Calculate the reference key for this reference
 
         """
         return (id(target.im_self),id(target.im_func))
     calculateKey = classmethod( calculateKey )
+    
     def __str__(self):
         """Give a friendly representation of the object"""
         return """%s( %s.%s )"""%(
             self.selfName,
             self.funcName,
         )
+    
     __repr__ = __str__
+    
     def __nonzero__( self ):
         """Whether we are still a valid reference"""
         return self() is not None
+    
     def __cmp__( self, other ):
         """Compare with another reference"""
         if not isinstance (other,self.__class__):
             return cmp( self.__class__, type(other) )
         return cmp( self.key, other.key)
+    
     def __call__(self):
         """Return a strong reference to the bound method
 
                 return getattr(target, function.__name__)
         return None
 
-
 def get_bound_method_weakref(target, onDelete):
     """Instantiates the appropiate BoundMethodWeakRef, depending on the details of
     the underlying class method implementation"""
     else:
         # no luck, use the alternative implementation:
         return BoundNonDescriptorMethodWeakref(target=target, onDelete=onDelete)
-

django/test/client.py

 from django.core.handlers.base import BaseHandler
 from django.core.handlers.wsgi import WSGIRequest
 from django.core.signals import got_request_exception
-from django.dispatch import dispatcher
 from django.http import SimpleCookie, HttpRequest
 from django.template import TemplateDoesNotExist
 from django.test import signals
         if self._request_middleware is None:
             self.load_middleware()
 
-        dispatcher.send(signal=signals.request_started)
+        signals.request_started.send(sender=self.__class__)
         try:
             request = WSGIRequest(environ)
             response = self.get_response(request)
                 response = middleware_method(request, response)
             response = self.apply_response_fixes(request, response)
         finally:
-            dispatcher.send(signal=signals.request_finished)
+            signals.request_finished.send(sender=self.__class__)
 
         return response
 
-def store_rendered_templates(store, signal, sender, template, context):
+def store_rendered_templates(store, signal, sender, template, context, **kwargs):
     """
     Stores templates and contexts that are rendered.
     """
         self.cookies = SimpleCookie()
         self.exc_info = None
 
-    def store_exc_info(self, *args, **kwargs):
+    def store_exc_info(self, **kwargs):
         """
         Stores exceptions when they are generated by a view.
         """
         # callback function.
         data = {}
         on_template_render = curry(store_rendered_templates, data)
-        dispatcher.connect(on_template_render, signal=signals.template_rendered)
+        signals.template_rendered.connect(on_template_render)
 
         # Capture exceptions created by the handler.
-        dispatcher.connect(self.store_exc_info, signal=got_request_exception)
+        got_request_exception.connect(self.store_exc_info)
 
         try:
             response = self.handler(environ)

django/test/signals.py

-template_rendered = object()
+from django.dispatch import Signal
+
+template_rendered = Signal(providing_args=["template", "context"])

django/test/utils.py

 from django.db import connection, get_creation_module
 from django.core import mail
 from django.core.management import call_command
-from django.dispatch import dispatcher
 from django.test import signals
 from django.template import Template
 from django.utils.translation import deactivate
     An instrumented Template render method, providing a signal
     that can be intercepted by the test system Client
     """
-    dispatcher.send(signal=signals.template_rendered, sender=self, template=self, context=context)
+    signals.template_rendered.send(sender=self, template=self, context=context)
     return self.nodelist.render(context)
 
 class TestSMTPConnection(object):

tests/modeltests/signals/models.py

 """
 
 from django.db import models
-from django.dispatch import dispatcher
 
 class Person(models.Model):
     first_name = models.CharField(max_length=20)
     def __unicode__(self):
         return u"%s %s" % (self.first_name, self.last_name)
 
-
-def pre_save_nokwargs_test(sender, instance):
-    print 'pre_save_nokwargs signal'
-
-def post_save_nokwargs_test(sender, instance):
-    print 'post_save_nokwargs signal'
-
-def pre_save_test(sender, instance, **kwargs):
+def pre_save_test(signal, sender, instance, **kwargs):
     print 'pre_save signal,', instance
     if kwargs.get('raw'):
         print 'Is raw'
 
-def post_save_test(sender, instance, **kwargs):
+def post_save_test(signal, sender, instance, **kwargs):
     print 'post_save signal,', instance
     if 'created' in kwargs:
         if kwargs['created']:
     if kwargs.get('raw'):
         print 'Is raw'
 
-def pre_delete_test(sender, instance, **kwargs):
+def pre_delete_test(signal, sender, instance, **kwargs):
     print 'pre_delete signal,', instance
     print 'instance.id is not None: %s' % (instance.id != None)
 
-def post_delete_test(sender, instance, **kwargs):
+def post_delete_test(signal, sender, instance, **kwargs):
     print 'post_delete signal,', instance
     print 'instance.id is None: %s' % (instance.id == None)
 
 __test__ = {'API_TESTS':"""
->>> dispatcher.connect(pre_save_nokwargs_test, signal=models.signals.pre_save)
->>> dispatcher.connect(post_save_nokwargs_test, signal=models.signals.post_save)
->>> dispatcher.connect(pre_save_test, signal=models.signals.pre_save)
->>> dispatcher.connect(post_save_test, signal=models.signals.post_save)
->>> dispatcher.connect(pre_delete_test, signal=models.signals.pre_delete)
->>> dispatcher.connect(post_delete_test, signal=models.signals.post_delete)
+>>> models.signals.pre_save.connect(pre_save_test)
+>>> models.signals.post_save.connect(post_save_test)
+>>> models.signals.pre_delete.connect(pre_delete_test)
+>>> models.signals.post_delete.connect(post_delete_test)
 
 >>> p1 = Person(first_name='John', last_name='Smith')
 >>> p1.save()
-pre_save_nokwargs signal
 pre_save signal, John Smith
-post_save_nokwargs signal
 post_save signal, John Smith
 Is created
 
 >>> p1.first_name = 'Tom'
 >>> p1.save()
-pre_save_nokwargs signal
 pre_save signal, Tom Smith
-post_save_nokwargs signal
 post_save signal, Tom Smith
 Is updated
 
 # Calling an internal method purely so that we can trigger a "raw" save.
 >>> p1.save_base(raw=True)
-pre_save_nokwargs signal
 pre_save signal, Tom Smith
 Is raw
-post_save_nokwargs signal
 post_save signal, Tom Smith
 Is updated
 Is raw
 >>> p2 = Person(first_name='James', last_name='Jones')
 >>> p2.id = 99999
 >>> p2.save()
-pre_save_nokwargs signal
 pre_save signal, James Jones
-post_save_nokwargs signal
 post_save signal, James Jones
 Is created
 
 >>> p2.id = 99998
 >>> p2.save()
-pre_save_nokwargs signal
 pre_save signal, James Jones
-post_save_nokwargs signal
 post_save signal, James Jones
 Is created
 
 >>> Person.objects.all()
 [<Person: James Jones>]
 
->>> dispatcher.disconnect(pre_save_nokwargs_test, signal=models.signals.pre_save)
->>> dispatcher.disconnect(post_save_nokwargs_test, signal=models.signals.post_save)
->>> dispatcher.disconnect(post_delete_test, signal=models.signals.post_delete)
->>> dispatcher.disconnect(pre_delete_test, signal=models.signals.pre_delete)
->>> dispatcher.disconnect(post_save_test, signal=models.signals.post_save)
->>> dispatcher.disconnect(pre_save_test, signal=models.signals.pre_save)
+>>> models.signals.post_delete.disconnect(post_delete_test)
+>>> models.signals.pre_delete.disconnect(pre_delete_test)
+>>> models.signals.post_save.disconnect(post_save_test)
+>>> models.signals.pre_save.disconnect(pre_save_test)
 """}

tests/regressiontests/dispatch/tests/__init__.py

 Unit-tests for the dispatch project
 """
 
+from test_saferef import *
 from test_dispatcher import *
-from test_robustapply import *
-from test_saferef import *

tests/regressiontests/dispatch/tests/test_dispatcher.py

-from django.dispatch.dispatcher import *
-from django.dispatch import dispatcher, robust
+from django.dispatch import Signal
 import unittest
 import copy
 import sys
     def garbage_collect():
         gc.collect()
 
-def x(a):
-    return a
-
-class Dummy(object):
-    pass
+def receiver_1_arg(val, **kwargs):
+    return val
 
 class Callable(object):
-    def __call__(self, a):
-        return a
+    def __call__(self, val, **kwargs):
+        return val
     
-    def a(self, a):
-        return a
+    def a(self, val, **kwargs):
+        return val
+
+a_signal = Signal(providing_args=["val"])
 
 class DispatcherTests(unittest.TestCase):
     """Test suite for dispatcher (barely started)"""
-    
-    def setUp(self):
-        # track the initial state, since it's possible that others have bleed receivers in
-        garbage_collect()
-        self.sendersBack = copy.copy(dispatcher.sendersBack)
-        self.connections = copy.copy(dispatcher.connections)
-        self.senders = copy.copy(dispatcher.senders)
-    
-    def _testIsClean(self):
+
+    def _testIsClean(self, signal):
         """Assert that everything has been cleaned up automatically"""
-        self.assertEqual(dispatcher.sendersBack, self.sendersBack)
-        self.assertEqual(dispatcher.connections, self.connections)
-        self.assertEqual(dispatcher.senders, self.senders)
+        self.assertEqual(signal.receivers, [])
+
+        # force cleanup just in case
+        signal.receivers = []
     
     def testExact(self):
-        a = Dummy()
-        signal = 'this'
-        connect(x, signal, a)
-        expected = [(x,a)]
-        result = send('this',a, a=a)
+        a_signal.connect(receiver_1_arg, sender=self)
+        expected = [(receiver_1_arg,"test")]
+        result = a_signal.send(sender=self, val="test")
         self.assertEqual(result, expected)
-        disconnect(x, signal, a)
-        self.assertEqual(list(getAllReceivers(a,signal)), [])
-        self._testIsClean()
-    
-    def testAnonymousSend(self):
-        a = Dummy()
-        signal = 'this'
-        connect(x, signal)
-        expected = [(x,a)]
-        result = send(signal,None, a=a)
+        a_signal.disconnect(receiver_1_arg, sender=self)
+        self._testIsClean(a_signal)
+
+    def testIgnoredSender(self):
+        a_signal.connect(receiver_1_arg)
+        expected = [(receiver_1_arg,"test")]
+        result = a_signal.send(sender=self, val="test")
         self.assertEqual(result, expected)
-        disconnect(x, signal)
-        self.assertEqual(list(getAllReceivers(None,signal)), [])
-        self._testIsClean()
-    
-    def testAnyRegistration(self):
-        a = Dummy()
-        signal = 'this'
-        connect(x, signal, Any)
-        expected = [(x,a)]
-        result = send('this',object(), a=a)
-        self.assertEqual(result, expected)
-        disconnect(x, signal, Any)
-        expected = []
-        result = send('this',object(), a=a)
-        self.assertEqual(result, expected)
-        self.assertEqual(list(getAllReceivers(Any,signal)), [])
-        
-        self._testIsClean()
-    
-    def testAnyRegistration2(self):
-        a = Dummy()
-        signal = 'this'
-        connect(x, Any, a)
-        expected = [(x,a)]
-        result = send('this',a, a=a)
-        self.assertEqual(result, expected)
-        disconnect(x, Any, a)
-        self.assertEqual(list(getAllReceivers(a,Any)), [])
-        self._testIsClean()
+        a_signal.disconnect(receiver_1_arg)
+        self._testIsClean(a_signal)
     
     def testGarbageCollected(self):
         a = Callable()
-        b = Dummy()
-        signal = 'this'
-        connect(a.a, signal, b)
+        a_signal.connect(a.a, sender=self)
         expected = []
         del a
         garbage_collect()
-        result = send('this',b, a=b)
+        result = a_signal.send(sender=self, val="test")
         self.assertEqual(result, expected)
-        self.assertEqual(list(getAllReceivers(b,signal)), [])
-        self._testIsClean()
-    
-    def testGarbageCollectedObj(self):
-        class x:
-            def __call__(self, a):
-                return a
-        a = Callable()
-        b = Dummy()
-        signal = 'this'
-        connect(a, signal, b)
-        expected = []
-        del a
-        garbage_collect()
-        result = send('this',b, a=b)
-        self.assertEqual(result, expected)
-        self.assertEqual(list(getAllReceivers(b,signal)), [])
-        self._testIsClean()
-
+        self._testIsClean(a_signal)
     
     def testMultipleRegistration(self):
         a = Callable()
-        b = Dummy()
-        signal = 'this'
-        connect(a, signal, b)
-        connect(a, signal, b)
-        connect(a, signal, b)
-        connect(a, signal, b)
-        connect(a, signal, b)
-        connect(a, signal, b)
-        result = send('this',b, a=b)
+        a_signal.connect(a)
+        a_signal.connect(a)
+        a_signal.connect(a)
+        a_signal.connect(a)
+        a_signal.connect(a)
+        a_signal.connect(a)
+        result = a_signal.send(sender=self, val="test")
         self.assertEqual(len(result), 1)
-        self.assertEqual(len(list(getAllReceivers(b,signal))), 1)
+        self.assertEqual(len(a_signal.receivers), 1)
         del a
-        del b
         del result
         garbage_collect()
-        self._testIsClean()
+        self._testIsClean(a_signal)
+
+    def testUidRegistration(self):
+        def uid_based_receiver_1(**kwargs):
+            pass
+
+        def uid_based_receiver_2(**kwargs):
+            pass
+
+        a_signal.connect(uid_based_receiver_1, dispatch_uid = "uid")
+        a_signal.connect(uid_based_receiver_2, dispatch_uid = "uid")
+        self.assertEqual(len(a_signal.receivers), 1)
+        a_signal.disconnect(dispatch_uid = "uid")
+        self._testIsClean(a_signal)
     
     def testRobust(self):
         """Test the sendRobust function"""
-        def fails():
+        def fails(val, **kwargs):
             raise ValueError('this')
-        a = object()
-        signal = 'this'
-        connect(fails, Any, a)
-        result = robust.sendRobust('this',a, a=a)
+        a_signal.connect(fails)
+        result = a_signal.send_robust(sender=self, val="test")
         err = result[0][1]
         self.assert_(isinstance(err, ValueError))
         self.assertEqual(err.args, ('this',))
+        a_signal.disconnect(fails)
+        self._testIsClean(a_signal)
 
 def getSuite():
     return unittest.makeSuite(DispatcherTests,'test')

tests/regressiontests/dispatch/tests/test_robustapply.py

-from django.dispatch.robustapply import *
-
-import unittest
-
-def noArgument():
-    pass
-
-def oneArgument(blah):
-    pass
-
-def twoArgument(blah, other):
-    pass
-
-class TestCases(unittest.TestCase):
-    def test01(self):
-        robustApply(noArgument)
-    
-    def test02(self):
-        self.assertRaises(TypeError, robustApply, noArgument, "this")
-    
-    def test03(self):
-        self.assertRaises(TypeError, robustApply, oneArgument)
-    
-    def test04(self):
-        """Raise error on duplication of a particular argument"""
-        self.assertRaises(TypeError, robustApply, oneArgument, "this", blah = "that")
-
-def getSuite():
-    return unittest.makeSuite(TestCases,'test')
-
-
-if __name__ == "__main__":
-    unittest.main()
-