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