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