dispatcher.py

Go to the documentation of this file.
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     # Keep track of senders for cleanup.
00148     # Is Anonymous something we want to clean up?
00149     if sender not in (None, Anonymous, Any):
00150         def remove(object, senderkey=senderkey):
00151             _removeSender(senderkey=senderkey)
00152         # Skip objects that can not be weakly referenced, which means
00153         # they won't be automatically cleaned up, but that's too bad.
00154         try:
00155             weakSender = weakref.ref(sender, remove)
00156             senders[senderkey] = weakSender
00157         except:
00158             pass
00159         
00160     receiverID = id(receiver)
00161     # get current set, remove any current references to
00162     # this receiver in the set, including back-references
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         # also removes from receivers
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             # Dereference the weak reference.
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         # Get receivers that receive *this* signal from *this* sender.
00288         getReceivers( sender, signal ),
00289         # Add receivers that receive *any* signal from *this* sender.
00290         getReceivers( sender, Any ),
00291         # Add receivers that receive *this* signal from *any* sender.
00292         getReceivers( Any, signal ),
00293         # Add receivers that receive *any* signal from *any* sender.
00294         getReceivers( Any, Any ),
00295     ):
00296         for receiver in set:
00297             if receiver: # filter out dead instance-method weakrefs
00298                 try:
00299                     if not receivers.has_key( receiver ):
00300                         receivers[receiver] = 1
00301                         yield receiver
00302                 except TypeError:
00303                     # dead weakrefs raise TypeError on hash...
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     # Call each receiver with whatever arguments it can accept.
00341     # Return a list of tuple pairs [(receiver, response), ... ].
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             # No more connected receivers. Therefore, remove the signal.
00408             try:
00409                 signals = connections[senderkey]
00410             except KeyError:
00411                 pass
00412             else:
00413                 del signals[signal]
00414                 if not signals:
00415                     # No more signal connections. Therefore, remove the sender.
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     # Senderkey will only be in senders dictionary if sender
00426     # could be weakly referenced.
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         # need to scan back references here and remove senderkey
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

Generated on Thu Apr 27 20:52:41 2006 for LICOS L02-01-00 by doxygen 1.4.6-NO