Main Page | Packages | Class Hierarchy | Alphabetical List | Class List | Directories | File List | Namespace Members | Class Members | Related Pages

dispatcher.py

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 

Generated on Fri Jul 21 13:26:27 2006 for LATTE R04-12-00 by doxygen 1.4.3