Index: django/test/signals.py
===================================================================
--- django/test/signals.py	(revision 7360)
+++ django/test/signals.py	(working copy)
@@ -1 +1,3 @@
-template_rendered = object()
\ No newline at end of file
+from django.dispatch.dispatcher import Signal
+
+template_rendered = Signal(providing_args=["template", "context"])
\ No newline at end of file
Index: django/db/models/signals.py
===================================================================
--- django/db/models/signals.py	(revision 7360)
+++ django/db/models/signals.py	(working copy)
@@ -1,12 +1,14 @@
-class_prepared = object()
+from django.dispatch.dispatcher import Signal
 
-pre_init= object()
-post_init = object()
+class_prepared = Signal()
 
-pre_save = object()
-post_save = object()
+pre_init = Signal(providing_args=["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=["app", "created_models", "verbosity", "interactive"])
Index: django/core/signals.py
===================================================================
--- django/core/signals.py	(revision 7360)
+++ django/core/signals.py	(working copy)
@@ -1,3 +1,5 @@
-request_started = object()
-request_finished = object()
-got_request_exception = object()
+from django.dispatch.dispatcher import Signal
+
+request_started = Signal()
+request_finished = Signal()
+got_request_exception = Signal(providing_args=["request"])
Index: django/dispatch/license.txt
===================================================================
--- django/dispatch/license.txt	(revision 7360)
+++ django/dispatch/license.txt	(working copy)
@@ -1,34 +0,0 @@
-PyDispatcher License
-
-    Copyright (c) 2001-2003, Patrick K. O'Brien and Contributors
-    All rights reserved.
-    
-    Redistribution and use in source and binary forms, with or without
-    modification, are permitted provided that the following conditions
-    are met:
-    
-        Redistributions of source code must retain the above copyright
-        notice, this list of conditions and the following disclaimer.
-    
-        Redistributions in binary form must reproduce the above
-        copyright notice, this list of conditions and the following
-        disclaimer in the documentation and/or other materials
-        provided with the distribution.
-    
-        The name of Patrick K. O'Brien, or the name of any Contributor,
-        may not be used to endorse or promote products derived from this 
-        software without specific prior written permission.
-    
-    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-    ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-    COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
-    INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-    SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-    HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
-    STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
-    OF THE POSSIBILITY OF SUCH DAMAGE. 
-
Index: django/dispatch/saferef.py
===================================================================
--- django/dispatch/saferef.py	(revision 7354)
+++ django/dispatch/saferef.py	(working copy)
@@ -3,7 +3,8 @@
 from django.utils.functional import curry
 
 def safeRef(target, onDelete = None):
-    """Return a *safe* weak reference to a callable target
+    """Return a tuple of a *safe* weak reference to a callable
+    target and the id of that object.
 
     target -- the object to be weakly referenced, if it's a
         bound method reference, will create a BoundMethodWeakref,
@@ -23,6 +24,7 @@
                 onDelete=onDelete
             )
             return reference
+    # If a weakref is created using 
     if callable(onDelete):
         return weakref.ref(target, onDelete)
     else:
@@ -234,5 +236,4 @@
         return BoundMethodWeakref(target=target, onDelete=onDelete)
     else:
         # no luck, use the alternative implementation:
-        return BoundNonDescriptorMethodWeakref(target=target, onDelete=onDelete)
-
+        return BoundNonDescriptorMethodWeakref(target=target, onDelete=onDelete)
\ No newline at end of file
Index: django/dispatch/errors.py
===================================================================
--- django/dispatch/errors.py	(revision 7354)
+++ django/dispatch/errors.py	(working copy)
@@ -3,8 +3,6 @@
 
 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)"""
 
Index: django/dispatch/robust.py
===================================================================
--- django/dispatch/robust.py	(revision 7360)
+++ django/dispatch/robust.py	(working copy)
@@ -1,57 +0,0 @@
-"""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
Index: django/dispatch/__init__.py
===================================================================
--- django/dispatch/__init__.py	(revision 7354)
+++ django/dispatch/__init__.py	(working copy)
@@ -4,3 +4,14 @@
 __author__ = "Patrick K. O'Brien"
 __license__ = "BSD-style, see license.txt for details"
 
+"""
+new - weak_dict
+0 receivers test 1.70392489433
+1 receiver test 11.3397159576 1.23367714882
+"""
+"""
+new - strong dict
+0 receivers test 2.07081604004
+1 receiver test 7.21637010574 1.23387694359
+10 receivers test 24.1335361004 4.61771893501
+"""
\ No newline at end of file
Index: django/dispatch/dispatcher_weak_dict.py
===================================================================
--- django/dispatch/dispatcher_weak_dict.py	(revision 0)
+++ django/dispatch/dispatcher_weak_dict.py	(revision 0)
@@ -0,0 +1,202 @@
+import weakref
+import inspect
+
+try:
+    set
+except NameError:
+    from sets import Set as set     # For Python 2.3
+
+from django.dispatch import saferef, errors
+
+WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref)
+
+class Signal(object):
+    """Base class for all signals
+    
+    Internal attributes:
+        receivers -- { receiver_key (id) : (receiver, 
+                                           acceptable_named_args, 
+                                           accepts_kwargs) }
+    """
+    def __init__(self, providing_args=[]):
+        """providing_args -- A list of the arguments this signal can pass along in
+                       a send() call.
+        """
+        self._receivers = {}
+        self.providing_args = set(providing_args)
+    
+    def connect(self, receiver, sender=None, weak=False):
+        """Connect receiver to sender for signal
+    
+        receiver -- a function or an instance method 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 the named arguments
+            declared in providing_args.
+    
+            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.
+            
+        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.
+    
+        returns None
+        """
+        allowed_args, _, kw_var, _ = inspect.getargspec(receiver)
+        accepts_kwargs = kw_var is not None
+        
+        receiver_key = (id(sender), id(receiver))
+        
+        if weak:
+            receiver = saferef.safeRef(receiver)
+        
+        self._receivers[receiver_key] = (receiver, set(allowed_args), accepts_kwargs)
+        
+    def disconnect(self, receiver, sender=None, weak=True):
+        """Disconnect receiver from sender for signal
+    
+        receiver -- the registered receiver to disconnect
+        sender -- the registered sender to disconnect, or None 
+                  if the receiver should be disconnected from all.
+        weak -- deprecated and ignored.
+    
+        disconnect reverses the process of connect.
+    
+        Note:
+            Using disconnect is not required to cleanup
+            routing when an object is deleted, the framework
+            will skip routes for deleted objects
+            automatically.  It's only necessary to disconnect
+            if you want to stop routing to a live object.
+            
+        returns None
+        """
+        receiver_key = (id(sender), id(receiver))
+        try:
+            del self._receivers[receiver_key]
+        except KeyError:
+            return False
+    
+    def send(self, sender, **named):
+        """
+        Send signal from sender to all connected receivers.
+        
+        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, or a DispatcherTypeError will be
+            raised.
+    
+        Return a list of tuple pairs [(receiver, response), ... ].
+
+        An exception from any receiver halts dispatch and propagates
+        back through send.
+        """
+        responses = []
+        if not self._receivers:
+            return responses
+
+        self._enforce_send_api(named) #FIXME: 20% speedup in send if we take this out
+        for receiver_key, receiver_info in self.get_live_receivers(sender):
+            receiver, allowed_args, accepts_kwargs = receiver_info
+
+            allowed = named.copy()
+            allowed['sender'] = sender
+                        
+            if not accepts_kwargs:
+                for arg in allowed.keys():
+                    if arg not in allowed_args:
+                        del allowed[arg]
+
+            responses.append((receiver, receiver(**allowed)))
+        return responses
+        
+    def get_live_receivers(self, sender):
+        """
+        Returns an iterable over all live receivers for the signal.
+        """
+        listening_to_all = id(None)
+        sending_from = id(sender)
+        for ((listen_to_id, receiver_id), receiver_info) in self._receivers.items():
+            if listen_to_id != listening_to_all and sending_from != listen_to_id:
+                continue
+            receiver_, allowed_args, accepts_kwargs = receiver_info
+            if not self._receiver_is_live(receiver_):
+                del self._receivers[(listen_to_id, receiver_id)]
+                continue
+            yield (sending_from, receiver_id), (self._get_receiver(receiver_), allowed_args, accepts_kwargs)
+            
+
+    def _get_receiver(self, receiver):
+        if isinstance(receiver, WEAKREF_TYPES):
+            return receiver()
+        else:
+            return receiver
+        
+    def _receiver_is_live(self, receiver):
+        return bool(self._get_receiver(receiver) is not None)
+
+    def _enforce_send_api(self, sent_names):
+        """
+        Ensures that send calls are made with the parameters declared 
+          in Signal construction.
+        """
+        sent_names_set = set(sent_names.keys())
+
+        if sent_names_set != self.providing_args:
+            pluralize = lambda qty: qty != 1 and 's' or ''
+            flatten = lambda s: ",".join([str(item) for item in s])
+
+            extras = sent_names_set - self.providing_args
+            missing = self.providing_args - sent_names_set
+            
+            message = "Signal.send"
+            if extras:
+                message += " got unexpected argment%s: %s" % (pluralize(len(extras)), flatten(extras))
+            if missing:
+                if extras:
+                    message += " and"
+                message += " missing expected argment%s: %s" % (pluralize(len(missing)), flatten(missing))
+            
+            raise errors.DispatcherTypeError, message
+
+
+def connect(receiver, signal, sender=None, weak=True):
+    """For backward compatibility only. See Signal.connect()
+    """
+    return signal.connect(receiver, sender, weak)
+
+def disconnect(receiver, signal, sender=None, weak=True):
+    """For backward compatibility only. See Signal.disconnect()
+    """
+    signal.disconnect(receiver, sender, weak)
+
+def send(signal, sender=None, **named):
+    """For backward compatibility only. See Signal.send()
+    """
+    return signal.send(sender=sender, **named)
+
+def sendExact(signal, sender, **named ):
+    """This function is deprecated, as it now has the same
+    meaning as send.
+    """
+    return signal.send(sender=sender, **named)
\ No newline at end of file

Property changes on: django/dispatch/dispatcher_weak_dict.py
___________________________________________________________________
Name: svn:executable
   + *

Index: django/dispatch/robustapply.py
===================================================================
--- django/dispatch/robustapply.py	(revision 7360)
+++ django/dispatch/robustapply.py	(working copy)
@@ -1,47 +0,0 @@
-"""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)
Index: django/dispatch/dispatcher_strong_dict.py
===================================================================
--- django/dispatch/dispatcher_strong_dict.py	(revision 0)
+++ django/dispatch/dispatcher_strong_dict.py	(revision 0)
@@ -0,0 +1,161 @@
+import inspect
+
+try:
+    set
+except NameError:
+    from sets import Set as set     # For Python 2.3
+
+from django.dispatch import errors
+
+class Signal(object):
+    """Base class for all signals
+    
+    Internal attributes:
+        receivers -- { receiver_key (id) : (receiver, 
+                                           acceptable_named_args, 
+                                           accepts_kwargs) }
+    """
+    
+    def __init__(self, providing_args=[]):
+        """providing_args -- A list of the arguments this signal can pass along in
+                       a send() call.
+        """
+        self._receivers = {}
+        self.providing_args = set(providing_args)
+    
+    def connect(self, receiver, sender=None):
+        """Connect receiver to sender for signal
+    
+        receiver -- a function or an instance method which is to
+            receive messages/signals/events.  Receivers must be
+            hashable objects.
+    
+            Receivers must be able to accept the named arguments
+            declared in providing_args.
+    
+        sender -- the sender to which the receiver should respond
+            Must either be of type Signal, or None to receive events
+            from any sender.
+            
+        returns None
+        """
+        allowed_args, _, kw_var, _ = inspect.getargspec(receiver)
+        accepts_kwargs = kw_var is not None
+        
+        receiver_key = (id(sender), id(receiver))
+        
+        self._receivers[receiver_key] = (receiver, set(allowed_args), accepts_kwargs)
+        
+    def disconnect(self, receiver, sender=None):
+        """Disconnect receiver from sender for signal
+    
+        receiver -- the registered receiver to disconnect
+        sender -- the registered sender to disconnect, or None 
+                  if the receiver should be disconnected from all.
+    
+        disconnect reverses the process of connect.
+    
+        Note:
+            Using disconnect is not required to cleanup
+            routing when an object is deleted, the framework
+            will skip routes for deleted objects
+            automatically.  It's only necessary to disconnect
+            if you want to stop routing to a live object.
+            
+        returns None
+        """
+        receiver_key = (id(sender), id(receiver))
+        try:
+            del self._receivers[receiver_key]
+        except KeyError:
+            return False
+    
+    def send(self, sender, **named):
+        """
+        Send signal from sender to all connected receivers.
+        
+        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, or a DispatcherTypeError will be
+            raised.
+    
+        Return a list of tuple pairs [(receiver, response), ... ].
+
+        An exception from any receiver halts dispatch and propagates
+        back through send.
+        """
+        responses = []
+        if len(self._receivers) == 0:
+            return responses
+
+        self._enforce_send_api(named) #FIXME: 20% speedup in send if we take this out
+
+        listening_to_all = id(None)
+        sending_from = id(sender)
+        
+        for ((listen_to_id, receiver_id), receiver_info) in self._receivers.items():
+            if listen_to_id != listening_to_all and sending_from != listen_to_id:
+                continue
+
+            receiver, allowed_args, accepts_kwargs = receiver_info
+
+            allowed = named.copy()
+            allowed['sender'] = sender
+                        
+            if not accepts_kwargs:
+                for arg in allowed.keys():
+                    if arg not in allowed_args:
+                        del allowed[arg]
+
+            responses.append((receiver, receiver(**allowed)))
+        return responses
+        
+    def _enforce_send_api(self, sent_names):
+        """
+        Ensures that send calls are made with the parameters declared 
+          in Signal construction.
+        """
+        sent_names_set = set(sent_names.keys())
+
+        if sent_names_set != self.providing_args:
+            pluralize = lambda qty: qty != 1 and 's' or ''
+            flatten = lambda s: ",".join([str(item) for item in s])
+
+            extras = sent_names_set - self.providing_args
+            missing = self.providing_args - sent_names_set
+            
+            message = "Signal.send"
+            if extras:
+                message += " got unexpected argment%s: %s" % (pluralize(len(extras)), flatten(extras))
+            if missing:
+                if extras:
+                    message += " and"
+                message += " missing expected argment%s: %s" % (pluralize(len(missing)), flatten(missing))
+            
+            raise errors.DispatcherTypeError, message
+
+
+def connect(receiver, signal, sender=None):
+    """For backward compatibility only. See Signal.connect()
+    """
+    return signal.connect(receiver, sender)
+
+def disconnect(receiver, signal, sender=None):
+    """For backward compatibility only. See Signal.disconnect()
+    """
+    signal.disconnect(receiver, sender)
+
+def send(signal, sender=None, **named):
+    """For backward compatibility only. See Signal.send()
+    """
+    return signal.send(sender=sender, **named)
+
+def sendExact(signal, sender, **named):
+    """This function is deprecated, as it now has the same
+    meaning as send.
+    """
+    return signal.send(sender=sender, **named)
\ No newline at end of file
Index: django/dispatch/dispatcher.py
===================================================================
--- django/dispatch/dispatcher.py	(revision 7354)
+++ django/dispatch/dispatcher.py	(working copy)
@@ -1,495 +1,208 @@
-"""Multiple-producer-multiple-consumer signal-dispatching
+import inspect
 
-dispatcher is the core of the PyDispatcher system,
-providing the primary API and the core logic for the
-system.
+try:
+    set
+except NameError:
+    from sets import Set as set     # For Python 2.3
 
-Module attributes of note:
+from django.dispatch import errors
 
-    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.
+pluralize = lambda qty: qty != 1 and 's' or ''
+flatten = lambda s: ",".join([str(item) for item in s])
 
-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 types, 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.
+class Signal(object):
     """
-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.
+    Internal attributes:
+        _receivers -- { receiver_key (id) : (sender_id,
+                                           receiver, 
+                                           acceptable_named_args, 
+                                           accepts_kwargs) }
     """
-Anonymous = _Anonymous()
-
-WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref)
-
-connections = {}
-senders = {}
-sendersBack = {}
-
-
-def 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
+    def __init__(self, providing_args=[]):
+        """providing_args -- A list of the arguments this signal will pass along in
+                       a send() call.
+        """
+        self._receivers = []
+        self.providing_args = set(providing_args)
     
-        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
+    def connect(self, receiver, sender=None):
+        """Connect receiver to sender for signal
     
-        if Any, receiver will receive the indicated signals
-        from any sender.
+        receiver -- a function or an instance method which is to
+            receive messages/signals/events.  Receivers must be
+            hashable objects.
+    
+            Receivers must be able to accept the named arguments
+            declared in providing_args.
+    
+        sender -- the sender to which the receiver should respond
+            Must either be of type Signal, or None to receive events
+            from any sender.
+            
+        returns None
+        """
+        allowed_args, _, kw_var, _ = inspect.getargspec(receiver)
+        allowed_args = set(allowed_args) 
+        accepts_kwargs = kw_var is not None
         
-        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.
+        if not accepts_kwargs:
+            self._enforce_receive_api(receiver, allowed_args)
         
-    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.
+        sender_id = id(sender) 
+        receiver_id = id(receiver)
 
-    returns None, may raise DispatcherTypeError
-    """
-    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)
-
-    signals = connections.setdefault(senderkey, {})
-
-    # 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
+        for existing_sender_id, existing_receiver, _, _ in self._receivers:
+            if self._receiver_match(existing_sender_id, id(existing_receiver), sender_id, receiver_id):
+                return
+        receiver_info = (sender_id, receiver, allowed_args, accepts_kwargs)
+        self._receivers.append(receiver_info)
         
-    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 disconnect(self, receiver, sender=None):
+        """Disconnect receiver from sender for signal
+    
+        receiver -- the registered receiver to disconnect
+        sender -- the registered sender to disconnect, or None 
+                  if the receiver should be disconnected from all.
+    
+        disconnect reverses the process of connect.
+    
+        Note:
+            Using disconnect is not required to cleanup
+            routing when an object is deleted, the framework
+            will skip routes for deleted objects
+            automatically.  It's only necessary to disconnect
+            if you want to stop routing to a live object.
+            
+        returns None
+        """
+        requested_sender_id = id(sender)
+        requested_receiver_id = id(receiver)
 
-    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.
+        for i, (sender_id, receiver, _, _) in enumerate(self._receivers):
+            if self._receiver_match(requested_sender_id,
+                                    requested_receiver_id,
+                                    sender_id,
+                                    id(receiver)):
+                self._receivers.pop(i)
+                return True
+        return False
+    
+    def _receiver_match(self, requested_sender_id, requested_receiver_id, 
+            sender_id, receiver_id):
+        return bool(requested_sender_id == sender_id and requested_receiver_id == receiver_id)
+    
+    def send(self, sender, **named):
+        """
+        Send signal from sender to all connected receivers.
         
-    returns None, may raise DispatcherTypeError or
-        DispatcherKeyError
-    """
-    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.
-    """
-    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.
+        sender -- the sender of the signal
+            Can be any python object (normally one registered with
+            a connect if you actually want something to occur).
     
-    signal -- (hashable) signal value, see connect for details
-
-    sender -- the sender of the signal
+        named -- named arguments which will be passed to receivers.
+            These arguments must be a subset of the argument names
+            defined in providing_args, or a DispatcherTypeError will be
+            raised.
     
-        if Any, only receivers registered for Any will receive
-        the message.
+        Return a list of tuple pairs [(receiver, response), ... ].
 
-        if Anonymous, only receivers registered to receive
-        messages from Anonymous or Any will receive the message
+        An exception from any receiver halts dispatch and propagates
+        back through send.
+        """
+        responses = []
+        if len(self._receivers) == 0:
+            return responses
 
-        Otherwise can be any python object (normally one
-        registered with a connect if you actually want
-        something to occur).
+        self._enforce_send_api(named) #FIXME: 2microsec (~30% for 1 receiver) speedup if we take this out
 
-    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.
+        listening_to_all = id(None)
+        sending_from = id(sender)
 
-    named -- named arguments which will be filtered according
-        to the parameters of the receivers to only provide those
-        acceptable to the receiver.
+        named['sender'] = sender
 
-    Return a list of tuple pairs [(receiver, response), ... ]
+        for (listen_to_id, receiver, allowed_args, accepts_kwargs) in self._receivers:
+            #a receiver can either listen to all senders or a specific sender.
+            if listen_to_id != listening_to_all and sending_from != listen_to_id:
+                continue
 
-    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
+            #debate: 
+            ##surprisingly slow
+            #if accepts_kwargs:
+            #    call_args = named
+            #else:
+            #    call_args = dict([pair for pair in named.items() if pair[0] in allowed_args])
 
+            #slightly faster
+            #call_args = named.copy()
+            #            
+            #if not accepts_kwargs:
+            #    for arg in call_args.keys():
+            #        if arg not in allowed_args:
+            #            del call_args[arg]
 
-def sendExact( signal=Any, sender=Anonymous, *arguments, **named ):
-    """Send signal only to those receivers registered for exact message
+            #fastest (so far)
+            if accepts_kwargs:
+                call_args = named
+            else:
+                call_args = {}
+                for arg in allowed_args:
+                    call_args[arg] = named[arg]
 
-    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
-    
+            #call_args = named
+            responses.append((receiver, receiver(**call_args)))
+        return responses
+        
+    def _enforce_send_api(self, call_names):
+        """
+        Ensures that send calls are made with the parameters declared 
+          in Signal construction.
+        """
+        call_names_set = set(call_names.keys())
 
-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
+        if call_names_set != self.providing_args:
+            extras = call_names_set - self.providing_args
+            missing = self.providing_args - call_names_set
             
-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)
+            message = "Signal.send"
+            if extras:
+                message += " received unexpected argment%s: %s" % (pluralize(len(extras)), flatten(extras))
+            if missing:
+                if extras:
+                    message += " and"
+                message += " missing expected argment%s: %s" % (pluralize(len(missing)), flatten(missing))
+            
+            raise errors.DispatcherTypeError, message
 
-def _removeSender(senderkey):
-    """Remove senderkey from connections."""
-    _removeBackrefs(senderkey)
+    def _enforce_receive_api(self, receiver, required_args):
+        """
+        Ensures that connected receivers don't expect unprovided arguments.
+        """
+        extra_args = required_args - self.providing_args
+        extra_args -= set(['sender', 'self'])
 
-    connections.pop(senderkey, None)
-    senders.pop(senderkey, None)
+        if extra_args:
+            message = "Signal.connect called with receiver expecting unprovided argment%s: %s" % (pluralize(len(extra_args)), flatten(extra_args))
+            raise errors.DispatcherTypeError, message
+        
 
+def connect(receiver, signal, sender=None):
+    """For backward compatibility only. See Signal.connect()
+    """
+    return signal.connect(receiver, sender)
 
-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 disconnect(receiver, signal, sender=None):
+    """For backward compatibility only. See Signal.disconnect()
+    """
+    signal.disconnect(receiver, sender)
 
+def send(signal, sender=None, **named):
+    """For backward compatibility only. See Signal.send()
+    """
+    return signal.send(sender=sender, **named)
 
-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
+def sendExact(signal, sender, **named):
+    """This function is deprecated, as it now has the same
+    meaning as send.
     """
-    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
+    return signal.send(sender=sender, **named)
\ No newline at end of file
Index: tests/regressiontests/dispatch/tests.py
===================================================================
--- tests/regressiontests/dispatch/tests.py	(revision 0)
+++ tests/regressiontests/dispatch/tests.py	(revision 0)
@@ -0,0 +1,93 @@
+"""
+>>> from django.dispatch import dispatcher
+>>> specific_sender = object()
+>>> different_sender = object()
+>>> expect_one = dispatcher.Signal(providing_args=['arg0'])
+>>> expect_three = dispatcher.Signal(providing_args=['arg0', 'arg1', 'arg2'])
+>>> unheard = dispatcher.Signal(providing_args=[])
+>>>
+>>> def handle_any(**kwargs):
+...     return 'handle_any'
+>>> def handle_mismatch(spam):
+...     return 'handle_mismatch'
+>>> def handle_three(arg0,arg1,arg2):
+...     return 'handle_three: ', arg0, arg1, arg2
+>>> def handle_two(arg0,arg1):
+...     return 'handle_two: ', arg0, arg1
+>>> def handle_one(arg0):
+...     return 'handle_one: ', arg0
+>>> def handle_none():
+...     return 'handle_none'
+
+>>> dispatcher.connect(handle_any, expect_one)
+>>> dispatcher.send(expect_one, arg0=1)
+[(<function handle_any at ...>, 'handle_any')]
+
+>>> #connect is idempotent
+>>> dispatcher.connect(handle_any, expect_one)
+>>> dispatcher.send(expect_one, arg0=1)
+[(<function handle_any at ...>, 'handle_any')]
+
+>>> #disconnect of unconnected silently passes
+>>> dispatcher.disconnect(handle_none, expect_one)
+
+>>> #but doesn't disconnect existing
+>>> dispatcher.send(expect_one, arg0=1)
+[(<function handle_any at ...>, 'handle_any')]
+
+>>> #disconnect is specific
+>>> dispatcher.disconnect(handle_any, expect_one)
+>>> dispatcher.send(expect_one, arg0=1)
+[]
+
+>>> #listening to a specific sender ignores from other senders
+>>> dispatcher.connect(handle_any, expect_one, specific_sender)
+>>> dispatcher.connect(handle_any, expect_one)
+>>> dispatcher.send(expect_one, arg0=1, sender=specific_sender)
+[(<function handle_any at ...>, 'handle_any'), (<function handle_any at ...>, 'handle_any')]
+>>> dispatcher.send(expect_one, arg0=1, sender=different_sender)
+[(<function handle_any at ...>, 'handle_any')]
+>>> dispatcher.send(expect_one, arg0=1)
+[(<function handle_any at ...>, 'handle_any')]
+>>> dispatcher.disconnect(handle_any, expect_one)
+>>> dispatcher.disconnect(handle_any, expect_one, specific_sender)
+
+>>> #receivers need not accept all args of a signal
+>>> dispatcher.connect(handle_none, expect_one)
+>>> dispatcher.send(expect_one, arg0=1)
+[(<function handle_none at ...>, 'handle_none')]
+
+>>> # but they must not require additional args.
+>>> dispatcher.connect(handle_mismatch, expect_one)
+Traceback (most recent call last):
+...
+DispatcherTypeError: Signal.connect called with receiver expecting unprovided argment: spam
+
+>>> #multiple receivers can accept different permutations of arguments.
+>>> dispatcher.connect(handle_three, expect_three)
+>>> dispatcher.connect(handle_two, expect_three)
+>>> dispatcher.connect(handle_one, expect_three)
+>>> dispatcher.send(expect_three, arg0=1, arg1=2, arg2=3)
+[(<function handle_three at ...>, ('handle_three: ', 1, 2, 3)), (<function handle_two at ...>, ('handle_two: ', 1, 2)), (<function handle_one at ...>, ('handle_one: ', 1))]
+>>> dispatcher.disconnect(handle_three, expect_three)
+>>> dispatcher.disconnect(handle_two, expect_three)
+>>> dispatcher.disconnect(handle_one, expect_three)
+>>> dispatcher.connect(handle_one, expect_three)
+>>> dispatcher.connect(handle_two, expect_three)
+>>> dispatcher.connect(handle_three, expect_three)
+>>> dispatcher.send(expect_three, arg0=1, arg1=2, arg2=3)
+[(<function handle_one at ...>, ('handle_one: ', 1)), (<function handle_two at ...>, ('handle_two: ', 1, 2)), (<function handle_three at ...>, ('handle_three: ', 1, 2, 3))]
+>>> #signals can fire with no receivers
+>>> dispatcher.send(unheard)
+[]
+
+>>> #Signals are first-class. (dispatcher.send is old-style)
+>>> expect_three.send(sender=None, **{'arg0':1, 'arg1':2, 'arg2':3})
+[(<function handle_one at ...>, ('handle_one: ', 1)), (<function handle_two at ...>, ('handle_two: ', 1, 2)), (<function handle_three at ...>, ('handle_three: ', 1, 2, 3))]
+
+>>> #Signals must be fired with only declared arguments.
+>>> expect_three.send(sender=None, **{'arg0':1, 'arg1':2, 'arg9':9})
+Traceback (most recent call last):
+...
+DispatcherTypeError: Signal.send received unexpected argment: arg9 and missing expected argment: arg2
+"""
