diff -r 57b4279d8c4e -r 03e267d67478 app/django/dispatch/dispatcher.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/django/dispatch/dispatcher.py Fri Jul 18 18:22:23 2008 +0000 @@ -0,0 +1,495 @@ +"""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.) +""" +import types, weakref +from django.dispatch import saferef, robustapply, errors + +__author__ = "Patrick K. O'Brien " +__cvsid__ = "$Id: dispatcher.py,v 1.9 2005/09/17 04:55:57 mcfletch Exp $" +__version__ = "$Revision: 1.9 $"[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) + + signals = connections.setdefault(senderkey, {}) + + # 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 signals.has_key(signal): + 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. + """ + existing = connections.get(id(sender)) + if existing is not None: + return existing.get(signal, []) + 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 dereferenced receivers which should receive + the given signal from sender, each receiver should + be produced only once by the resulting generator + """ + receivers = {} + # Get receivers that receive *this* signal from *this* sender. + # Add receivers that receive *any* signal from *this* sender. + # Add receivers that receive *this* signal from *any* sender. + # Add receivers that receive *any* signal from *any* sender. + l = [] + i = id(sender) + if i in connections: + sender_receivers = connections[i] + if signal in sender_receivers: + l.extend(sender_receivers[signal]) + if signal is not Any and Any in sender_receivers: + l.extend(sender_receivers[Any]) + + if sender is not Any: + i = id(Any) + if i in connections: + sender_receivers = connections[i] + if sender_receivers is not None: + if signal in sender_receivers: + l.extend(sender_receivers[signal]) + if signal is not Any and Any in sender_receivers: + l.extend(sender_receivers[Any]) + + for receiver in l: + try: + if not receiver in receivers: + if isinstance(receiver, WEAKREF_TYPES): + receiver = receiver() + # this should only (rough guess) be possible if somehow, deref'ing + # triggered a wipe. + if receiver is None: + continue + 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 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) + for senderkey in sendersBack.get(backKey,()): + try: + signals = connections[senderkey].keys() + except KeyError,err: + pass + else: + for signal in signals: + try: + receivers = connections[senderkey][signal] + except KeyError: + pass + else: + try: + receivers.remove( receiver ) + except Exception, err: + pass + _cleanupConnections(senderkey, signal) + try: + del sendersBack[ backKey ] + except KeyError: + pass + +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) + + connections.pop(senderkey, None) + senders.pop(senderkey, None) + + +def _removeBackrefs( senderkey): + """Remove all back-references to this senderkey""" + for receiver_list in connections.pop(senderkey, {}).values(): + for receiver in receiver_list: + _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) + receivers_list = sendersBack.get( receiverkey, () ) + while senderkey in receivers_list: + try: + receivers_list.remove( senderkey ) + except: + break + if not receivers_list: + try: + del sendersBack[ receiverkey ] + except KeyError: + pass + return True