|
@@ -0,0 +1,492 @@
|
|
|
+"""Multiple-producer-multiple-consumer signal-dispatching
|
|
|
+
|
|
|
+dispatcher is the core of the PyDispatcher system,
|
|
|
+providing the primary API and the core logic for the
|
|
|
+system.
|
|
|
+
|
|
|
+Module attributes of note:
|
|
|
+
|
|
|
+ Any -- Singleton used to signal either "Any Sender" or
|
|
|
+ "Any Signal". See documentation of the _Any class.
|
|
|
+ Anonymous -- Singleton used to signal "Anonymous Sender"
|
|
|
+ See documentation of the _Anonymous class.
|
|
|
+
|
|
|
+Internal attributes:
|
|
|
+ WEAKREF_TYPES -- tuple of types/classes which represent
|
|
|
+ weak references to receivers, and thus must be de-
|
|
|
+ referenced on retrieval to retrieve the callable
|
|
|
+ object
|
|
|
+ connections -- { senderkey (id) : { signal : [receivers...]}}
|
|
|
+ senders -- { senderkey (id) : weakref(sender) }
|
|
|
+ used for cleaning up sender references on sender
|
|
|
+ deletion
|
|
|
+ sendersBack -- { receiverkey (id) : [senderkey (id)...] }
|
|
|
+ used for cleaning up receiver references on receiver
|
|
|
+ deletion, (considerably speeds up the cleanup process
|
|
|
+ vs. the original code.)
|
|
|
+"""
|
|
|
+from __future__ import generators
|
|
|
+import weakref
|
|
|
+from pydispatch import saferef, robustapply, errors
|
|
|
+
|
|
|
+__author__ = "Patrick K. O'Brien <pobrien@orbtech.com>"
|
|
|
+__cvsid__ = "$Id: dispatcher.py,v 1.1 2010/03/30 15:45:55 mcfletch Exp $"
|
|
|
+__version__ = "$Revision: 1.1 $"[11:-2]
|
|
|
+
|
|
|
+class _Parameter:
|
|
|
+ """Used to represent default parameter values."""
|
|
|
+ def __repr__(self):
|
|
|
+ return self.__class__.__name__
|
|
|
+
|
|
|
+class _Any(_Parameter):
|
|
|
+ """Singleton used to signal either "Any Sender" or "Any Signal"
|
|
|
+
|
|
|
+ The Any object can be used with connect, disconnect,
|
|
|
+ send, or sendExact to signal that the parameter given
|
|
|
+ Any should react to all senders/signals, not just
|
|
|
+ a particular sender/signal.
|
|
|
+ """
|
|
|
+Any = _Any()
|
|
|
+
|
|
|
+class _Anonymous(_Parameter):
|
|
|
+ """Singleton used to signal "Anonymous Sender"
|
|
|
+
|
|
|
+ The Anonymous object is used to signal that the sender
|
|
|
+ of a message is not specified (as distinct from being
|
|
|
+ "any sender"). Registering callbacks for Anonymous
|
|
|
+ will only receive messages sent without senders. Sending
|
|
|
+ with anonymous will only send messages to those receivers
|
|
|
+ registered for Any or Anonymous.
|
|
|
+
|
|
|
+ Note:
|
|
|
+ The default sender for connect is Any, while the
|
|
|
+ default sender for send is Anonymous. This has
|
|
|
+ the effect that if you do not specify any senders
|
|
|
+ in either function then all messages are routed
|
|
|
+ as though there was a single sender (Anonymous)
|
|
|
+ being used everywhere.
|
|
|
+ """
|
|
|
+Anonymous = _Anonymous()
|
|
|
+
|
|
|
+WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref)
|
|
|
+
|
|
|
+connections = {}
|
|
|
+senders = {}
|
|
|
+sendersBack = {}
|
|
|
+
|
|
|
+
|
|
|
+def connect(receiver, signal=Any, sender=Any, weak=True):
|
|
|
+ """Connect receiver to sender for signal
|
|
|
+
|
|
|
+ receiver -- a callable Python object which is to receive
|
|
|
+ messages/signals/events. Receivers must be hashable
|
|
|
+ objects.
|
|
|
+
|
|
|
+ if weak is True, then receiver must be weak-referencable
|
|
|
+ (more precisely saferef.safeRef() must be able to create
|
|
|
+ a reference to the receiver).
|
|
|
+
|
|
|
+ Receivers are fairly flexible in their specification,
|
|
|
+ as the machinery in the robustApply module takes care
|
|
|
+ of most of the details regarding figuring out appropriate
|
|
|
+ subsets of the sent arguments to apply to a given
|
|
|
+ receiver.
|
|
|
+
|
|
|
+ Note:
|
|
|
+ if receiver is itself a weak reference (a callable),
|
|
|
+ it will be de-referenced by the system's machinery,
|
|
|
+ so *generally* weak references are not suitable as
|
|
|
+ receivers, though some use might be found for the
|
|
|
+ facility whereby a higher-level library passes in
|
|
|
+ pre-weakrefed receiver references.
|
|
|
+
|
|
|
+ signal -- the signal to which the receiver should respond
|
|
|
+
|
|
|
+ if Any, receiver will receive any signal from the
|
|
|
+ indicated sender (which might also be Any, but is not
|
|
|
+ necessarily Any).
|
|
|
+
|
|
|
+ Otherwise must be a hashable Python object other than
|
|
|
+ None (DispatcherError raised on None).
|
|
|
+
|
|
|
+ sender -- the sender to which the receiver should respond
|
|
|
+
|
|
|
+ if Any, receiver will receive the indicated signals
|
|
|
+ from any sender.
|
|
|
+
|
|
|
+ if Anonymous, receiver will only receive indicated
|
|
|
+ signals from send/sendExact which do not specify a
|
|
|
+ sender, or specify Anonymous explicitly as the sender.
|
|
|
+
|
|
|
+ Otherwise can be any python object.
|
|
|
+
|
|
|
+ weak -- whether to use weak references to the receiver
|
|
|
+ By default, the module will attempt to use weak
|
|
|
+ references to the receiver objects. If this parameter
|
|
|
+ is false, then strong references will be used.
|
|
|
+
|
|
|
+ returns None, may raise DispatcherTypeError
|
|
|
+ """
|
|
|
+ if signal is None:
|
|
|
+ raise errors.DispatcherTypeError(
|
|
|
+ 'Signal cannot be None (receiver=%r sender=%r)'%( receiver,sender)
|
|
|
+ )
|
|
|
+ if weak:
|
|
|
+ receiver = saferef.safeRef(receiver, onDelete=_removeReceiver)
|
|
|
+ senderkey = id(sender)
|
|
|
+ if senderkey in connections:
|
|
|
+ signals = connections[senderkey]
|
|
|
+ else:
|
|
|
+ connections[senderkey] = signals = {}
|
|
|
+ # Keep track of senders for cleanup.
|
|
|
+ # Is Anonymous something we want to clean up?
|
|
|
+ if sender not in (None, Anonymous, Any):
|
|
|
+ def remove(object, senderkey=senderkey):
|
|
|
+ _removeSender(senderkey=senderkey)
|
|
|
+ # Skip objects that can not be weakly referenced, which means
|
|
|
+ # they won't be automatically cleaned up, but that's too bad.
|
|
|
+ try:
|
|
|
+ weakSender = weakref.ref(sender, remove)
|
|
|
+ senders[senderkey] = weakSender
|
|
|
+ except:
|
|
|
+ pass
|
|
|
+
|
|
|
+ receiverID = id(receiver)
|
|
|
+ # get current set, remove any current references to
|
|
|
+ # this receiver in the set, including back-references
|
|
|
+ if signal in signals:
|
|
|
+ receivers = signals[signal]
|
|
|
+ _removeOldBackRefs(senderkey, signal, receiver, receivers)
|
|
|
+ else:
|
|
|
+ receivers = signals[signal] = []
|
|
|
+ try:
|
|
|
+ current = sendersBack.get( receiverID )
|
|
|
+ if current is None:
|
|
|
+ sendersBack[ receiverID ] = current = []
|
|
|
+ if senderkey not in current:
|
|
|
+ current.append(senderkey)
|
|
|
+ except:
|
|
|
+ pass
|
|
|
+
|
|
|
+ receivers.append(receiver)
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+def disconnect(receiver, signal=Any, sender=Any, weak=True):
|
|
|
+ """Disconnect receiver from sender for signal
|
|
|
+
|
|
|
+ receiver -- the registered receiver to disconnect
|
|
|
+ signal -- the registered signal to disconnect
|
|
|
+ sender -- the registered sender to disconnect
|
|
|
+ weak -- the weakref state to disconnect
|
|
|
+
|
|
|
+ disconnect reverses the process of connect,
|
|
|
+ the semantics for the individual elements are
|
|
|
+ logically equivalent to a tuple of
|
|
|
+ (receiver, signal, sender, weak) used as a key
|
|
|
+ to be deleted from the internal routing tables.
|
|
|
+ (The actual process is slightly more complex
|
|
|
+ but the semantics are basically the same).
|
|
|
+
|
|
|
+ Note:
|
|
|
+ Using disconnect is not required to cleanup
|
|
|
+ routing when an object is deleted, the framework
|
|
|
+ will remove routes for deleted objects
|
|
|
+ automatically. It's only necessary to disconnect
|
|
|
+ if you want to stop routing to a live object.
|
|
|
+
|
|
|
+ returns None, may raise DispatcherTypeError or
|
|
|
+ DispatcherKeyError
|
|
|
+ """
|
|
|
+ if signal is None:
|
|
|
+ raise errors.DispatcherTypeError(
|
|
|
+ 'Signal cannot be None (receiver=%r sender=%r)'%( receiver,sender)
|
|
|
+ )
|
|
|
+ if weak: receiver = saferef.safeRef(receiver)
|
|
|
+ senderkey = id(sender)
|
|
|
+ try:
|
|
|
+ signals = connections[senderkey]
|
|
|
+ receivers = signals[signal]
|
|
|
+ except KeyError:
|
|
|
+ raise errors.DispatcherKeyError(
|
|
|
+ """No receivers found for signal %r from sender %r""" %(
|
|
|
+ signal,
|
|
|
+ sender
|
|
|
+ )
|
|
|
+ )
|
|
|
+ try:
|
|
|
+ # also removes from receivers
|
|
|
+ _removeOldBackRefs(senderkey, signal, receiver, receivers)
|
|
|
+ except ValueError:
|
|
|
+ raise errors.DispatcherKeyError(
|
|
|
+ """No connection to receiver %s for signal %s from sender %s""" %(
|
|
|
+ receiver,
|
|
|
+ signal,
|
|
|
+ sender
|
|
|
+ )
|
|
|
+ )
|
|
|
+ _cleanupConnections(senderkey, signal)
|
|
|
+
|
|
|
+def getReceivers( sender = Any, signal = Any ):
|
|
|
+ """Get list of receivers from global tables
|
|
|
+
|
|
|
+ This utility function allows you to retrieve the
|
|
|
+ raw list of receivers from the connections table
|
|
|
+ for the given sender and signal pair.
|
|
|
+
|
|
|
+ Note:
|
|
|
+ there is no guarantee that this is the actual list
|
|
|
+ stored in the connections table, so the value
|
|
|
+ should be treated as a simple iterable/truth value
|
|
|
+ rather than, for instance a list to which you
|
|
|
+ might append new records.
|
|
|
+
|
|
|
+ Normally you would use liveReceivers( getReceivers( ...))
|
|
|
+ to retrieve the actual receiver objects as an iterable
|
|
|
+ object.
|
|
|
+ """
|
|
|
+ try:
|
|
|
+ return connections[id(sender)][signal]
|
|
|
+ except KeyError:
|
|
|
+ return []
|
|
|
+
|
|
|
+def liveReceivers(receivers):
|
|
|
+ """Filter sequence of receivers to get resolved, live receivers
|
|
|
+
|
|
|
+ This is a generator which will iterate over
|
|
|
+ the passed sequence, checking for weak references
|
|
|
+ and resolving them, then returning all live
|
|
|
+ receivers.
|
|
|
+ """
|
|
|
+ for receiver in receivers:
|
|
|
+ if isinstance( receiver, WEAKREF_TYPES):
|
|
|
+ # Dereference the weak reference.
|
|
|
+ receiver = receiver()
|
|
|
+ if receiver is not None:
|
|
|
+ yield receiver
|
|
|
+ else:
|
|
|
+ yield receiver
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+def getAllReceivers( sender = Any, signal = Any ):
|
|
|
+ """Get list of all receivers from global tables
|
|
|
+
|
|
|
+ This gets all receivers which should receive
|
|
|
+ the given signal from sender, each receiver should
|
|
|
+ be produced only once by the resulting generator
|
|
|
+ """
|
|
|
+ receivers = {}
|
|
|
+ for set in (
|
|
|
+ # Get receivers that receive *this* signal from *this* sender.
|
|
|
+ getReceivers( sender, signal ),
|
|
|
+ # Add receivers that receive *any* signal from *this* sender.
|
|
|
+ getReceivers( sender, Any ),
|
|
|
+ # Add receivers that receive *this* signal from *any* sender.
|
|
|
+ getReceivers( Any, signal ),
|
|
|
+ # Add receivers that receive *any* signal from *any* sender.
|
|
|
+ getReceivers( Any, Any ),
|
|
|
+ ):
|
|
|
+ for receiver in set:
|
|
|
+ if receiver: # filter out dead instance-method weakrefs
|
|
|
+ try:
|
|
|
+ if receiver not in receivers:
|
|
|
+ receivers[receiver] = 1
|
|
|
+ yield receiver
|
|
|
+ except TypeError:
|
|
|
+ # dead weakrefs raise TypeError on hash...
|
|
|
+ pass
|
|
|
+
|
|
|
+def send(signal=Any, sender=Anonymous, *arguments, **named):
|
|
|
+ """Send signal from sender to all connected receivers.
|
|
|
+
|
|
|
+ signal -- (hashable) signal value, see connect for details
|
|
|
+
|
|
|
+ sender -- the sender of the signal
|
|
|
+
|
|
|
+ if Any, only receivers registered for Any will receive
|
|
|
+ the message.
|
|
|
+
|
|
|
+ if Anonymous, only receivers registered to receive
|
|
|
+ messages from Anonymous or Any will receive the message
|
|
|
+
|
|
|
+ Otherwise can be any python object (normally one
|
|
|
+ registered with a connect if you actually want
|
|
|
+ something to occur).
|
|
|
+
|
|
|
+ arguments -- positional arguments which will be passed to
|
|
|
+ *all* receivers. Note that this may raise TypeErrors
|
|
|
+ if the receivers do not allow the particular arguments.
|
|
|
+ Note also that arguments are applied before named
|
|
|
+ arguments, so they should be used with care.
|
|
|
+
|
|
|
+ named -- named arguments which will be filtered according
|
|
|
+ to the parameters of the receivers to only provide those
|
|
|
+ acceptable to the receiver.
|
|
|
+
|
|
|
+ Return a list of tuple pairs [(receiver, response), ... ]
|
|
|
+
|
|
|
+ if any receiver raises an error, the error propagates back
|
|
|
+ through send, terminating the dispatch loop, so it is quite
|
|
|
+ possible to not have all receivers called if a raises an
|
|
|
+ error.
|
|
|
+ """
|
|
|
+ # Call each receiver with whatever arguments it can accept.
|
|
|
+ # Return a list of tuple pairs [(receiver, response), ... ].
|
|
|
+ responses = []
|
|
|
+ for receiver in liveReceivers(getAllReceivers(sender, signal)):
|
|
|
+ response = robustapply.robustApply(
|
|
|
+ receiver,
|
|
|
+ signal=signal,
|
|
|
+ sender=sender,
|
|
|
+ *arguments,
|
|
|
+ **named
|
|
|
+ )
|
|
|
+ responses.append((receiver, response))
|
|
|
+ return responses
|
|
|
+def sendExact( signal=Any, sender=Anonymous, *arguments, **named ):
|
|
|
+ """Send signal only to those receivers registered for exact message
|
|
|
+
|
|
|
+ sendExact allows for avoiding Any/Anonymous registered
|
|
|
+ handlers, sending only to those receivers explicitly
|
|
|
+ registered for a particular signal on a particular
|
|
|
+ sender.
|
|
|
+ """
|
|
|
+ responses = []
|
|
|
+ for receiver in liveReceivers(getReceivers(sender, signal)):
|
|
|
+ response = robustapply.robustApply(
|
|
|
+ receiver,
|
|
|
+ signal=signal,
|
|
|
+ sender=sender,
|
|
|
+ *arguments,
|
|
|
+ **named
|
|
|
+ )
|
|
|
+ responses.append((receiver, response))
|
|
|
+ return responses
|
|
|
+
|
|
|
+
|
|
|
+def _removeReceiver(receiver):
|
|
|
+ """Remove receiver from connections."""
|
|
|
+ if not sendersBack:
|
|
|
+ # During module cleanup the mapping will be replaced with None
|
|
|
+ return False
|
|
|
+ backKey = id(receiver)
|
|
|
+ try:
|
|
|
+ backSet = sendersBack.pop(backKey)
|
|
|
+ except KeyError:
|
|
|
+ return False
|
|
|
+ else:
|
|
|
+ for senderkey in backSet:
|
|
|
+ try:
|
|
|
+ signals = connections[senderkey].keys()
|
|
|
+ except KeyError:
|
|
|
+ pass
|
|
|
+ else:
|
|
|
+ for signal in signals:
|
|
|
+ try:
|
|
|
+ receivers = connections[senderkey][signal]
|
|
|
+ except KeyError:
|
|
|
+ pass
|
|
|
+ else:
|
|
|
+ try:
|
|
|
+ receivers.remove( receiver )
|
|
|
+ except Exception:
|
|
|
+ pass
|
|
|
+ _cleanupConnections(senderkey, signal)
|
|
|
+
|
|
|
+def _cleanupConnections(senderkey, signal):
|
|
|
+ """Delete any empty signals for senderkey. Delete senderkey if empty."""
|
|
|
+ try:
|
|
|
+ receivers = connections[senderkey][signal]
|
|
|
+ except:
|
|
|
+ pass
|
|
|
+ else:
|
|
|
+ if not receivers:
|
|
|
+ # No more connected receivers. Therefore, remove the signal.
|
|
|
+ try:
|
|
|
+ signals = connections[senderkey]
|
|
|
+ except KeyError:
|
|
|
+ pass
|
|
|
+ else:
|
|
|
+ del signals[signal]
|
|
|
+ if not signals:
|
|
|
+ # No more signal connections. Therefore, remove the sender.
|
|
|
+ _removeSender(senderkey)
|
|
|
+
|
|
|
+def _removeSender(senderkey):
|
|
|
+ """Remove senderkey from connections."""
|
|
|
+ _removeBackrefs(senderkey)
|
|
|
+ try:
|
|
|
+ del connections[senderkey]
|
|
|
+ except KeyError:
|
|
|
+ pass
|
|
|
+ # Senderkey will only be in senders dictionary if sender
|
|
|
+ # could be weakly referenced.
|
|
|
+ try:
|
|
|
+ del senders[senderkey]
|
|
|
+ except:
|
|
|
+ pass
|
|
|
+
|
|
|
+
|
|
|
+def _removeBackrefs( senderkey):
|
|
|
+ """Remove all back-references to this senderkey"""
|
|
|
+ try:
|
|
|
+ signals = connections[senderkey]
|
|
|
+ except KeyError:
|
|
|
+ signals = None
|
|
|
+ else:
|
|
|
+ items = signals.items()
|
|
|
+ def allReceivers( ):
|
|
|
+ for signal,set in items:
|
|
|
+ for item in set:
|
|
|
+ yield item
|
|
|
+ for receiver in allReceivers():
|
|
|
+ _killBackref( receiver, senderkey )
|
|
|
+
|
|
|
+def _removeOldBackRefs(senderkey, signal, receiver, receivers):
|
|
|
+ """Kill old sendersBack references from receiver
|
|
|
+
|
|
|
+ This guards against multiple registration of the same
|
|
|
+ receiver for a given signal and sender leaking memory
|
|
|
+ as old back reference records build up.
|
|
|
+
|
|
|
+ Also removes old receiver instance from receivers
|
|
|
+ """
|
|
|
+ try:
|
|
|
+ index = receivers.index(receiver)
|
|
|
+ # need to scan back references here and remove senderkey
|
|
|
+ except ValueError:
|
|
|
+ return False
|
|
|
+ else:
|
|
|
+ oldReceiver = receivers[index]
|
|
|
+ del receivers[index]
|
|
|
+ found = 0
|
|
|
+ signals = connections.get(signal)
|
|
|
+ if signals is not None:
|
|
|
+ for sig,recs in connections.get(signal,{}).iteritems():
|
|
|
+ if sig != signal:
|
|
|
+ for rec in recs:
|
|
|
+ if rec is oldReceiver:
|
|
|
+ found = 1
|
|
|
+ break
|
|
|
+ if not found:
|
|
|
+ _killBackref( oldReceiver, senderkey )
|
|
|
+ return True
|
|
|
+ return False
|
|
|
+
|
|
|
+
|
|
|
+def _killBackref( receiver, senderkey ):
|
|
|
+ """Do the actual removal of back reference from receiver to senderkey"""
|
|
|
+ receiverkey = id(receiver)
|
|
|
+ set = sendersBack.get( receiverkey, () )
|
|
|
+ while senderkey in set:
|
|
|
+ try:
|
|
|
+ set.remove( senderkey )
|
|
|
+ except:
|
|
|
+ break
|
|
|
+ if not set:
|
|
|
+ try:
|
|
|
+ del sendersBack[ receiverkey ]
|
|
|
+ except KeyError:
|
|
|
+ pass
|
|
|
+ return True
|