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 1.3 2005/11/16 04:27:12 stuvi Exp $"
00034 __version__ = "$Revision: 1.3 $"
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 if weak:
00141 receiver = saferef.safeRef(receiver, onDelete=_removeReceiver)
00142 senderkey = id(sender)
00143 if connections.has_key(senderkey):
00144 signals = connections[senderkey]
00145 else:
00146 connections[senderkey] = signals = {}
00147
00148
00149 if sender not in (None, Anonymous, Any):
00150 def remove(object, senderkey=senderkey):
00151 _removeSender(senderkey=senderkey)
00152
00153
00154 try:
00155 weakSender = weakref.ref(sender, remove)
00156 senders[senderkey] = weakSender
00157 except:
00158 pass
00159
00160 receiverID = id(receiver)
00161
00162
00163 if signals.has_key(signal):
00164 receivers = signals[signal]
00165 _removeOldBackRefs(senderkey, signal, receiver, receivers)
00166 else:
00167 receivers = signals[signal] = []
00168 try:
00169 current = sendersBack.get( receiverID )
00170 if current is None:
00171 sendersBack[ receiverID ] = current = []
00172 if senderkey not in current:
00173 current.append(senderkey)
00174 except:
00175 pass
00176
00177 receivers.append(receiver)
00178
00179
00180
00181 def disconnect(receiver, signal=Any, sender=Any, weak=True):
00182 """Disconnect receiver from sender for signal
00183
00184 receiver -- the registered receiver to disconnect
00185 signal -- the registered signal to disconnect
00186 sender -- the registered sender to disconnect
00187 weak -- the weakref state to disconnect
00188
00189 disconnect reverses the process of connect,
00190 the semantics for the individual elements are
00191 logically equivalent to a tuple of
00192 (receiver, signal, sender, weak) used as a key
00193 to be deleted from the internal routing tables.
00194 (The actual process is slightly more complex
00195 but the semantics are basically the same).
00196
00197 Note:
00198 Using disconnect is not required to cleanup
00199 routing when an object is deleted, the framework
00200 will remove routes for deleted objects
00201 automatically. It's only necessary to disconnect
00202 if you want to stop routing to a live object.
00203
00204 returns None, may raise DispatcherTypeError or
00205 DispatcherKeyError
00206 """
00207 if signal is None:
00208 raise errors.DispatcherTypeError(
00209 'Signal cannot be None (receiver=%r sender=%r)'%( receiver,sender)
00210 )
00211 if weak: receiver = saferef.safeRef(receiver)
00212 senderkey = id(sender)
00213 try:
00214 signals = connections[senderkey]
00215 receivers = signals[signal]
00216 except KeyError:
00217 raise errors.DispatcherKeyError(
00218 """No receivers found for signal %r from sender %r""" %(
00219 signal,
00220 sender
00221 )
00222 )
00223 try:
00224
00225 _removeOldBackRefs(senderkey, signal, receiver, receivers)
00226 except ValueError:
00227 raise errors.DispatcherKeyError(
00228 """No connection to receiver %s for signal %s from sender %s""" %(
00229 receiver,
00230 signal,
00231 sender
00232 )
00233 )
00234 _cleanupConnections(senderkey, signal)
00235
00236 def getReceivers( sender = Any, signal = Any ):
00237 """Get list of receivers from global tables
00238
00239 This utility function allows you to retrieve the
00240 raw list of receivers from the connections table
00241 for the given sender and signal pair.
00242
00243 Note:
00244 there is no guarantee that this is the actual list
00245 stored in the connections table, so the value
00246 should be treated as a simple iterable/truth value
00247 rather than, for instance a list to which you
00248 might append new records.
00249
00250 Normally you would use liveReceivers( getReceivers( ...))
00251 to retrieve the actual receiver objects as an iterable
00252 object.
00253 """
00254 try:
00255 return connections[id(sender)][signal]
00256 except KeyError:
00257 return []
00258
00259 def liveReceivers(receivers):
00260 """Filter sequence of receivers to get resolved, live receivers
00261
00262 This is a generator which will iterate over
00263 the passed sequence, checking for weak references
00264 and resolving them, then returning all live
00265 receivers.
00266 """
00267 for receiver in receivers:
00268 if isinstance( receiver, WEAKREF_TYPES):
00269
00270 receiver = receiver()
00271 if receiver is not None:
00272 yield receiver
00273 else:
00274 yield receiver
00275
00276
00277
00278 def getAllReceivers( sender = Any, signal = Any ):
00279 """Get list of all receivers from global tables
00280
00281 This gets all receivers which should receive
00282 the given signal from sender, each receiver should
00283 be produced only once by the resulting generator
00284 """
00285 receivers = {}
00286 for set in (
00287
00288 getReceivers( sender, signal ),
00289
00290 getReceivers( sender, Any ),
00291
00292 getReceivers( Any, signal ),
00293
00294 getReceivers( Any, Any ),
00295 ):
00296 for receiver in set:
00297 if receiver:
00298 try:
00299 if not receivers.has_key( receiver ):
00300 receivers[receiver] = 1
00301 yield receiver
00302 except TypeError:
00303
00304 pass
00305
00306 def send(signal=Any, sender=Anonymous, *arguments, **named):
00307 """Send signal from sender to all connected receivers.
00308
00309 signal -- (hashable) signal value, see connect for details
00310
00311 sender -- the sender of the signal
00312
00313 if Any, only receivers registered for Any will receive
00314 the message.
00315
00316 if Anonymous, only receivers registered to receive
00317 messages from Anonymous or Any will receive the message
00318
00319 Otherwise can be any python object (normally one
00320 registered with a connect if you actually want
00321 something to occur).
00322
00323 arguments -- positional arguments which will be passed to
00324 *all* receivers. Note that this may raise TypeErrors
00325 if the receivers do not allow the particular arguments.
00326 Note also that arguments are applied before named
00327 arguments, so they should be used with care.
00328
00329 named -- named arguments which will be filtered according
00330 to the parameters of the receivers to only provide those
00331 acceptable to the receiver.
00332
00333 Return a list of tuple pairs [(receiver, response), ... ]
00334
00335 if any receiver raises an error, the error propagates back
00336 through send, terminating the dispatch loop, so it is quite
00337 possible to not have all receivers called if a raises an
00338 error.
00339 """
00340
00341
00342 responses = []
00343 for receiver in liveReceivers(getAllReceivers(sender, signal)):
00344 response = robustapply.robustApply(
00345 receiver,
00346 signal=signal,
00347 sender=sender,
00348 *arguments,
00349 **named
00350 )
00351 responses.append((receiver, response))
00352 return responses
00353 def sendExact( signal=Any, sender=Anonymous, *arguments, **named ):
00354 """Send signal only to those receivers registered for exact message
00355
00356 sendExact allows for avoiding Any/Anonymous registered
00357 handlers, sending only to those receivers explicitly
00358 registered for a particular signal on a particular
00359 sender.
00360 """
00361 responses = []
00362 for receiver in liveReceivers(getReceivers(sender, signal)):
00363 response = robustapply.robustApply(
00364 receiver,
00365 signal=signal,
00366 sender=sender,
00367 *arguments,
00368 **named
00369 )
00370 responses.append((receiver, response))
00371 return responses
00372
00373
00374 def _removeReceiver(receiver):
00375 """Remove receiver from connections."""
00376 backKey = id(receiver)
00377 for senderkey in sendersBack.get(backKey,()):
00378 try:
00379 signals = connections[senderkey].keys()
00380 except KeyError,err:
00381 pass
00382 else:
00383 for signal in signals:
00384 try:
00385 receivers = connections[senderkey][signal]
00386 except KeyError:
00387 pass
00388 else:
00389 try:
00390 receivers.remove( receiver )
00391 except Exception, err:
00392 pass
00393 _cleanupConnections(senderkey, signal)
00394 try:
00395 del sendersBack[ backKey ]
00396 except KeyError:
00397 pass
00398
00399 def _cleanupConnections(senderkey, signal):
00400 """Delete any empty signals for senderkey. Delete senderkey if empty."""
00401 try:
00402 receivers = connections[senderkey][signal]
00403 except:
00404 pass
00405 else:
00406 if not receivers:
00407
00408 try:
00409 signals = connections[senderkey]
00410 except KeyError:
00411 pass
00412 else:
00413 del signals[signal]
00414 if not signals:
00415
00416 _removeSender(senderkey)
00417
00418 def _removeSender(senderkey):
00419 """Remove senderkey from connections."""
00420 _removeBackrefs(senderkey)
00421 try:
00422 del connections[senderkey]
00423 except KeyError:
00424 pass
00425
00426
00427 try: del senders[senderkey]
00428 except: pass
00429
00430
00431 def _removeBackrefs( senderkey):
00432 """Remove all back-references to this senderkey"""
00433 try:
00434 signals = connections[senderkey]
00435 except KeyError:
00436 signals = None
00437 else:
00438 items = signals.items()
00439 def allReceivers( ):
00440 for signal,set in items:
00441 for item in set:
00442 yield item
00443 for receiver in allReceivers():
00444 _killBackref( receiver, senderkey )
00445
00446 def _removeOldBackRefs(senderkey, signal, receiver, receivers):
00447 """Kill old sendersBack references from receiver
00448
00449 This guards against multiple registration of the same
00450 receiver for a given signal and sender leaking memory
00451 as old back reference records build up.
00452
00453 Also removes old receiver instance from receivers
00454 """
00455 try:
00456 index = receivers.index(receiver)
00457
00458 except ValueError:
00459 return False
00460 else:
00461 oldReceiver = receivers[index]
00462 del receivers[index]
00463 found = 0
00464 signals = connections.get(signal)
00465 if signals is not None:
00466 for sig,recs in connections.get(signal,{}).iteritems():
00467 if sig != signal:
00468 for rec in recs:
00469 if rec is oldReceiver:
00470 found = 1
00471 break
00472 if not found:
00473 _killBackref( oldReceiver, senderkey )
00474 return True
00475 return False
00476
00477
00478 def _killBackref( receiver, senderkey ):
00479 """Do the actual removal of back reference from receiver to senderkey"""
00480 receiverkey = id(receiver)
00481 set = sendersBack.get( receiverkey, () )
00482 while senderkey in set:
00483 try:
00484 set.remove( senderkey )
00485 except:
00486 break
00487 if not set:
00488 try:
00489 del sendersBack[ receiverkey ]
00490 except KeyError:
00491 pass
00492 return True