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