Ticket #6814: faster_signals-2.diff

File faster_signals-2.diff, 53.4 KB (added by Jeremy Dunck, 16 years ago)

tentative implementation: "strong_list" in timings.

  • django/test/signals.py

     
    1 template_rendered = object()
    2  No newline at end of file
     1from django.dispatch.dispatcher import Signal
     2
     3template_rendered = Signal(providing_args=["template", "context"])
     4 No newline at end of file
  • django/db/models/signals.py

     
    1 class_prepared = object()
     1from django.dispatch.dispatcher import Signal
    22
    3 pre_init= object()
    4 post_init = object()
     3class_prepared = Signal()
    54
    6 pre_save = object()
    7 post_save = object()
     5pre_init = Signal(providing_args=["args", "kwargs"])
     6post_init = Signal(providing_args=["instance"])
    87
    9 pre_delete = object()
    10 post_delete = object()
     8pre_save = Signal(providing_args=["instance", "raw"])
     9post_save = Signal(providing_args=["instance", "raw", "created"])
    1110
    12 post_syncdb = object()
     11pre_delete = Signal(providing_args=["instance"])
     12post_delete = Signal(providing_args=["instance"])
     13
     14post_syncdb = Signal(providing_args=["app", "created_models", "verbosity", "interactive"])
  • django/core/signals.py

     
    1 request_started = object()
    2 request_finished = object()
    3 got_request_exception = object()
     1from django.dispatch.dispatcher import Signal
     2
     3request_started = Signal()
     4request_finished = Signal()
     5got_request_exception = Signal(providing_args=["request"])
  • django/dispatch/license.txt

     
    1 PyDispatcher License
    2 
    3     Copyright (c) 2001-2003, Patrick K. O'Brien and Contributors
    4     All rights reserved.
    5    
    6     Redistribution and use in source and binary forms, with or without
    7     modification, are permitted provided that the following conditions
    8     are met:
    9    
    10         Redistributions of source code must retain the above copyright
    11         notice, this list of conditions and the following disclaimer.
    12    
    13         Redistributions in binary form must reproduce the above
    14         copyright notice, this list of conditions and the following
    15         disclaimer in the documentation and/or other materials
    16         provided with the distribution.
    17    
    18         The name of Patrick K. O'Brien, or the name of any Contributor,
    19         may not be used to endorse or promote products derived from this
    20         software without specific prior written permission.
    21    
    22     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    23     ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    24     LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
    25     FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
    26     COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
    27     INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    28     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    29     SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
    30     HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
    31     STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    32     ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
    33     OF THE POSSIBILITY OF SUCH DAMAGE.
    34 
  • django/dispatch/saferef.py

     
    33from django.utils.functional import curry
    44
    55def safeRef(target, onDelete = None):
    6     """Return a *safe* weak reference to a callable target
     6    """Return a tuple of a *safe* weak reference to a callable
     7    target and the id of that object.
    78
    89    target -- the object to be weakly referenced, if it's a
    910        bound method reference, will create a BoundMethodWeakref,
     
    2324                onDelete=onDelete
    2425            )
    2526            return reference
     27    # If a weakref is created using
    2628    if callable(onDelete):
    2729        return weakref.ref(target, onDelete)
    2830    else:
     
    234236        return BoundMethodWeakref(target=target, onDelete=onDelete)
    235237    else:
    236238        # no luck, use the alternative implementation:
    237         return BoundNonDescriptorMethodWeakref(target=target, onDelete=onDelete)
    238 
     239        return BoundNonDescriptorMethodWeakref(target=target, onDelete=onDelete)
     240 No newline at end of file
  • django/dispatch/errors.py

     
    33
    44class DispatcherError(Exception):
    55    """Base class for all Dispatcher errors"""
    6 class DispatcherKeyError(KeyError, DispatcherError):
    7     """Error raised when unknown (sender,signal) set specified"""
    86class DispatcherTypeError(TypeError, DispatcherError):
    97    """Error raised when inappropriate signal-type specified (None)"""
    108
  • django/dispatch/robust.py

     
    1 """Module implementing error-catching version of send (sendRobust)"""
    2 from django.dispatch.dispatcher import Any, Anonymous, liveReceivers, getAllReceivers
    3 from django.dispatch.robustapply import robustApply
    4 
    5 def sendRobust(
    6     signal=Any,
    7     sender=Anonymous,
    8     *arguments, **named
    9 ):
    10     """Send signal from sender to all connected receivers catching errors
    11    
    12     signal -- (hashable) signal value, see connect for details
    13 
    14     sender -- the sender of the signal
    15    
    16         if Any, only receivers registered for Any will receive
    17         the message.
    18 
    19         if Anonymous, only receivers registered to receive
    20         messages from Anonymous or Any will receive the message
    21 
    22         Otherwise can be any python object (normally one
    23         registered with a connect if you actually want
    24         something to occur).
    25 
    26     arguments -- positional arguments which will be passed to
    27         *all* receivers. Note that this may raise TypeErrors
    28         if the receivers do not allow the particular arguments.
    29         Note also that arguments are applied before named
    30         arguments, so they should be used with care.
    31 
    32     named -- named arguments which will be filtered according
    33         to the parameters of the receivers to only provide those
    34         acceptable to the receiver.
    35 
    36     Return a list of tuple pairs [(receiver, response), ... ]
    37 
    38     if any receiver raises an error (specifically any subclass of Exception),
    39     the error instance is returned as the result for that receiver.
    40     """
    41     # Call each receiver with whatever arguments it can accept.
    42     # Return a list of tuple pairs [(receiver, response), ... ].
    43     responses = []
    44     for receiver in liveReceivers(getAllReceivers(sender, signal)):
    45         try:
    46             response = robustApply(
    47                 receiver,
    48                 signal=signal,
    49                 sender=sender,
    50                 *arguments,
    51                 **named
    52             )
    53         except Exception, err:
    54             responses.append((receiver, err))
    55         else:
    56             responses.append((receiver, response))
    57     return responses
  • django/dispatch/__init__.py

     
    44__author__ = "Patrick K. O'Brien"
    55__license__ = "BSD-style, see license.txt for details"
    66
     7"""
     8new - weak_dict
     90 receivers test 1.70392489433
     101 receiver test 11.3397159576 1.23367714882
     11"""
     12"""
     13new - strong dict
     140 receivers test 2.07081604004
     151 receiver test 7.21637010574 1.23387694359
     1610 receivers test 24.1335361004 4.61771893501
     17"""
     18 No newline at end of file
  • django/dispatch/dispatcher_weak_dict.py

     
     1import weakref
     2import inspect
     3
     4try:
     5    set
     6except NameError:
     7    from sets import Set as set     # For Python 2.3
     8
     9from django.dispatch import saferef, errors
     10
     11WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref)
     12
     13class Signal(object):
     14    """Base class for all signals
     15   
     16    Internal attributes:
     17        receivers -- { receiver_key (id) : (receiver,
     18                                           acceptable_named_args,
     19                                           accepts_kwargs) }
     20    """
     21    def __init__(self, providing_args=[]):
     22        """providing_args -- A list of the arguments this signal can pass along in
     23                       a send() call.
     24        """
     25        self._receivers = {}
     26        self.providing_args = set(providing_args)
     27   
     28    def connect(self, receiver, sender=None, weak=False):
     29        """Connect receiver to sender for signal
     30   
     31        receiver -- a function or an instance method which is to
     32            receive messages/signals/events.  Receivers must be
     33            hashable objects.
     34   
     35            if weak is True, then receiver must be weak-referencable
     36            (more precisely saferef.safeRef() must be able to create
     37            a reference to the receiver).
     38       
     39            Receivers must be able to accept the named arguments
     40            declared in providing_args.
     41   
     42            Note:
     43                if receiver is itself a weak reference (a callable),
     44                it will be de-referenced by the system's machinery,
     45                so *generally* weak references are not suitable as
     46                receivers, though some use might be found for the
     47                facility whereby a higher-level library passes in
     48                pre-weakrefed receiver references.
     49           
     50        sender -- the sender to which the receiver should respond
     51            Must either be of type Signal, or None to receive events
     52            from any sender.
     53           
     54        weak -- whether to use weak references to the receiver
     55            By default, the module will attempt to use weak
     56            references to the receiver objects.  If this parameter
     57            is false, then strong references will be used.
     58   
     59        returns None
     60        """
     61        allowed_args, _, kw_var, _ = inspect.getargspec(receiver)
     62        accepts_kwargs = kw_var is not None
     63       
     64        receiver_key = (id(sender), id(receiver))
     65       
     66        if weak:
     67            receiver = saferef.safeRef(receiver)
     68       
     69        self._receivers[receiver_key] = (receiver, set(allowed_args), accepts_kwargs)
     70       
     71    def disconnect(self, receiver, sender=None, weak=True):
     72        """Disconnect receiver from sender for signal
     73   
     74        receiver -- the registered receiver to disconnect
     75        sender -- the registered sender to disconnect, or None
     76                  if the receiver should be disconnected from all.
     77        weak -- deprecated and ignored.
     78   
     79        disconnect reverses the process of connect.
     80   
     81        Note:
     82            Using disconnect is not required to cleanup
     83            routing when an object is deleted, the framework
     84            will skip routes for deleted objects
     85            automatically.  It's only necessary to disconnect
     86            if you want to stop routing to a live object.
     87           
     88        returns None
     89        """
     90        receiver_key = (id(sender), id(receiver))
     91        try:
     92            del self._receivers[receiver_key]
     93        except KeyError:
     94            return False
     95   
     96    def send(self, sender, **named):
     97        """
     98        Send signal from sender to all connected receivers.
     99       
     100        sender -- the sender of the signal
     101            Can be any python object (normally one registered with
     102            a connect if you actually want something to occur).
     103   
     104        named -- named arguments which will be passed to receivers.
     105            These arguments must be a subset of the argument names
     106            defined in providing_args, or a DispatcherTypeError will be
     107            raised.
     108   
     109        Return a list of tuple pairs [(receiver, response), ... ].
     110
     111        An exception from any receiver halts dispatch and propagates
     112        back through send.
     113        """
     114        responses = []
     115        if not self._receivers:
     116            return responses
     117
     118        self._enforce_send_api(named) #FIXME: 20% speedup in send if we take this out
     119        for receiver_key, receiver_info in self.get_live_receivers(sender):
     120            receiver, allowed_args, accepts_kwargs = receiver_info
     121
     122            allowed = named.copy()
     123            allowed['sender'] = sender
     124                       
     125            if not accepts_kwargs:
     126                for arg in allowed.keys():
     127                    if arg not in allowed_args:
     128                        del allowed[arg]
     129
     130            responses.append((receiver, receiver(**allowed)))
     131        return responses
     132       
     133    def get_live_receivers(self, sender):
     134        """
     135        Returns an iterable over all live receivers for the signal.
     136        """
     137        listening_to_all = id(None)
     138        sending_from = id(sender)
     139        for ((listen_to_id, receiver_id), receiver_info) in self._receivers.items():
     140            if listen_to_id != listening_to_all and sending_from != listen_to_id:
     141                continue
     142            receiver_, allowed_args, accepts_kwargs = receiver_info
     143            if not self._receiver_is_live(receiver_):
     144                del self._receivers[(listen_to_id, receiver_id)]
     145                continue
     146            yield (sending_from, receiver_id), (self._get_receiver(receiver_), allowed_args, accepts_kwargs)
     147           
     148
     149    def _get_receiver(self, receiver):
     150        if isinstance(receiver, WEAKREF_TYPES):
     151            return receiver()
     152        else:
     153            return receiver
     154       
     155    def _receiver_is_live(self, receiver):
     156        return bool(self._get_receiver(receiver) is not None)
     157
     158    def _enforce_send_api(self, sent_names):
     159        """
     160        Ensures that send calls are made with the parameters declared
     161          in Signal construction.
     162        """
     163        sent_names_set = set(sent_names.keys())
     164
     165        if sent_names_set != self.providing_args:
     166            pluralize = lambda qty: qty != 1 and 's' or ''
     167            flatten = lambda s: ",".join([str(item) for item in s])
     168
     169            extras = sent_names_set - self.providing_args
     170            missing = self.providing_args - sent_names_set
     171           
     172            message = "Signal.send"
     173            if extras:
     174                message += " got unexpected argment%s: %s" % (pluralize(len(extras)), flatten(extras))
     175            if missing:
     176                if extras:
     177                    message += " and"
     178                message += " missing expected argment%s: %s" % (pluralize(len(missing)), flatten(missing))
     179           
     180            raise errors.DispatcherTypeError, message
     181
     182
     183def connect(receiver, signal, sender=None, weak=True):
     184    """For backward compatibility only. See Signal.connect()
     185    """
     186    return signal.connect(receiver, sender, weak)
     187
     188def disconnect(receiver, signal, sender=None, weak=True):
     189    """For backward compatibility only. See Signal.disconnect()
     190    """
     191    signal.disconnect(receiver, sender, weak)
     192
     193def send(signal, sender=None, **named):
     194    """For backward compatibility only. See Signal.send()
     195    """
     196    return signal.send(sender=sender, **named)
     197
     198def sendExact(signal, sender, **named ):
     199    """This function is deprecated, as it now has the same
     200    meaning as send.
     201    """
     202    return signal.send(sender=sender, **named)
     203 No newline at end of file
  • django/dispatch/robustapply.py

    Property changes on: django/dispatch/dispatcher_weak_dict.py
    ___________________________________________________________________
    Name: svn:executable
       + *
    
     
    1 """Robust apply mechanism
    2 
    3 Provides a function "call", which can sort out
    4 what arguments a given callable object can take,
    5 and subset the given arguments to match only
    6 those which are acceptable.
    7 """
    8 
    9 def function( receiver ):
    10     """Get function-like callable object for given receiver
    11 
    12     returns (function_or_method, codeObject, fromMethod)
    13 
    14     If fromMethod is true, then the callable already
    15     has its first argument bound
    16     """
    17     if hasattr(receiver, '__call__'):
    18         # receiver is a class instance; assume it is callable.
    19         # Reassign receiver to the actual method that will be called.
    20         if hasattr( receiver.__call__, 'im_func') or hasattr( receiver.__call__, 'im_code'):
    21             receiver = receiver.__call__
    22     if hasattr( receiver, 'im_func' ):
    23         # an instance-method...
    24         return receiver, receiver.im_func.func_code, 1
    25     elif not hasattr( receiver, 'func_code'):
    26         raise ValueError('unknown reciever type %s %s'%(receiver, type(receiver)))
    27     return receiver, receiver.func_code, 0
    28 
    29 def robustApply(receiver, *arguments, **named):
    30     """Call receiver with arguments and an appropriate subset of named
    31     """
    32     receiver, codeObject, startIndex = function( receiver )
    33     acceptable = codeObject.co_varnames[startIndex+len(arguments):codeObject.co_argcount]
    34     for name in codeObject.co_varnames[startIndex:startIndex+len(arguments)]:
    35         if named.has_key( name ):
    36             raise TypeError(
    37                 """Argument %r specified both positionally and as a keyword for calling %r"""% (
    38                     name, receiver,
    39                 )
    40             )
    41     if not (codeObject.co_flags & 8):
    42         # fc does not have a **kwds type parameter, therefore
    43         # remove unacceptable arguments.
    44         for arg in named.keys():
    45             if arg not in acceptable:
    46                 del named[arg]
    47     return receiver(*arguments, **named)
  • django/dispatch/dispatcher_strong_dict.py

     
     1import inspect
     2
     3try:
     4    set
     5except NameError:
     6    from sets import Set as set     # For Python 2.3
     7
     8from django.dispatch import errors
     9
     10class Signal(object):
     11    """Base class for all signals
     12   
     13    Internal attributes:
     14        receivers -- { receiver_key (id) : (receiver,
     15                                           acceptable_named_args,
     16                                           accepts_kwargs) }
     17    """
     18   
     19    def __init__(self, providing_args=[]):
     20        """providing_args -- A list of the arguments this signal can pass along in
     21                       a send() call.
     22        """
     23        self._receivers = {}
     24        self.providing_args = set(providing_args)
     25   
     26    def connect(self, receiver, sender=None):
     27        """Connect receiver to sender for signal
     28   
     29        receiver -- a function or an instance method which is to
     30            receive messages/signals/events.  Receivers must be
     31            hashable objects.
     32   
     33            Receivers must be able to accept the named arguments
     34            declared in providing_args.
     35   
     36        sender -- the sender to which the receiver should respond
     37            Must either be of type Signal, or None to receive events
     38            from any sender.
     39           
     40        returns None
     41        """
     42        allowed_args, _, kw_var, _ = inspect.getargspec(receiver)
     43        accepts_kwargs = kw_var is not None
     44       
     45        receiver_key = (id(sender), id(receiver))
     46       
     47        self._receivers[receiver_key] = (receiver, set(allowed_args), accepts_kwargs)
     48       
     49    def disconnect(self, receiver, sender=None):
     50        """Disconnect receiver from sender for signal
     51   
     52        receiver -- the registered receiver to disconnect
     53        sender -- the registered sender to disconnect, or None
     54                  if the receiver should be disconnected from all.
     55   
     56        disconnect reverses the process of connect.
     57   
     58        Note:
     59            Using disconnect is not required to cleanup
     60            routing when an object is deleted, the framework
     61            will skip routes for deleted objects
     62            automatically.  It's only necessary to disconnect
     63            if you want to stop routing to a live object.
     64           
     65        returns None
     66        """
     67        receiver_key = (id(sender), id(receiver))
     68        try:
     69            del self._receivers[receiver_key]
     70        except KeyError:
     71            return False
     72   
     73    def send(self, sender, **named):
     74        """
     75        Send signal from sender to all connected receivers.
     76       
     77        sender -- the sender of the signal
     78            Can be any python object (normally one registered with
     79            a connect if you actually want something to occur).
     80   
     81        named -- named arguments which will be passed to receivers.
     82            These arguments must be a subset of the argument names
     83            defined in providing_args, or a DispatcherTypeError will be
     84            raised.
     85   
     86        Return a list of tuple pairs [(receiver, response), ... ].
     87
     88        An exception from any receiver halts dispatch and propagates
     89        back through send.
     90        """
     91        responses = []
     92        if len(self._receivers) == 0:
     93            return responses
     94
     95        self._enforce_send_api(named) #FIXME: 20% speedup in send if we take this out
     96
     97        listening_to_all = id(None)
     98        sending_from = id(sender)
     99       
     100        for ((listen_to_id, receiver_id), receiver_info) in self._receivers.items():
     101            if listen_to_id != listening_to_all and sending_from != listen_to_id:
     102                continue
     103
     104            receiver, allowed_args, accepts_kwargs = receiver_info
     105
     106            allowed = named.copy()
     107            allowed['sender'] = sender
     108                       
     109            if not accepts_kwargs:
     110                for arg in allowed.keys():
     111                    if arg not in allowed_args:
     112                        del allowed[arg]
     113
     114            responses.append((receiver, receiver(**allowed)))
     115        return responses
     116       
     117    def _enforce_send_api(self, sent_names):
     118        """
     119        Ensures that send calls are made with the parameters declared
     120          in Signal construction.
     121        """
     122        sent_names_set = set(sent_names.keys())
     123
     124        if sent_names_set != self.providing_args:
     125            pluralize = lambda qty: qty != 1 and 's' or ''
     126            flatten = lambda s: ",".join([str(item) for item in s])
     127
     128            extras = sent_names_set - self.providing_args
     129            missing = self.providing_args - sent_names_set
     130           
     131            message = "Signal.send"
     132            if extras:
     133                message += " got unexpected argment%s: %s" % (pluralize(len(extras)), flatten(extras))
     134            if missing:
     135                if extras:
     136                    message += " and"
     137                message += " missing expected argment%s: %s" % (pluralize(len(missing)), flatten(missing))
     138           
     139            raise errors.DispatcherTypeError, message
     140
     141
     142def connect(receiver, signal, sender=None):
     143    """For backward compatibility only. See Signal.connect()
     144    """
     145    return signal.connect(receiver, sender)
     146
     147def disconnect(receiver, signal, sender=None):
     148    """For backward compatibility only. See Signal.disconnect()
     149    """
     150    signal.disconnect(receiver, sender)
     151
     152def send(signal, sender=None, **named):
     153    """For backward compatibility only. See Signal.send()
     154    """
     155    return signal.send(sender=sender, **named)
     156
     157def sendExact(signal, sender, **named):
     158    """This function is deprecated, as it now has the same
     159    meaning as send.
     160    """
     161    return signal.send(sender=sender, **named)
     162 No newline at end of file
  • django/dispatch/dispatcher.py

     
    1 """Multiple-producer-multiple-consumer signal-dispatching
     1import inspect
    22
    3 dispatcher is the core of the PyDispatcher system,
    4 providing the primary API and the core logic for the
    5 system.
     3try:
     4    set
     5except NameError:
     6    from sets import Set as set     # For Python 2.3
    67
    7 Module attributes of note:
     8from django.dispatch import errors
    89
    9     Any -- Singleton used to signal either "Any Sender" or
    10         "Any Signal".  See documentation of the _Any class.
    11     Anonymous -- Singleton used to signal "Anonymous Sender"
    12         See documentation of the _Anonymous class.
     10pluralize = lambda qty: qty != 1 and 's' or ''
     11flatten = lambda s: ",".join([str(item) for item in s])
    1312
    14 Internal attributes:
    15     WEAKREF_TYPES -- tuple of types/classes which represent
    16         weak references to receivers, and thus must be de-
    17         referenced on retrieval to retrieve the callable
    18         object
    19     connections -- { senderkey (id) : { signal : [receivers...]}}
    20     senders -- { senderkey (id) : weakref(sender) }
    21         used for cleaning up sender references on sender
    22         deletion
    23     sendersBack -- { receiverkey (id) : [senderkey (id)...] }
    24         used for cleaning up receiver references on receiver
    25         deletion, (considerably speeds up the cleanup process
    26         vs. the original code.)
    27 """
    28 import types, weakref
    29 from django.dispatch import saferef, robustapply, errors
    30 
    31 __author__ = "Patrick K. O'Brien <pobrien@orbtech.com>"
    32 __cvsid__ = "$Id: dispatcher.py,v 1.9 2005/09/17 04:55:57 mcfletch Exp $"
    33 __version__ = "$Revision: 1.9 $"[11:-2]
    34 
    35 
    36 class _Parameter:
    37     """Used to represent default parameter values."""
    38     def __repr__(self):
    39         return self.__class__.__name__
    40 
    41 class _Any(_Parameter):
    42     """Singleton used to signal either "Any Sender" or "Any Signal"
    43 
    44     The Any object can be used with connect, disconnect,
    45     send, or sendExact to signal that the parameter given
    46     Any should react to all senders/signals, not just
    47     a particular sender/signal.
     13class Signal(object):
    4814    """
    49 Any = _Any()
    50 
    51 class _Anonymous(_Parameter):
    52     """Singleton used to signal "Anonymous Sender"
    53 
    54     The Anonymous object is used to signal that the sender
    55     of a message is not specified (as distinct from being
    56     "any sender").  Registering callbacks for Anonymous
    57     will only receive messages sent without senders.  Sending
    58     with anonymous will only send messages to those receivers
    59     registered for Any or Anonymous.
    60 
    61     Note:
    62         The default sender for connect is Any, while the
    63         default sender for send is Anonymous.  This has
    64         the effect that if you do not specify any senders
    65         in either function then all messages are routed
    66         as though there was a single sender (Anonymous)
    67         being used everywhere.
     15    Internal attributes:
     16        _receivers -- { receiver_key (id) : (sender_id,
     17                                           receiver,
     18                                           acceptable_named_args,
     19                                           accepts_kwargs) }
    6820    """
    69 Anonymous = _Anonymous()
    70 
    71 WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref)
    72 
    73 connections = {}
    74 senders = {}
    75 sendersBack = {}
    76 
    77 
    78 def connect(receiver, signal=Any, sender=Any, weak=True):
    79     """Connect receiver to sender for signal
    80 
    81     receiver -- a callable Python object which is to receive
    82         messages/signals/events.  Receivers must be hashable
    83         objects.
    84 
    85         if weak is True, then receiver must be weak-referencable
    86         (more precisely saferef.safeRef() must be able to create
    87         a reference to the receiver).
    8821   
    89         Receivers are fairly flexible in their specification,
    90         as the machinery in the robustApply module takes care
    91         of most of the details regarding figuring out appropriate
    92         subsets of the sent arguments to apply to a given
    93         receiver.
    94 
    95         Note:
    96             if receiver is itself a weak reference (a callable),
    97             it will be de-referenced by the system's machinery,
    98             so *generally* weak references are not suitable as
    99             receivers, though some use might be found for the
    100             facility whereby a higher-level library passes in
    101             pre-weakrefed receiver references.
    102 
    103     signal -- the signal to which the receiver should respond
     22    def __init__(self, providing_args=[]):
     23        """providing_args -- A list of the arguments this signal will pass along in
     24                       a send() call.
     25        """
     26        self._receivers = []
     27        self.providing_args = set(providing_args)
    10428   
    105         if Any, receiver will receive any signal from the
    106         indicated sender (which might also be Any, but is not
    107         necessarily Any).
    108        
    109         Otherwise must be a hashable Python object other than
    110         None (DispatcherError raised on None).
    111        
    112     sender -- the sender to which the receiver should respond
     29    def connect(self, receiver, sender=None):
     30        """Connect receiver to sender for signal
    11331   
    114         if Any, receiver will receive the indicated signals
    115         from any sender.
     32        receiver -- a function or an instance method which is to
     33            receive messages/signals/events.  Receivers must be
     34            hashable objects.
     35   
     36            Receivers must be able to accept the named arguments
     37            declared in providing_args.
     38   
     39        sender -- the sender to which the receiver should respond
     40            Must either be of type Signal, or None to receive events
     41            from any sender.
     42           
     43        returns None
     44        """
     45        allowed_args, _, kw_var, _ = inspect.getargspec(receiver)
     46        allowed_args = set(allowed_args)
     47        accepts_kwargs = kw_var is not None
    11648       
    117         if Anonymous, receiver will only receive indicated
    118         signals from send/sendExact which do not specify a
    119         sender, or specify Anonymous explicitly as the sender.
    120 
    121         Otherwise can be any python object.
     49        if not accepts_kwargs:
     50            self._enforce_receive_api(receiver, allowed_args)
    12251       
    123     weak -- whether to use weak references to the receiver
    124         By default, the module will attempt to use weak
    125         references to the receiver objects.  If this parameter
    126         is false, then strong references will be used.
     52        sender_id = id(sender)
     53        receiver_id = id(receiver)
    12754
    128     returns None, may raise DispatcherTypeError
    129     """
    130     if signal is None:
    131         raise errors.DispatcherTypeError(
    132             'Signal cannot be None (receiver=%r sender=%r)'%( receiver,sender)
    133         )
    134     if weak:
    135         receiver = saferef.safeRef(receiver, onDelete=_removeReceiver)
    136     senderkey = id(sender)
    137 
    138     signals = connections.setdefault(senderkey, {})
    139 
    140     # Keep track of senders for cleanup.
    141     # Is Anonymous something we want to clean up?
    142     if sender not in (None, Anonymous, Any):
    143         def remove(object, senderkey=senderkey):
    144             _removeSender(senderkey=senderkey)
    145         # Skip objects that can not be weakly referenced, which means
    146         # they won't be automatically cleaned up, but that's too bad.
    147         try:
    148             weakSender = weakref.ref(sender, remove)
    149             senders[senderkey] = weakSender
    150         except:
    151             pass
     55        for existing_sender_id, existing_receiver, _, _ in self._receivers:
     56            if self._receiver_match(existing_sender_id, id(existing_receiver), sender_id, receiver_id):
     57                return
     58        receiver_info = (sender_id, receiver, allowed_args, accepts_kwargs)
     59        self._receivers.append(receiver_info)
    15260       
    153     receiverID = id(receiver)
    154     # get current set, remove any current references to
    155     # this receiver in the set, including back-references
    156     if signals.has_key(signal):
    157         receivers = signals[signal]
    158         _removeOldBackRefs(senderkey, signal, receiver, receivers)
    159     else:
    160         receivers = signals[signal] = []
    161     try:
    162         current = sendersBack.get( receiverID )
    163         if current is None:
    164             sendersBack[ receiverID ] = current = []
    165         if senderkey not in current:
    166             current.append(senderkey)
    167     except:
    168         pass
     61    def disconnect(self, receiver, sender=None):
     62        """Disconnect receiver from sender for signal
     63   
     64        receiver -- the registered receiver to disconnect
     65        sender -- the registered sender to disconnect, or None
     66                  if the receiver should be disconnected from all.
     67   
     68        disconnect reverses the process of connect.
     69   
     70        Note:
     71            Using disconnect is not required to cleanup
     72            routing when an object is deleted, the framework
     73            will skip routes for deleted objects
     74            automatically.  It's only necessary to disconnect
     75            if you want to stop routing to a live object.
     76           
     77        returns None
     78        """
     79        requested_sender_id = id(sender)
     80        requested_receiver_id = id(receiver)
    16981
    170     receivers.append(receiver)
    171 
    172 
    173 
    174 def disconnect(receiver, signal=Any, sender=Any, weak=True):
    175     """Disconnect receiver from sender for signal
    176 
    177     receiver -- the registered receiver to disconnect
    178     signal -- the registered signal to disconnect
    179     sender -- the registered sender to disconnect
    180     weak -- the weakref state to disconnect
    181 
    182     disconnect reverses the process of connect,
    183     the semantics for the individual elements are
    184     logically equivalent to a tuple of
    185     (receiver, signal, sender, weak) used as a key
    186     to be deleted from the internal routing tables.
    187     (The actual process is slightly more complex
    188     but the semantics are basically the same).
    189 
    190     Note:
    191         Using disconnect is not required to cleanup
    192         routing when an object is deleted, the framework
    193         will remove routes for deleted objects
    194         automatically.  It's only necessary to disconnect
    195         if you want to stop routing to a live object.
     82        for i, (sender_id, receiver, _, _) in enumerate(self._receivers):
     83            if self._receiver_match(requested_sender_id,
     84                                    requested_receiver_id,
     85                                    sender_id,
     86                                    id(receiver)):
     87                self._receivers.pop(i)
     88                return True
     89        return False
     90   
     91    def _receiver_match(self, requested_sender_id, requested_receiver_id,
     92            sender_id, receiver_id):
     93        return bool(requested_sender_id == sender_id and requested_receiver_id == receiver_id)
     94   
     95    def send(self, sender, **named):
     96        """
     97        Send signal from sender to all connected receivers.
    19698       
    197     returns None, may raise DispatcherTypeError or
    198         DispatcherKeyError
    199     """
    200     if signal is None:
    201         raise errors.DispatcherTypeError(
    202             'Signal cannot be None (receiver=%r sender=%r)'%( receiver,sender)
    203         )
    204     if weak: receiver = saferef.safeRef(receiver)
    205     senderkey = id(sender)
    206     try:
    207         signals = connections[senderkey]
    208         receivers = signals[signal]
    209     except KeyError:
    210         raise errors.DispatcherKeyError(
    211             """No receivers found for signal %r from sender %r""" %(
    212                 signal,
    213                 sender
    214             )
    215         )
    216     try:
    217         # also removes from receivers
    218         _removeOldBackRefs(senderkey, signal, receiver, receivers)
    219     except ValueError:
    220         raise errors.DispatcherKeyError(
    221             """No connection to receiver %s for signal %s from sender %s""" %(
    222                 receiver,
    223                 signal,
    224                 sender
    225             )
    226         )
    227     _cleanupConnections(senderkey, signal)
    228 
    229 def getReceivers( sender = Any, signal = Any ):
    230     """Get list of receivers from global tables
    231 
    232     This utility function allows you to retrieve the
    233     raw list of receivers from the connections table
    234     for the given sender and signal pair.
    235 
    236     Note:
    237         there is no guarantee that this is the actual list
    238         stored in the connections table, so the value
    239         should be treated as a simple iterable/truth value
    240         rather than, for instance a list to which you
    241         might append new records.
    242 
    243     Normally you would use liveReceivers( getReceivers( ...))
    244     to retrieve the actual receiver objects as an iterable
    245     object.
    246     """
    247     existing = connections.get(id(sender))
    248     if existing is not None:
    249         return existing.get(signal, [])
    250     return []
    251 
    252 def liveReceivers(receivers):
    253     """Filter sequence of receivers to get resolved, live receivers
    254 
    255     This is a generator which will iterate over
    256     the passed sequence, checking for weak references
    257     and resolving them, then returning all live
    258     receivers.
    259     """
    260     for receiver in receivers:
    261         if isinstance( receiver, WEAKREF_TYPES):
    262             # Dereference the weak reference.
    263             receiver = receiver()
    264             if receiver is not None:
    265                 yield receiver
    266         else:
    267             yield receiver
    268 
    269 
    270 
    271 def getAllReceivers( sender = Any, signal = Any ):
    272     """Get list of all receivers from global tables
    273 
    274     This gets all dereferenced receivers which should receive
    275     the given signal from sender, each receiver should
    276     be produced only once by the resulting generator
    277     """
    278     receivers = {}
    279     # Get receivers that receive *this* signal from *this* sender.
    280     # Add receivers that receive *any* signal from *this* sender.
    281     # Add receivers that receive *this* signal from *any* sender.
    282     # Add receivers that receive *any* signal from *any* sender.
    283     l = []
    284     i = id(sender)
    285     if i in connections:
    286         sender_receivers = connections[i]
    287         if signal in sender_receivers:
    288             l.extend(sender_receivers[signal])
    289         if signal is not Any and Any in sender_receivers:
    290             l.extend(sender_receivers[Any])
    291 
    292     if sender is not Any:
    293         i = id(Any)
    294         if i in connections:
    295             sender_receivers = connections[i]
    296             if sender_receivers is not None:
    297                 if signal in sender_receivers:
    298                     l.extend(sender_receivers[signal])
    299                 if signal is not Any and Any in sender_receivers:
    300                     l.extend(sender_receivers[Any])
    301 
    302     for receiver in l:
    303         try:
    304             if not receiver in receivers:
    305                 if isinstance(receiver, WEAKREF_TYPES):
    306                     receiver = receiver()
    307                     # this should only (rough guess) be possible if somehow, deref'ing
    308                     # triggered a wipe.
    309                     if receiver is None:
    310                         continue
    311                 receivers[receiver] = 1
    312                 yield receiver
    313         except TypeError:
    314             # dead weakrefs raise TypeError on hash...
    315             pass
    316 
    317 def send(signal=Any, sender=Anonymous, *arguments, **named):
    318     """Send signal from sender to all connected receivers.
     99        sender -- the sender of the signal
     100            Can be any python object (normally one registered with
     101            a connect if you actually want something to occur).
    319102   
    320     signal -- (hashable) signal value, see connect for details
    321 
    322     sender -- the sender of the signal
     103        named -- named arguments which will be passed to receivers.
     104            These arguments must be a subset of the argument names
     105            defined in providing_args, or a DispatcherTypeError will be
     106            raised.
    323107   
    324         if Any, only receivers registered for Any will receive
    325         the message.
     108        Return a list of tuple pairs [(receiver, response), ... ].
    326109
    327         if Anonymous, only receivers registered to receive
    328         messages from Anonymous or Any will receive the message
     110        An exception from any receiver halts dispatch and propagates
     111        back through send.
     112        """
     113        responses = []
     114        if len(self._receivers) == 0:
     115            return responses
    329116
    330         Otherwise can be any python object (normally one
    331         registered with a connect if you actually want
    332         something to occur).
     117        self._enforce_send_api(named) #FIXME: 2microsec (~30% for 1 receiver) speedup if we take this out
    333118
    334     arguments -- positional arguments which will be passed to
    335         *all* receivers. Note that this may raise TypeErrors
    336         if the receivers do not allow the particular arguments.
    337         Note also that arguments are applied before named
    338         arguments, so they should be used with care.
     119        listening_to_all = id(None)
     120        sending_from = id(sender)
    339121
    340     named -- named arguments which will be filtered according
    341         to the parameters of the receivers to only provide those
    342         acceptable to the receiver.
     122        named['sender'] = sender
    343123
    344     Return a list of tuple pairs [(receiver, response), ... ]
     124        for (listen_to_id, receiver, allowed_args, accepts_kwargs) in self._receivers:
     125            #a receiver can either listen to all senders or a specific sender.
     126            if listen_to_id != listening_to_all and sending_from != listen_to_id:
     127                continue
    345128
    346     if any receiver raises an error, the error propagates back
    347     through send, terminating the dispatch loop, so it is quite
    348     possible to not have all receivers called if a raises an
    349     error.
    350     """
    351     # Call each receiver with whatever arguments it can accept.
    352     # Return a list of tuple pairs [(receiver, response), ... ].
    353     responses = []
    354     for receiver in getAllReceivers(sender, signal):
    355         response = robustapply.robustApply(
    356             receiver,
    357             signal=signal,
    358             sender=sender,
    359             *arguments,
    360             **named
    361         )
    362         responses.append((receiver, response))
    363     return responses
     129            #debate:
     130            ##surprisingly slow
     131            #if accepts_kwargs:
     132            #    call_args = named
     133            #else:
     134            #    call_args = dict([pair for pair in named.items() if pair[0] in allowed_args])
    364135
     136            #slightly faster
     137            #call_args = named.copy()
     138            #           
     139            #if not accepts_kwargs:
     140            #    for arg in call_args.keys():
     141            #        if arg not in allowed_args:
     142            #            del call_args[arg]
    365143
    366 def sendExact( signal=Any, sender=Anonymous, *arguments, **named ):
    367     """Send signal only to those receivers registered for exact message
     144            #fastest (so far)
     145            if accepts_kwargs:
     146                call_args = named
     147            else:
     148                call_args = {}
     149                for arg in allowed_args:
     150                    call_args[arg] = named[arg]
    368151
    369     sendExact allows for avoiding Any/Anonymous registered
    370     handlers, sending only to those receivers explicitly
    371     registered for a particular signal on a particular
    372     sender.
    373     """
    374     responses = []
    375     for receiver in liveReceivers(getReceivers(sender, signal)):
    376         response = robustapply.robustApply(
    377             receiver,
    378             signal=signal,
    379             sender=sender,
    380             *arguments,
    381             **named
    382         )
    383         responses.append((receiver, response))
    384     return responses
    385    
     152            #call_args = named
     153            responses.append((receiver, receiver(**call_args)))
     154        return responses
     155       
     156    def _enforce_send_api(self, call_names):
     157        """
     158        Ensures that send calls are made with the parameters declared
     159          in Signal construction.
     160        """
     161        call_names_set = set(call_names.keys())
    386162
    387 def _removeReceiver(receiver):
    388     """Remove receiver from connections."""
    389     if not sendersBack:
    390         # During module cleanup the mapping will be replaced with None
    391         return False
    392     backKey = id(receiver)
    393     for senderkey in sendersBack.get(backKey,()):
    394         try:
    395             signals = connections[senderkey].keys()
    396         except KeyError,err:
    397             pass
    398         else:
    399             for signal in signals:
    400                 try:
    401                     receivers = connections[senderkey][signal]
    402                 except KeyError:
    403                     pass
    404                 else:
    405                     try:
    406                         receivers.remove( receiver )
    407                     except Exception, err:
    408                         pass
    409                 _cleanupConnections(senderkey, signal)
    410     try:
    411         del sendersBack[ backKey ]
    412     except KeyError:
    413         pass
     163        if call_names_set != self.providing_args:
     164            extras = call_names_set - self.providing_args
     165            missing = self.providing_args - call_names_set
    414166           
    415 def _cleanupConnections(senderkey, signal):
    416     """Delete any empty signals for senderkey. Delete senderkey if empty."""
    417     try:
    418         receivers = connections[senderkey][signal]
    419     except:
    420         pass
    421     else:
    422         if not receivers:
    423             # No more connected receivers. Therefore, remove the signal.
    424             try:
    425                 signals = connections[senderkey]
    426             except KeyError:
    427                 pass
    428             else:
    429                 del signals[signal]
    430                 if not signals:
    431                     # No more signal connections. Therefore, remove the sender.
    432                     _removeSender(senderkey)
     167            message = "Signal.send"
     168            if extras:
     169                message += " received unexpected argment%s: %s" % (pluralize(len(extras)), flatten(extras))
     170            if missing:
     171                if extras:
     172                    message += " and"
     173                message += " missing expected argment%s: %s" % (pluralize(len(missing)), flatten(missing))
     174           
     175            raise errors.DispatcherTypeError, message
    433176
    434 def _removeSender(senderkey):
    435     """Remove senderkey from connections."""
    436     _removeBackrefs(senderkey)
     177    def _enforce_receive_api(self, receiver, required_args):
     178        """
     179        Ensures that connected receivers don't expect unprovided arguments.
     180        """
     181        extra_args = required_args - self.providing_args
     182        extra_args -= set(['sender', 'self'])
    437183
    438     connections.pop(senderkey, None)
    439     senders.pop(senderkey, None)
     184        if extra_args:
     185            message = "Signal.connect called with receiver expecting unprovided argment%s: %s" % (pluralize(len(extra_args)), flatten(extra_args))
     186            raise errors.DispatcherTypeError, message
     187       
    440188
     189def connect(receiver, signal, sender=None):
     190    """For backward compatibility only. See Signal.connect()
     191    """
     192    return signal.connect(receiver, sender)
    441193
    442 def _removeBackrefs( senderkey):
    443     """Remove all back-references to this senderkey"""
    444     for receiver_list in connections.pop(senderkey, {}).values():
    445         for receiver in receiver_list:
    446             _killBackref( receiver, senderkey )
     194def disconnect(receiver, signal, sender=None):
     195    """For backward compatibility only. See Signal.disconnect()
     196    """
     197    signal.disconnect(receiver, sender)
    447198
     199def send(signal, sender=None, **named):
     200    """For backward compatibility only. See Signal.send()
     201    """
     202    return signal.send(sender=sender, **named)
    448203
    449 def _removeOldBackRefs(senderkey, signal, receiver, receivers):
    450     """Kill old sendersBack references from receiver
    451 
    452     This guards against multiple registration of the same
    453     receiver for a given signal and sender leaking memory
    454     as old back reference records build up.
    455 
    456     Also removes old receiver instance from receivers
     204def sendExact(signal, sender, **named):
     205    """This function is deprecated, as it now has the same
     206    meaning as send.
    457207    """
    458     try:
    459         index = receivers.index(receiver)
    460         # need to scan back references here and remove senderkey
    461     except ValueError:
    462         return False
    463     else:
    464         oldReceiver = receivers[index]
    465         del receivers[index]
    466         found = 0
    467         signals = connections.get(signal)
    468         if signals is not None:
    469             for sig,recs in connections.get(signal,{}).iteritems():
    470                 if sig != signal:
    471                     for rec in recs:
    472                         if rec is oldReceiver:
    473                             found = 1
    474                             break
    475         if not found:
    476             _killBackref( oldReceiver, senderkey )
    477             return True
    478         return False
    479        
    480        
    481 def _killBackref( receiver, senderkey ):
    482     """Do the actual removal of back reference from receiver to senderkey"""
    483     receiverkey = id(receiver)
    484     receivers_list = sendersBack.get( receiverkey, () )
    485     while senderkey in receivers_list:
    486         try:
    487             receivers_list.remove( senderkey )
    488         except:
    489             break
    490     if not receivers_list:
    491         try:
    492             del sendersBack[ receiverkey ]
    493         except KeyError:
    494             pass
    495     return True
     208    return signal.send(sender=sender, **named)
     209 No newline at end of file
  • tests/regressiontests/dispatch/tests.py

     
     1"""
     2>>> from django.dispatch import dispatcher
     3>>> specific_sender = object()
     4>>> different_sender = object()
     5>>> expect_one = dispatcher.Signal(providing_args=['arg0'])
     6>>> expect_three = dispatcher.Signal(providing_args=['arg0', 'arg1', 'arg2'])
     7>>> unheard = dispatcher.Signal(providing_args=[])
     8>>>
     9>>> def handle_any(**kwargs):
     10...     return 'handle_any'
     11>>> def handle_mismatch(spam):
     12...     return 'handle_mismatch'
     13>>> def handle_three(arg0,arg1,arg2):
     14...     return 'handle_three: ', arg0, arg1, arg2
     15>>> def handle_two(arg0,arg1):
     16...     return 'handle_two: ', arg0, arg1
     17>>> def handle_one(arg0):
     18...     return 'handle_one: ', arg0
     19>>> def handle_none():
     20...     return 'handle_none'
     21
     22>>> dispatcher.connect(handle_any, expect_one)
     23>>> dispatcher.send(expect_one, arg0=1)
     24[(<function handle_any at ...>, 'handle_any')]
     25
     26>>> #connect is idempotent
     27>>> dispatcher.connect(handle_any, expect_one)
     28>>> dispatcher.send(expect_one, arg0=1)
     29[(<function handle_any at ...>, 'handle_any')]
     30
     31>>> #disconnect of unconnected silently passes
     32>>> dispatcher.disconnect(handle_none, expect_one)
     33
     34>>> #but doesn't disconnect existing
     35>>> dispatcher.send(expect_one, arg0=1)
     36[(<function handle_any at ...>, 'handle_any')]
     37
     38>>> #disconnect is specific
     39>>> dispatcher.disconnect(handle_any, expect_one)
     40>>> dispatcher.send(expect_one, arg0=1)
     41[]
     42
     43>>> #listening to a specific sender ignores from other senders
     44>>> dispatcher.connect(handle_any, expect_one, specific_sender)
     45>>> dispatcher.connect(handle_any, expect_one)
     46>>> dispatcher.send(expect_one, arg0=1, sender=specific_sender)
     47[(<function handle_any at ...>, 'handle_any'), (<function handle_any at ...>, 'handle_any')]
     48>>> dispatcher.send(expect_one, arg0=1, sender=different_sender)
     49[(<function handle_any at ...>, 'handle_any')]
     50>>> dispatcher.send(expect_one, arg0=1)
     51[(<function handle_any at ...>, 'handle_any')]
     52>>> dispatcher.disconnect(handle_any, expect_one)
     53>>> dispatcher.disconnect(handle_any, expect_one, specific_sender)
     54
     55>>> #receivers need not accept all args of a signal
     56>>> dispatcher.connect(handle_none, expect_one)
     57>>> dispatcher.send(expect_one, arg0=1)
     58[(<function handle_none at ...>, 'handle_none')]
     59
     60>>> # but they must not require additional args.
     61>>> dispatcher.connect(handle_mismatch, expect_one)
     62Traceback (most recent call last):
     63...
     64DispatcherTypeError: Signal.connect called with receiver expecting unprovided argment: spam
     65
     66>>> #multiple receivers can accept different permutations of arguments.
     67>>> dispatcher.connect(handle_three, expect_three)
     68>>> dispatcher.connect(handle_two, expect_three)
     69>>> dispatcher.connect(handle_one, expect_three)
     70>>> dispatcher.send(expect_three, arg0=1, arg1=2, arg2=3)
     71[(<function handle_three at ...>, ('handle_three: ', 1, 2, 3)), (<function handle_two at ...>, ('handle_two: ', 1, 2)), (<function handle_one at ...>, ('handle_one: ', 1))]
     72>>> dispatcher.disconnect(handle_three, expect_three)
     73>>> dispatcher.disconnect(handle_two, expect_three)
     74>>> dispatcher.disconnect(handle_one, expect_three)
     75>>> dispatcher.connect(handle_one, expect_three)
     76>>> dispatcher.connect(handle_two, expect_three)
     77>>> dispatcher.connect(handle_three, expect_three)
     78>>> dispatcher.send(expect_three, arg0=1, arg1=2, arg2=3)
     79[(<function handle_one at ...>, ('handle_one: ', 1)), (<function handle_two at ...>, ('handle_two: ', 1, 2)), (<function handle_three at ...>, ('handle_three: ', 1, 2, 3))]
     80>>> #signals can fire with no receivers
     81>>> dispatcher.send(unheard)
     82[]
     83
     84>>> #Signals are first-class. (dispatcher.send is old-style)
     85>>> expect_three.send(sender=None, **{'arg0':1, 'arg1':2, 'arg2':3})
     86[(<function handle_one at ...>, ('handle_one: ', 1)), (<function handle_two at ...>, ('handle_two: ', 1, 2)), (<function handle_three at ...>, ('handle_three: ', 1, 2, 3))]
     87
     88>>> #Signals must be fired with only declared arguments.
     89>>> expect_three.send(sender=None, **{'arg0':1, 'arg1':2, 'arg9':9})
     90Traceback (most recent call last):
     91...
     92DispatcherTypeError: Signal.send received unexpected argment: arg9 and missing expected argment: arg2
     93"""
Back to Top