| 1 | """Provides global signal dispatching services.""" |
| 2 | |
| 3 | __author__ = "Patrick K. O'Brien <pobrien@orbtech.com>" |
| 4 | __cvsid__ = "$Id$" |
| 5 | __revision__ = "$Revision$"[11:-2] |
| 6 | |
| 7 | import exceptions |
| 8 | import types |
| 9 | import weakref |
| 10 | |
| 11 | |
| 12 | class DispatcherError(exceptions.Exception): |
| 13 | def __init__(self, args=None): |
| 14 | self.args = args |
| 15 | |
| 16 | |
| 17 | class Parameter: |
| 18 | """Used to represent default parameter values.""" |
| 19 | def __repr__(self): |
| 20 | return self.__class__.__name__ |
| 21 | |
| 22 | class Any(Parameter): pass |
| 23 | Any = Any() |
| 24 | |
| 25 | class Anonymous(Parameter): pass |
| 26 | Anonymous = Anonymous() |
| 27 | |
| 28 | |
| 29 | connections = {} |
| 30 | senders = {} |
| 31 | _boundMethods = weakref.WeakKeyDictionary() |
| 32 | |
| 33 | |
| 34 | def connect(receiver, signal=Any, sender=Any, weak=True): |
| 35 | """Connect receiver to sender for signal. |
| 36 | |
| 37 | If sender is Any, receiver will receive signal from any sender. |
| 38 | If signal is Any, receiver will receive any signal from sender. |
| 39 | If sender is None, receiver will receive signal from Anonymous. |
| 40 | If signal is Any and sender is None, receiver will receive any |
| 41 | signal from Anonymous. |
| 42 | If signal is Any and sender is Any, receiver will receive any |
| 43 | signal from any sender. |
| 44 | If weak is true, weak references will be used.""" |
| 45 | if signal is None: |
| 46 | raise DispatcherError, 'signal cannot be None' |
| 47 | if weak: |
| 48 | receiver = safeRef(receiver) |
| 49 | senderkey = id(sender) |
| 50 | signals = {} |
| 51 | if connections.has_key(senderkey): |
| 52 | signals = connections[senderkey] |
| 53 | else: |
| 54 | connections[senderkey] = signals |
| 55 | # Keep track of senders for cleanup. |
| 56 | if sender not in (None, Any): |
| 57 | def remove(object, senderkey=senderkey): |
| 58 | _removeSender(senderkey=senderkey) |
| 59 | # Skip objects that can not be weakly referenced, which means |
| 60 | # they won't be automatically cleaned up, but that's too bad. |
| 61 | try: |
| 62 | weakSender = weakref.ref(sender, remove) |
| 63 | senders[senderkey] = weakSender |
| 64 | except: |
| 65 | pass |
| 66 | receivers = [] |
| 67 | if signals.has_key(signal): |
| 68 | receivers = signals[signal] |
| 69 | else: |
| 70 | signals[signal] = receivers |
| 71 | try: |
| 72 | receivers.remove(receiver) |
| 73 | except ValueError: |
| 74 | pass |
| 75 | receivers.append(receiver) |
| 76 | |
| 77 | def disconnect(receiver, signal=Any, sender=Any, weak=True): |
| 78 | """Disconnect receiver from sender for signal. |
| 79 | |
| 80 | Disconnecting is not required. The use of disconnect is the same as for |
| 81 | connect, only in reverse. Think of it as undoing a previous connection.""" |
| 82 | if signal is None: |
| 83 | raise DispatcherError, 'signal cannot be None' |
| 84 | if weak: |
| 85 | receiver = safeRef(receiver) |
| 86 | senderkey = id(sender) |
| 87 | try: |
| 88 | receivers = connections[senderkey][signal] |
| 89 | except KeyError: |
| 90 | raise DispatcherError, \ |
| 91 | 'No receivers for signal %r from sender %s' % (signal, sender) |
| 92 | try: |
| 93 | receivers.remove(receiver) |
| 94 | except ValueError: |
| 95 | raise DispatcherError, \ |
| 96 | 'No connection to receiver %s for signal %r from sender %s' % \ |
| 97 | (receiver, signal, sender) |
| 98 | _cleanupConnections(senderkey, signal) |
| 99 | |
| 100 | def send(signal, sender=Anonymous, **kwds): |
| 101 | """Send signal from sender to all connected receivers. |
| 102 | |
| 103 | Return a list of tuple pairs [(receiver, response), ... ]. |
| 104 | If sender is not specified, signal is sent anonymously.""" |
| 105 | senderkey = id(sender) |
| 106 | anykey = id(Any) |
| 107 | # Get receivers that receive *this* signal from *this* sender. |
| 108 | receivers = [] |
| 109 | try: |
| 110 | receivers.extend(connections[senderkey][signal]) |
| 111 | except KeyError: |
| 112 | pass |
| 113 | # Add receivers that receive *any* signal from *this* sender. |
| 114 | anyreceivers = [] |
| 115 | try: |
| 116 | anyreceivers = connections[senderkey][Any] |
| 117 | except KeyError: |
| 118 | pass |
| 119 | for receiver in anyreceivers: |
| 120 | if receivers.count(receiver) == 0: |
| 121 | receivers.append(receiver) |
| 122 | # Add receivers that receive *this* signal from *any* sender. |
| 123 | anyreceivers = [] |
| 124 | try: |
| 125 | anyreceivers = connections[anykey][signal] |
| 126 | except KeyError: |
| 127 | pass |
| 128 | for receiver in anyreceivers: |
| 129 | if receivers.count(receiver) == 0: |
| 130 | receivers.append(receiver) |
| 131 | # Add receivers that receive *any* signal from *any* sender. |
| 132 | anyreceivers = [] |
| 133 | try: |
| 134 | anyreceivers = connections[anykey][Any] |
| 135 | except KeyError: |
| 136 | pass |
| 137 | for receiver in anyreceivers: |
| 138 | if receivers.count(receiver) == 0: |
| 139 | receivers.append(receiver) |
| 140 | # Call each receiver with whatever arguments it can accept. |
| 141 | # Return a list of tuple pairs [(receiver, response), ... ]. |
| 142 | responses = [] |
| 143 | for receiver in receivers: |
| 144 | if type(receiver) is weakref.ReferenceType \ |
| 145 | or isinstance(receiver, BoundMethodWeakref): |
| 146 | # Dereference the weak reference. |
| 147 | receiver = receiver() |
| 148 | if receiver is None: |
| 149 | # This receiver is dead, so skip it. |
| 150 | continue |
| 151 | response = _call(receiver, signal=signal, sender=sender, **kwds) |
| 152 | responses += [(receiver, response)] |
| 153 | return responses |
| 154 | |
| 155 | def _call(receiver, **kwds): |
| 156 | """Call receiver with only arguments it can accept.""" |
| 157 | ## if type(receiver) is types.InstanceType: |
| 158 | if hasattr(receiver, '__call__') and \ |
| 159 | (hasattr(receiver.__call__, 'im_func') or hasattr(receiver.__call__, 'im_code')): |
| 160 | # receiver is a class instance; assume it is callable. |
| 161 | # Reassign receiver to the actual method that will be called. |
| 162 | receiver = receiver.__call__ |
| 163 | if hasattr(receiver, 'im_func'): |
| 164 | # receiver is a method. Drop the first argument, usually 'self'. |
| 165 | fc = receiver.im_func.func_code |
| 166 | acceptable = fc.co_varnames[1:fc.co_argcount] |
| 167 | elif hasattr(receiver, 'func_code'): |
| 168 | # receiver is a function. |
| 169 | fc = receiver.func_code |
| 170 | acceptable = fc.co_varnames[0:fc.co_argcount] |
| 171 | else: |
| 172 | raise DispatcherError, 'Unknown receiver %s of type %s' % (receiver, type(receiver)) |
| 173 | if not (fc.co_flags & 8): |
| 174 | # fc does not have a **kwds type parameter, therefore |
| 175 | # remove unacceptable arguments. |
| 176 | for arg in kwds.keys(): |
| 177 | if arg not in acceptable: |
| 178 | del kwds[arg] |
| 179 | return receiver(**kwds) |
| 180 | |
| 181 | |
| 182 | def safeRef(object): |
| 183 | """Return a *safe* weak reference to a callable object.""" |
| 184 | if hasattr(object, 'im_self'): |
| 185 | if object.im_self is not None: |
| 186 | # Turn a bound method into a BoundMethodWeakref instance. |
| 187 | # Keep track of these instances for lookup by disconnect(). |
| 188 | selfkey = object.im_self |
| 189 | funckey = object.im_func |
| 190 | if not _boundMethods.has_key(selfkey): |
| 191 | _boundMethods[selfkey] = weakref.WeakKeyDictionary() |
| 192 | if not _boundMethods[selfkey].has_key(funckey): |
| 193 | _boundMethods[selfkey][funckey] = \ |
| 194 | BoundMethodWeakref(boundMethod=object) |
| 195 | return _boundMethods[selfkey][funckey] |
| 196 | return weakref.ref(object, _removeReceiver) |
| 197 | |
| 198 | |
| 199 | class BoundMethodWeakref: |
| 200 | """BoundMethodWeakref class.""" |
| 201 | |
| 202 | def __init__(self, boundMethod): |
| 203 | """Return a weak-reference-like instance for a bound method.""" |
| 204 | self.isDead = 0 |
| 205 | def remove(object, self=self): |
| 206 | """Set self.isDead to true when method or instance is destroyed.""" |
| 207 | self.isDead = 1 |
| 208 | _removeReceiver(receiver=self) |
| 209 | self.weakSelf = weakref.ref(boundMethod.im_self, remove) |
| 210 | self.weakFunc = weakref.ref(boundMethod.im_func, remove) |
| 211 | |
| 212 | def __repr__(self): |
| 213 | """Return the closest representation.""" |
| 214 | return '<bound method weakref for %s.%s>' % (self.weakSelf, self.weakFunc) |
| 215 | |
| 216 | def __call__(self): |
| 217 | """Return a strong reference to the bound method.""" |
| 218 | if self.isDead: |
| 219 | return None |
| 220 | else: |
| 221 | object = self.weakSelf() |
| 222 | method = self.weakFunc().__name__ |
| 223 | try: # wxPython hack to handle wxDead objects. |
| 224 | return getattr(object, method) |
| 225 | except AttributeError: |
| 226 | ## _removeReceiver(receiver=self) |
| 227 | return None |
| 228 | |
| 229 | |
| 230 | def _removeReceiver(receiver): |
| 231 | """Remove receiver from connections.""" |
| 232 | for senderkey in connections.keys(): |
| 233 | for signal in connections[senderkey].keys(): |
| 234 | receivers = connections[senderkey][signal] |
| 235 | try: |
| 236 | receivers.remove(receiver) |
| 237 | except: |
| 238 | pass |
| 239 | _cleanupConnections(senderkey, signal) |
| 240 | |
| 241 | def _cleanupConnections(senderkey, signal): |
| 242 | """Delete any empty signals for senderkey. Delete senderkey if empty.""" |
| 243 | receivers = connections[senderkey][signal] |
| 244 | if not receivers: |
| 245 | # No more connected receivers. Therefore, remove the signal. |
| 246 | signals = connections[senderkey] |
| 247 | del signals[signal] |
| 248 | if not signals: |
| 249 | # No more signal connections. Therefore, remove the sender. |
| 250 | _removeSender(senderkey) |
| 251 | |
| 252 | def _removeSender(senderkey): |
| 253 | """Remove senderkey from connections.""" |
| 254 | del connections[senderkey] |
| 255 | # Senderkey will only be in senders dictionary if sender |
| 256 | # could be weakly referenced. |
| 257 | try: |
| 258 | del senders[senderkey] |
| 259 | except: |
| 260 | pass |