00001 """Multiple-producer-multiple-consumer signal-dispatching 00002 00003 dispatcher is the core of the PyDispatcher system, 00004 providing the primary API and the core logic for the 00005 system. 00006 00007 Module attributes of note: 00008 00009 Any -- Singleton used to signal either "Any Sender" or 00010 "Any Signal". See documentation of the _Any class. 00011 Anonymous -- Singleton used to signal "Anonymous Sender" 00012 See documentation of the _Anonymous class. 00013 00014 Internal attributes: 00015 WEAKREF_TYPES -- tuple of types/classes which represent 00016 weak references to receivers, and thus must be de- 00017 referenced on retrieval to retrieve the callable 00018 object 00019 connections -- { senderkey (id) : { signal : [receivers...]}} 00020 senders -- { senderkey (id) : weakref(sender) } 00021 used for cleaning up sender references on sender 00022 deletion 00023 sendersBack -- { receiverkey (id) : [senderkey (id)...] } 00024 used for cleaning up receiver references on receiver 00025 deletion, (considerably speeds up the cleanup process 00026 vs. the original code.) 00027 """ 00028 from __future__ import generators 00029 import types, weakref 00030 import saferef, robustapply, errors 00031 00032 __author__ = "Patrick K. O'Brien <pobrien@orbtech.com>" 00033 __cvsid__ = "$Id: dispatcher.py,v 2.1 2005/02/26 02:41:17 stuvi Exp $" 00034 __version__ = "$Revision: 2.1 $" 00035 00036 try: 00037 True 00038 except NameError: 00039 True = 1==1 00040 False = 1==0 00041 00042 class _Parameter: 00043 """Used to represent default parameter values.""" 00044 def __repr__(self): 00045 return self.__class__.__name__ 00046 00047 class _Any(_Parameter): 00048 """Singleton used to signal either "Any Sender" or "Any Signal" 00049 00050 The Any object can be used with connect, disconnect, 00051 send, or sendExact to signal that the parameter given 00052 Any should react to all senders/signals, not just 00053 a particular sender/signal. 00054 """ 00055 Any = _Any() 00056 00057 class _Anonymous(_Parameter): 00058 """Singleton used to signal "Anonymous Sender" 00059 00060 The Anonymous object is used to signal that the sender 00061 of a message is not specified (as distinct from being 00062 "any sender"). Registering callbacks for Anonymous 00063 will only receive messages sent without senders. Sending 00064 with anonymous will only send messages to those receivers 00065 registered for Any or Anonymous. 00066 00067 Note: 00068 The default sender for connect is Any, while the 00069 default sender for send is Anonymous. This has 00070 the effect that if you do not specify any senders 00071 in either function then all messages are routed 00072 as though there was a single sender (Anonymous) 00073 being used everywhere. 00074 """ 00075 Anonymous = _Anonymous() 00076 00077 WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref) 00078 00079 connections = {} 00080 senders = {} 00081 sendersBack = {} 00082 00083 00084 def connect(receiver, signal=Any, sender=Any, weak=True): 00085 """Connect receiver to sender for signal 00086 00087 receiver -- a callable Python object which is to receive 00088 messages/signals/events. Receivers must be hashable 00089 objects. 00090 00091 if weak is True, then receiver must be weak-referencable 00092 (more precisely saferef.safeRef() must be able to create 00093 a reference to the receiver). 00094 00095 Receivers are fairly flexible in their specification, 00096 as the machinery in the robustApply module takes care 00097 of most of the details regarding figuring out appropriate 00098 subsets of the sent arguments to apply to a given 00099 receiver. 00100 00101 Note: 00102 if receiver is itself a weak reference (a callable), 00103 it will be de-referenced by the system's machinery, 00104 so *generally* weak references are not suitable as 00105 receivers, though some use might be found for the 00106 facility whereby a higher-level library passes in 00107 pre-weakrefed receiver references. 00108 00109 signal -- the signal to which the receiver should respond 00110 00111 if Any, receiver will receive any signal from the 00112 indicated sender (which might also be Any, but is not 00113 necessarily Any). 00114 00115 Otherwise must be a hashable Python object other than 00116 None (DispatcherError raised on None). 00117 00118 sender -- the sender to which the receiver should respond 00119 00120 if Any, receiver will receive the indicated signals 00121 from any sender. 00122 00123 if Anonymous, receiver will only receive indicated 00124 signals from send/sendExact which do not specify a 00125 sender, or specify Anonymous explicitly as the sender. 00126 00127 Otherwise can be any python object. 00128 00129 weak -- whether to use weak references to the receiver 00130 By default, the module will attempt to use weak 00131 references to the receiver objects. If this parameter 00132 is false, then strong references will be used. 00133 00134 returns None, may raise DispatcherTypeError 00135 """ 00136 if signal is None: 00137 raise errors.DispatcherTypeError( 00138 'Signal cannot be None (receiver=%r sender=%r)'%( receiver,sender) 00139 ) 00140 receiverID = id( receiver ) 00141 if weak: receiver = saferef.safeRef(receiver, onDelete=_removeReceiver) 00142 senderkey = id(sender) 00143 signals = {} 00144 if connections.has_key(senderkey): 00145 signals = connections[senderkey] 00146 else: 00147 connections[senderkey] = signals 00148 # Keep track of senders for cleanup. 00149 # Is Anonymous something we want to clean up? 00150 if sender not in (None, Anonymous, Any): 00151 def remove(object, senderkey=senderkey): 00152 _removeSender(senderkey=senderkey) 00153 # Skip objects that can not be weakly referenced, which means 00154 # they won't be automatically cleaned up, but that's too bad. 00155 try: 00156 weakSender = weakref.ref(sender, remove) 00157 senders[senderkey] = weakSender 00158 sendersBack.setdefault(receiverID,[]).append(senderkey) 00159 except: 00160 pass 00161 receivers = [] 00162 if signals.has_key(signal): 00163 receivers = signals[signal] 00164 else: 00165 signals[signal] = receivers 00166 try: receivers.remove(receiver) 00167 except ValueError: pass 00168 receivers.append(receiver) 00169 00170 def disconnect(receiver, signal=Any, sender=Any, weak=True): 00171 """Disconnect receiver from sender for signal 00172 00173 receiver -- the registered receiver to disconnect 00174 signal -- the registered signal to disconnect 00175 sender -- the registered sender to disconnect 00176 weak -- the weakref state to disconnect 00177 00178 disconnect reverses the process of connect, 00179 the semantics for the individual elements are 00180 logically equivalent to a tuple of 00181 (receiver, signal, sender, weak) used as a key 00182 to be deleted from the internal routing tables. 00183 (The actual process is slightly more complex 00184 but the semantics are basically the same). 00185 00186 Note: 00187 Using disconnect is not required to cleanup 00188 routing when an object is deleted, the framework 00189 will remove routes for deleted objects 00190 automatically. It's only necessary to disconnect 00191 if you want to stop routing to a live object. 00192 00193 returns None, may raise DispatcherTypeError or 00194 DispatcherKeyError 00195 """ 00196 if signal is None: 00197 raise errors.DispatcherTypeError( 00198 'Signal cannot be None (receiver=%r sender=%r)'%( receiver,sender) 00199 ) 00200 if weak: receiver = saferef.safeRef(receiver) 00201 senderkey = id(sender) 00202 try: 00203 receivers = connections[senderkey][signal] 00204 except KeyError: 00205 raise errors.DispatcherKeyError( 00206 """No receivers found for signal %r from sender %r""" %( 00207 signal, 00208 sender 00209 ) 00210 ) 00211 try: 00212 receivers.remove(receiver) 00213 except ValueError: 00214 raise errors.DispatcherKeyError( 00215 """No connection to receiver %s for signal %s from sender %s""" %( 00216 receiver, 00217 signal, 00218 sender 00219 ) 00220 ) 00221 _cleanupConnections(senderkey, signal) 00222 00223 def getReceivers( sender = Any, signal = Any ): 00224 """Get list of receivers from global tables 00225 00226 This utility function allows you to retrieve the 00227 raw list of receivers from the connections table 00228 for the given sender and signal pair. 00229 00230 Note: 00231 there is no guarantee that this is the actual list 00232 stored in the connections table, so the value 00233 should be treated as a simple iterable/truth value 00234 rather than, for instance a list to which you 00235 might append new records. 00236 00237 Normally you would use liveReceivers( getReceivers( ...)) 00238 to retrieve the actual receiver objects as an iterable 00239 object. 00240 """ 00241 try: 00242 return connections[id(sender)][signal] 00243 except KeyError: 00244 return [] 00245 00246 def liveReceivers(receivers): 00247 """Filter sequence of receivers to get resolved, live receivers 00248 00249 This is a generator which will iterate over 00250 the passed sequence, checking for weak references 00251 and resolving them, then returning all live 00252 receivers. 00253 """ 00254 for receiver in receivers: 00255 if isinstance( receiver, WEAKREF_TYPES): 00256 # Dereference the weak reference. 00257 receiver = receiver() 00258 if receiver is not None: 00259 yield receiver 00260 else: 00261 yield receiver 00262 00263 def getAllReceivers( sender = Any, signal = Any ): 00264 """Get list of all receivers from global tables 00265 00266 This gets all receivers which should receive 00267 the given signal from sender, each receiver should 00268 be produced only once by the resulting generator 00269 """ 00270 receivers = {} 00271 for set in ( 00272 # Get receivers that receive *this* signal from *this* sender. 00273 getReceivers( sender, signal ), 00274 # Add receivers that receive *any* signal from *this* sender. 00275 getReceivers( sender, Any ), 00276 # Add receivers that receive *this* signal from *any* sender. 00277 getReceivers( Any, signal ), 00278 # Add receivers that receive *any* signal from *any* sender. 00279 getReceivers( Any, Any ), 00280 ): 00281 for receiver in set: 00282 if receiver: # filter out dead instance-method weakrefs 00283 try: 00284 if not receivers.has_key( receiver ): 00285 receivers[receiver] = 1 00286 yield receiver 00287 except TypeError: 00288 # dead weakrefs raise TypeError on hash... 00289 pass 00290 00291 def send(signal=Any, sender=Anonymous, *arguments, **named): 00292 """Send signal from sender to all connected receivers. 00293 00294 signal -- (hashable) signal value, see connect for details 00295 00296 sender -- the sender of the signal 00297 00298 if Any, only receivers registered for Any will receive 00299 the message. 00300 00301 if Anonymous, only receivers registered to receive 00302 messages from Anonymous or Any will receive the message 00303 00304 Otherwise can be any python object (normally one 00305 registered with a connect if you actually want 00306 something to occur). 00307 00308 arguments -- positional arguments which will be passed to 00309 *all* receivers. Note that this may raise TypeErrors 00310 if the receivers do not allow the particular arguments. 00311 Note also that arguments are applied before named 00312 arguments, so they should be used with care. 00313 00314 named -- named arguments which will be filtered according 00315 to the parameters of the receivers to only provide those 00316 acceptable to the receiver. 00317 00318 Return a list of tuple pairs [(receiver, response), ... ] 00319 00320 if any receiver raises an error, the error propagates back 00321 through send, terminating the dispatch loop, so it is quite 00322 possible to not have all receivers called if a raises an 00323 error. 00324 """ 00325 # Call each receiver with whatever arguments it can accept. 00326 # Return a list of tuple pairs [(receiver, response), ... ]. 00327 responses = [] 00328 for receiver in liveReceivers(getAllReceivers(sender, signal)): 00329 response = robustapply.robustApply( 00330 receiver, 00331 signal=signal, 00332 sender=sender, 00333 *arguments, 00334 **named 00335 ) 00336 responses.append((receiver, response)) 00337 return responses 00338 def sendExact( signal=Any, sender=Anonymous, *arguments, **named ): 00339 """Send signal only to those receivers registered for exact message 00340 00341 sendExact allows for avoiding Any/Anonymous registered 00342 handlers, sending only to those receivers explicitly 00343 registered for a particular signal on a particular 00344 sender. 00345 """ 00346 responses = [] 00347 for receiver in liveReceivers(getReceivers(sender, signal)): 00348 response = robustapply.robustApply( 00349 receiver, 00350 signal=signal, 00351 sender=sender, 00352 *arguments, 00353 **named 00354 ) 00355 responses.append((receiver, response)) 00356 return responses 00357 00358 00359 def _removeReceiver(receiver): 00360 """Remove receiver from connections.""" 00361 backKey = id(receiver) 00362 for senderkey in sendersBack.get(backKey,()): 00363 try: 00364 for signal in connections[senderkey].keys(): 00365 try: 00366 receivers = connections[senderkey][signal] 00367 except KeyError: 00368 pass 00369 else: 00370 try: receivers.remove(receiver) 00371 except: pass 00372 _cleanupConnections(senderkey, signal) 00373 except KeyError: 00374 pass 00375 00376 def _cleanupConnections(senderkey, signal): 00377 """Delete any empty signals for senderkey. Delete senderkey if empty.""" 00378 try: 00379 receivers = connections[senderkey][signal] 00380 except: 00381 pass 00382 else: 00383 if not receivers: 00384 # No more connected receivers. Therefore, remove the signal. 00385 try: 00386 signals = connections[senderkey] 00387 except KeyError: 00388 pass 00389 else: 00390 del signals[signal] 00391 if not signals: 00392 # No more signal connections. Therefore, remove the sender. 00393 _removeSender(senderkey) 00394 00395 def _removeSender(senderkey): 00396 """Remove senderkey from connections.""" 00397 try: 00398 del connections[senderkey] 00399 except KeyError: 00400 pass 00401 # Senderkey will only be in senders dictionary if sender 00402 # could be weakly referenced. 00403 try: del senders[senderkey] 00404 except: pass 00405 00406