"""Provides global signal dispatching services.""" __author__ = "Patrick K. O'Brien " __cvsid__ = "$Id$" __revision__ = "$Revision$"[11:-2] import exceptions import types import weakref class DispatcherError(exceptions.Exception): def __init__(self, args=None): self.args = args class Parameter: """Used to represent default parameter values.""" def __repr__(self): return self.__class__.__name__ class Any(Parameter): pass Any = Any() class Anonymous(Parameter): pass Anonymous = Anonymous() connections = {} senders = {} _boundMethods = weakref.WeakKeyDictionary() def connect(receiver, signal=Any, sender=Any, weak=True): """ Connect receiver to sender for signal. * If sender is Any, receiver will receive signal from any sender. * If signal is Any, receiver will receive any signal from sender. * If sender is None, receiver will receive signal from Anonymous. * If signal is Any and sender is None, receiver will receive any signal from Anonymous. * If signal is Any and sender is Any, receiver will receive any signal from any sender. * If weak is true, weak references will be used. """ if signal is None: raise DispatcherError, 'signal cannot be None' if weak: receiver = safeRef(receiver) senderkey = id(sender) signals = {} if connections.has_key(senderkey): signals = connections[senderkey] else: connections[senderkey] = signals # Keep track of senders for cleanup. if sender not in (None, 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 receivers = [] if signals.has_key(signal): receivers = signals[signal] else: signals[signal] = receivers try: receivers.remove(receiver) except ValueError: pass receivers.append(receiver) def disconnect(receiver, signal=Any, sender=Any, weak=True): """Disconnect receiver from sender for signal. Disconnecting is not required. The use of disconnect is the same as for connect, only in reverse. Think of it as undoing a previous connection.""" if signal is None: raise DispatcherError, 'signal cannot be None' if weak: receiver = safeRef(receiver) senderkey = id(sender) try: receivers = connections[senderkey][signal] except KeyError: raise DispatcherError, \ 'No receivers for signal %r from sender %s' % (signal, sender) try: receivers.remove(receiver) except ValueError: raise DispatcherError, \ 'No connection to receiver %s for signal %r from sender %s' % \ (receiver, signal, sender) _cleanupConnections(senderkey, signal) def send(signal, sender=Anonymous, **kwds): """Send signal from sender to all connected receivers. Return a list of tuple pairs [(receiver, response), ... ]. If sender is not specified, signal is sent anonymously.""" senderkey = id(sender) anykey = id(Any) # Get receivers that receive *this* signal from *this* sender. receivers = [] try: receivers.extend(connections[senderkey][signal]) except KeyError: pass # Add receivers that receive *any* signal from *this* sender. anyreceivers = [] try: anyreceivers = connections[senderkey][Any] except KeyError: pass for receiver in anyreceivers: if receivers.count(receiver) == 0: receivers.append(receiver) # Add receivers that receive *this* signal from *any* sender. anyreceivers = [] try: anyreceivers = connections[anykey][signal] except KeyError: pass for receiver in anyreceivers: if receivers.count(receiver) == 0: receivers.append(receiver) # Add receivers that receive *any* signal from *any* sender. anyreceivers = [] try: anyreceivers = connections[anykey][Any] except KeyError: pass for receiver in anyreceivers: if receivers.count(receiver) == 0: receivers.append(receiver) # Call each receiver with whatever arguments it can accept. # Return a list of tuple pairs [(receiver, response), ... ]. responses = [] for receiver in receivers: if type(receiver) is weakref.ReferenceType \ or isinstance(receiver, BoundMethodWeakref): # Dereference the weak reference. receiver = receiver() if receiver is None: # This receiver is dead, so skip it. continue response = _call(receiver, signal=signal, sender=sender, **kwds) responses += [(receiver, response)] return responses def _call(receiver, **kwds): """Call receiver with only arguments it can accept.""" ## if type(receiver) is types.InstanceType: if hasattr(receiver, '__call__') and \ (hasattr(receiver.__call__, 'im_func') or hasattr(receiver.__call__, 'im_code')): # receiver is a class instance; assume it is callable. # Reassign receiver to the actual method that will be called. receiver = receiver.__call__ if hasattr(receiver, 'im_func'): # receiver is a method. Drop the first argument, usually 'self'. fc = receiver.im_func.func_code acceptable = fc.co_varnames[1:fc.co_argcount] elif hasattr(receiver, 'func_code'): # receiver is a function. fc = receiver.func_code acceptable = fc.co_varnames[0:fc.co_argcount] else: raise DispatcherError, 'Unknown receiver %s of type %s' % (receiver, type(receiver)) if not (fc.co_flags & 8): # fc does not have a **kwds type parameter, therefore # remove unacceptable arguments. for arg in kwds.keys(): if arg not in acceptable: del kwds[arg] return receiver(**kwds) def safeRef(object): """Return a *safe* weak reference to a callable object.""" if hasattr(object, 'im_self'): if object.im_self is not None: # Turn a bound method into a BoundMethodWeakref instance. # Keep track of these instances for lookup by disconnect(). selfkey = object.im_self funckey = object.im_func if not _boundMethods.has_key(selfkey): _boundMethods[selfkey] = weakref.WeakKeyDictionary() if not _boundMethods[selfkey].has_key(funckey): _boundMethods[selfkey][funckey] = \ BoundMethodWeakref(boundMethod=object) return _boundMethods[selfkey][funckey] return weakref.ref(object, _removeReceiver) class BoundMethodWeakref: """BoundMethodWeakref class.""" def __init__(self, boundMethod): """Return a weak-reference-like instance for a bound method.""" self.isDead = 0 def remove(object, self=self): """Set self.isDead to true when method or instance is destroyed.""" self.isDead = 1 _removeReceiver(receiver=self) self.weakSelf = weakref.ref(boundMethod.im_self, remove) self.weakFunc = weakref.ref(boundMethod.im_func, remove) def __repr__(self): """Return the closest representation.""" return '' % (self.weakSelf, self.weakFunc) def __call__(self): """Return a strong reference to the bound method.""" if self.isDead: return None else: object = self.weakSelf() method = self.weakFunc().__name__ try: # wxPython hack to handle wxDead objects. return getattr(object, method) except AttributeError: ## _removeReceiver(receiver=self) return None def _removeReceiver(receiver): """Remove receiver from connections.""" for senderkey in connections.keys(): for signal in connections[senderkey].keys(): receivers = connections[senderkey][signal] try: receivers.remove(receiver) except: pass _cleanupConnections(senderkey, signal) def _cleanupConnections(senderkey, signal): """Delete any empty signals for senderkey. Delete senderkey if empty.""" receivers = connections[senderkey][signal] if not receivers: # No more connected receivers. Therefore, remove the signal. signals = connections[senderkey] del signals[signal] if not signals: # No more signal connections. Therefore, remove the sender. _removeSender(senderkey) def _removeSender(senderkey): """Remove senderkey from connections.""" del connections[senderkey] # Senderkey will only be in senders dictionary if sender # could be weakly referenced. try: del senders[senderkey] except: pass