]>
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 | """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 |