Ticket #6814: faster_signals-2.diff
File faster_signals-2.diff, 53.4 KB (added by , 17 years ago) |
---|
-
django/test/signals.py
1 template_rendered = object() 2 No newline at end of file 1 from django.dispatch.dispatcher import Signal 2 3 template_rendered = Signal(providing_args=["template", "context"]) 4 No newline at end of file -
django/db/models/signals.py
1 class_prepared = object() 1 from django.dispatch.dispatcher import Signal 2 2 3 pre_init= object() 4 post_init = object() 3 class_prepared = Signal() 5 4 6 pre_ save = object()7 post_ save = object()5 pre_init = Signal(providing_args=["args", "kwargs"]) 6 post_init = Signal(providing_args=["instance"]) 8 7 9 pre_ delete = object()10 post_ delete = object()8 pre_save = Signal(providing_args=["instance", "raw"]) 9 post_save = Signal(providing_args=["instance", "raw", "created"]) 11 10 12 post_syncdb = object() 11 pre_delete = Signal(providing_args=["instance"]) 12 post_delete = Signal(providing_args=["instance"]) 13 14 post_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() 1 from django.dispatch.dispatcher import Signal 2 3 request_started = Signal() 4 request_finished = Signal() 5 got_request_exception = Signal(providing_args=["request"]) -
django/dispatch/license.txt
1 PyDispatcher License2 3 Copyright (c) 2001-2003, Patrick K. O'Brien and Contributors4 All rights reserved.5 6 Redistribution and use in source and binary forms, with or without7 modification, are permitted provided that the following conditions8 are met:9 10 Redistributions of source code must retain the above copyright11 notice, this list of conditions and the following disclaimer.12 13 Redistributions in binary form must reproduce the above14 copyright notice, this list of conditions and the following15 disclaimer in the documentation and/or other materials16 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 this20 software without specific prior written permission.21 22 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS23 ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT24 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS25 FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE26 COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT,27 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES28 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR29 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 ADVISED33 OF THE POSSIBILITY OF SUCH DAMAGE.34 -
django/dispatch/saferef.py
3 3 from django.utils.functional import curry 4 4 5 5 def 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. 7 8 8 9 target -- the object to be weakly referenced, if it's a 9 10 bound method reference, will create a BoundMethodWeakref, … … 23 24 onDelete=onDelete 24 25 ) 25 26 return reference 27 # If a weakref is created using 26 28 if callable(onDelete): 27 29 return weakref.ref(target, onDelete) 28 30 else: … … 234 236 return BoundMethodWeakref(target=target, onDelete=onDelete) 235 237 else: 236 238 # 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
3 3 4 4 class DispatcherError(Exception): 5 5 """Base class for all Dispatcher errors""" 6 class DispatcherKeyError(KeyError, DispatcherError):7 """Error raised when unknown (sender,signal) set specified"""8 6 class DispatcherTypeError(TypeError, DispatcherError): 9 7 """Error raised when inappropriate signal-type specified (None)""" 10 8 -
django/dispatch/robust.py
1 """Module implementing error-catching version of send (sendRobust)"""2 from django.dispatch.dispatcher import Any, Anonymous, liveReceivers, getAllReceivers3 from django.dispatch.robustapply import robustApply4 5 def sendRobust(6 signal=Any,7 sender=Anonymous,8 *arguments, **named9 ):10 """Send signal from sender to all connected receivers catching errors11 12 signal -- (hashable) signal value, see connect for details13 14 sender -- the sender of the signal15 16 if Any, only receivers registered for Any will receive17 the message.18 19 if Anonymous, only receivers registered to receive20 messages from Anonymous or Any will receive the message21 22 Otherwise can be any python object (normally one23 registered with a connect if you actually want24 something to occur).25 26 arguments -- positional arguments which will be passed to27 *all* receivers. Note that this may raise TypeErrors28 if the receivers do not allow the particular arguments.29 Note also that arguments are applied before named30 arguments, so they should be used with care.31 32 named -- named arguments which will be filtered according33 to the parameters of the receivers to only provide those34 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 **named52 )53 except Exception, err:54 responses.append((receiver, err))55 else:56 responses.append((receiver, response))57 return responses -
django/dispatch/__init__.py
4 4 __author__ = "Patrick K. O'Brien" 5 5 __license__ = "BSD-style, see license.txt for details" 6 6 7 """ 8 new - weak_dict 9 0 receivers test 1.70392489433 10 1 receiver test 11.3397159576 1.23367714882 11 """ 12 """ 13 new - strong dict 14 0 receivers test 2.07081604004 15 1 receiver test 7.21637010574 1.23387694359 16 10 receivers test 24.1335361004 4.61771893501 17 """ 18 No newline at end of file -
django/dispatch/dispatcher_weak_dict.py
1 import weakref 2 import inspect 3 4 try: 5 set 6 except NameError: 7 from sets import Set as set # For Python 2.3 8 9 from django.dispatch import saferef, errors 10 11 WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref) 12 13 class 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 183 def connect(receiver, signal, sender=None, weak=True): 184 """For backward compatibility only. See Signal.connect() 185 """ 186 return signal.connect(receiver, sender, weak) 187 188 def disconnect(receiver, signal, sender=None, weak=True): 189 """For backward compatibility only. See Signal.disconnect() 190 """ 191 signal.disconnect(receiver, sender, weak) 192 193 def send(signal, sender=None, **named): 194 """For backward compatibility only. See Signal.send() 195 """ 196 return signal.send(sender=sender, **named) 197 198 def 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 mechanism2 3 Provides a function "call", which can sort out4 what arguments a given callable object can take,5 and subset the given arguments to match only6 those which are acceptable.7 """8 9 def function( receiver ):10 """Get function-like callable object for given receiver11 12 returns (function_or_method, codeObject, fromMethod)13 14 If fromMethod is true, then the callable already15 has its first argument bound16 """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, 125 elif not hasattr( receiver, 'func_code'):26 raise ValueError('unknown reciever type %s %s'%(receiver, type(receiver)))27 return receiver, receiver.func_code, 028 29 def robustApply(receiver, *arguments, **named):30 """Call receiver with arguments and an appropriate subset of named31 """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, therefore43 # 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
1 import inspect 2 3 try: 4 set 5 except NameError: 6 from sets import Set as set # For Python 2.3 7 8 from django.dispatch import errors 9 10 class 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 142 def connect(receiver, signal, sender=None): 143 """For backward compatibility only. See Signal.connect() 144 """ 145 return signal.connect(receiver, sender) 146 147 def disconnect(receiver, signal, sender=None): 148 """For backward compatibility only. See Signal.disconnect() 149 """ 150 signal.disconnect(receiver, sender) 151 152 def send(signal, sender=None, **named): 153 """For backward compatibility only. See Signal.send() 154 """ 155 return signal.send(sender=sender, **named) 156 157 def 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 1 import inspect 2 2 3 dispatcher is the core of the PyDispatcher system, 4 providing the primary API and the core logic for the 5 system. 3 try: 4 set 5 except NameError: 6 from sets import Set as set # For Python 2.3 6 7 7 Module attributes of note: 8 from django.dispatch import errors 8 9 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. 10 pluralize = lambda qty: qty != 1 and 's' or '' 11 flatten = lambda s: ",".join([str(item) for item in s]) 13 12 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. 13 class Signal(object): 48 14 """ 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) } 68 20 """ 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 signal80 81 receiver -- a callable Python object which is to receive82 messages/signals/events. Receivers must be hashable83 objects.84 85 if weak is True, then receiver must be weak-referencable86 (more precisely saferef.safeRef() must be able to create87 a reference to the receiver).88 21 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) 104 28 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 113 31 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 116 48 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) 122 51 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) 127 54 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) 152 60 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) 169 81 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. 196 98 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). 319 102 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. 323 107 324 if Any, only receivers registered for Any will receive 325 the message. 108 Return a list of tuple pairs [(receiver, response), ... ]. 326 109 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 329 116 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 333 118 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) 339 121 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 343 123 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 345 128 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]) 364 135 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] 365 143 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] 368 151 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()) 386 162 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 414 166 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 433 176 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']) 437 183 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 440 188 189 def connect(receiver, signal, sender=None): 190 """For backward compatibility only. See Signal.connect() 191 """ 192 return signal.connect(receiver, sender) 441 193 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 ) 194 def disconnect(receiver, signal, sender=None): 195 """For backward compatibility only. See Signal.disconnect() 196 """ 197 signal.disconnect(receiver, sender) 447 198 199 def send(signal, sender=None, **named): 200 """For backward compatibility only. See Signal.send() 201 """ 202 return signal.send(sender=sender, **named) 448 203 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 204 def sendExact(signal, sender, **named): 205 """This function is deprecated, as it now has the same 206 meaning as send. 457 207 """ 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) 62 Traceback (most recent call last): 63 ... 64 DispatcherTypeError: 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}) 90 Traceback (most recent call last): 91 ... 92 DispatcherTypeError: Signal.send received unexpected argment: arg9 and missing expected argment: arg2 93 """