|
1 """Multiple-producer-multiple-consumer signal-dispatching |
|
2 |
|
3 dispatcher is the core of the PyDispatcher system, |
|
4 providing the primary API and the core logic for the |
|
5 system. |
|
6 |
|
7 Module attributes of note: |
|
8 |
|
9 Any -- Singleton used to signal either "Any Sender" or |
|
10 "Any Signal". See documentation of the _Any class. |
|
11 Anonymous -- Singleton used to signal "Anonymous Sender" |
|
12 See documentation of the _Anonymous class. |
|
13 |
|
14 Internal attributes: |
|
15 WEAKREF_TYPES -- tuple of types/classes which represent |
|
16 weak references to receivers, and thus must be de- |
|
17 referenced on retrieval to retrieve the callable |
|
18 object |
|
19 connections -- { senderkey (id) : { signal : [receivers...]}} |
|
20 senders -- { senderkey (id) : weakref(sender) } |
|
21 used for cleaning up sender references on sender |
|
22 deletion |
|
23 sendersBack -- { receiverkey (id) : [senderkey (id)...] } |
|
24 used for cleaning up receiver references on receiver |
|
25 deletion, (considerably speeds up the cleanup process |
|
26 vs. the original code.) |
|
27 """ |
|
28 import types, weakref |
|
29 from django.dispatch import saferef, robustapply, errors |
|
30 |
|
31 __author__ = "Patrick K. O'Brien <pobrien@orbtech.com>" |
|
32 __cvsid__ = "$Id: dispatcher.py,v 1.9 2005/09/17 04:55:57 mcfletch Exp $" |
|
33 __version__ = "$Revision: 1.9 $"[11:-2] |
|
34 |
|
35 |
|
36 class _Parameter: |
|
37 """Used to represent default parameter values.""" |
|
38 def __repr__(self): |
|
39 return self.__class__.__name__ |
|
40 |
|
41 class _Any(_Parameter): |
|
42 """Singleton used to signal either "Any Sender" or "Any Signal" |
|
43 |
|
44 The Any object can be used with connect, disconnect, |
|
45 send, or sendExact to signal that the parameter given |
|
46 Any should react to all senders/signals, not just |
|
47 a particular sender/signal. |
|
48 """ |
|
49 Any = _Any() |
|
50 |
|
51 class _Anonymous(_Parameter): |
|
52 """Singleton used to signal "Anonymous Sender" |
|
53 |
|
54 The Anonymous object is used to signal that the sender |
|
55 of a message is not specified (as distinct from being |
|
56 "any sender"). Registering callbacks for Anonymous |
|
57 will only receive messages sent without senders. Sending |
|
58 with anonymous will only send messages to those receivers |
|
59 registered for Any or Anonymous. |
|
60 |
|
61 Note: |
|
62 The default sender for connect is Any, while the |
|
63 default sender for send is Anonymous. This has |
|
64 the effect that if you do not specify any senders |
|
65 in either function then all messages are routed |
|
66 as though there was a single sender (Anonymous) |
|
67 being used everywhere. |
|
68 """ |
|
69 Anonymous = _Anonymous() |
|
70 |
|
71 WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref) |
|
72 |
|
73 connections = {} |
|
74 senders = {} |
|
75 sendersBack = {} |
|
76 |
|
77 |
|
78 def connect(receiver, signal=Any, sender=Any, weak=True): |
|
79 """Connect receiver to sender for signal |
|
80 |
|
81 receiver -- a callable Python object which is to receive |
|
82 messages/signals/events. Receivers must be hashable |
|
83 objects. |
|
84 |
|
85 if weak is True, then receiver must be weak-referencable |
|
86 (more precisely saferef.safeRef() must be able to create |
|
87 a reference to the receiver). |
|
88 |
|
89 Receivers are fairly flexible in their specification, |
|
90 as the machinery in the robustApply module takes care |
|
91 of most of the details regarding figuring out appropriate |
|
92 subsets of the sent arguments to apply to a given |
|
93 receiver. |
|
94 |
|
95 Note: |
|
96 if receiver is itself a weak reference (a callable), |
|
97 it will be de-referenced by the system's machinery, |
|
98 so *generally* weak references are not suitable as |
|
99 receivers, though some use might be found for the |
|
100 facility whereby a higher-level library passes in |
|
101 pre-weakrefed receiver references. |
|
102 |
|
103 signal -- the signal to which the receiver should respond |
|
104 |
|
105 if Any, receiver will receive any signal from the |
|
106 indicated sender (which might also be Any, but is not |
|
107 necessarily Any). |
|
108 |
|
109 Otherwise must be a hashable Python object other than |
|
110 None (DispatcherError raised on None). |
|
111 |
|
112 sender -- the sender to which the receiver should respond |
|
113 |
|
114 if Any, receiver will receive the indicated signals |
|
115 from any sender. |
|
116 |
|
117 if Anonymous, receiver will only receive indicated |
|
118 signals from send/sendExact which do not specify a |
|
119 sender, or specify Anonymous explicitly as the sender. |
|
120 |
|
121 Otherwise can be any python object. |
|
122 |
|
123 weak -- whether to use weak references to the receiver |
|
124 By default, the module will attempt to use weak |
|
125 references to the receiver objects. If this parameter |
|
126 is false, then strong references will be used. |
|
127 |
|
128 returns None, may raise DispatcherTypeError |
|
129 """ |
|
130 if signal is None: |
|
131 raise errors.DispatcherTypeError( |
|
132 'Signal cannot be None (receiver=%r sender=%r)'%( receiver,sender) |
|
133 ) |
|
134 if weak: |
|
135 receiver = saferef.safeRef(receiver, onDelete=_removeReceiver) |
|
136 senderkey = id(sender) |
|
137 |
|
138 signals = connections.setdefault(senderkey, {}) |
|
139 |
|
140 # Keep track of senders for cleanup. |
|
141 # Is Anonymous something we want to clean up? |
|
142 if sender not in (None, Anonymous, Any): |
|
143 def remove(object, senderkey=senderkey): |
|
144 _removeSender(senderkey=senderkey) |
|
145 # Skip objects that can not be weakly referenced, which means |
|
146 # they won't be automatically cleaned up, but that's too bad. |
|
147 try: |
|
148 weakSender = weakref.ref(sender, remove) |
|
149 senders[senderkey] = weakSender |
|
150 except: |
|
151 pass |
|
152 |
|
153 receiverID = id(receiver) |
|
154 # get current set, remove any current references to |
|
155 # this receiver in the set, including back-references |
|
156 if signals.has_key(signal): |
|
157 receivers = signals[signal] |
|
158 _removeOldBackRefs(senderkey, signal, receiver, receivers) |
|
159 else: |
|
160 receivers = signals[signal] = [] |
|
161 try: |
|
162 current = sendersBack.get( receiverID ) |
|
163 if current is None: |
|
164 sendersBack[ receiverID ] = current = [] |
|
165 if senderkey not in current: |
|
166 current.append(senderkey) |
|
167 except: |
|
168 pass |
|
169 |
|
170 receivers.append(receiver) |
|
171 |
|
172 |
|
173 |
|
174 def disconnect(receiver, signal=Any, sender=Any, weak=True): |
|
175 """Disconnect receiver from sender for signal |
|
176 |
|
177 receiver -- the registered receiver to disconnect |
|
178 signal -- the registered signal to disconnect |
|
179 sender -- the registered sender to disconnect |
|
180 weak -- the weakref state to disconnect |
|
181 |
|
182 disconnect reverses the process of connect, |
|
183 the semantics for the individual elements are |
|
184 logically equivalent to a tuple of |
|
185 (receiver, signal, sender, weak) used as a key |
|
186 to be deleted from the internal routing tables. |
|
187 (The actual process is slightly more complex |
|
188 but the semantics are basically the same). |
|
189 |
|
190 Note: |
|
191 Using disconnect is not required to cleanup |
|
192 routing when an object is deleted, the framework |
|
193 will remove routes for deleted objects |
|
194 automatically. It's only necessary to disconnect |
|
195 if you want to stop routing to a live object. |
|
196 |
|
197 returns None, may raise DispatcherTypeError or |
|
198 DispatcherKeyError |
|
199 """ |
|
200 if signal is None: |
|
201 raise errors.DispatcherTypeError( |
|
202 'Signal cannot be None (receiver=%r sender=%r)'%( receiver,sender) |
|
203 ) |
|
204 if weak: receiver = saferef.safeRef(receiver) |
|
205 senderkey = id(sender) |
|
206 try: |
|
207 signals = connections[senderkey] |
|
208 receivers = signals[signal] |
|
209 except KeyError: |
|
210 raise errors.DispatcherKeyError( |
|
211 """No receivers found for signal %r from sender %r""" %( |
|
212 signal, |
|
213 sender |
|
214 ) |
|
215 ) |
|
216 try: |
|
217 # also removes from receivers |
|
218 _removeOldBackRefs(senderkey, signal, receiver, receivers) |
|
219 except ValueError: |
|
220 raise errors.DispatcherKeyError( |
|
221 """No connection to receiver %s for signal %s from sender %s""" %( |
|
222 receiver, |
|
223 signal, |
|
224 sender |
|
225 ) |
|
226 ) |
|
227 _cleanupConnections(senderkey, signal) |
|
228 |
|
229 def getReceivers( sender = Any, signal = Any ): |
|
230 """Get list of receivers from global tables |
|
231 |
|
232 This utility function allows you to retrieve the |
|
233 raw list of receivers from the connections table |
|
234 for the given sender and signal pair. |
|
235 |
|
236 Note: |
|
237 there is no guarantee that this is the actual list |
|
238 stored in the connections table, so the value |
|
239 should be treated as a simple iterable/truth value |
|
240 rather than, for instance a list to which you |
|
241 might append new records. |
|
242 |
|
243 Normally you would use liveReceivers( getReceivers( ...)) |
|
244 to retrieve the actual receiver objects as an iterable |
|
245 object. |
|
246 """ |
|
247 existing = connections.get(id(sender)) |
|
248 if existing is not None: |
|
249 return existing.get(signal, []) |
|
250 return [] |
|
251 |
|
252 def liveReceivers(receivers): |
|
253 """Filter sequence of receivers to get resolved, live receivers |
|
254 |
|
255 This is a generator which will iterate over |
|
256 the passed sequence, checking for weak references |
|
257 and resolving them, then returning all live |
|
258 receivers. |
|
259 """ |
|
260 for receiver in receivers: |
|
261 if isinstance( receiver, WEAKREF_TYPES): |
|
262 # Dereference the weak reference. |
|
263 receiver = receiver() |
|
264 if receiver is not None: |
|
265 yield receiver |
|
266 else: |
|
267 yield receiver |
|
268 |
|
269 |
|
270 |
|
271 def getAllReceivers( sender = Any, signal = Any ): |
|
272 """Get list of all receivers from global tables |
|
273 |
|
274 This gets all dereferenced receivers which should receive |
|
275 the given signal from sender, each receiver should |
|
276 be produced only once by the resulting generator |
|
277 """ |
|
278 receivers = {} |
|
279 # Get receivers that receive *this* signal from *this* sender. |
|
280 # Add receivers that receive *any* signal from *this* sender. |
|
281 # Add receivers that receive *this* signal from *any* sender. |
|
282 # Add receivers that receive *any* signal from *any* sender. |
|
283 l = [] |
|
284 i = id(sender) |
|
285 if i in connections: |
|
286 sender_receivers = connections[i] |
|
287 if signal in sender_receivers: |
|
288 l.extend(sender_receivers[signal]) |
|
289 if signal is not Any and Any in sender_receivers: |
|
290 l.extend(sender_receivers[Any]) |
|
291 |
|
292 if sender is not Any: |
|
293 i = id(Any) |
|
294 if i in connections: |
|
295 sender_receivers = connections[i] |
|
296 if sender_receivers is not None: |
|
297 if signal in sender_receivers: |
|
298 l.extend(sender_receivers[signal]) |
|
299 if signal is not Any and Any in sender_receivers: |
|
300 l.extend(sender_receivers[Any]) |
|
301 |
|
302 for receiver in l: |
|
303 try: |
|
304 if not receiver in receivers: |
|
305 if isinstance(receiver, WEAKREF_TYPES): |
|
306 receiver = receiver() |
|
307 # this should only (rough guess) be possible if somehow, deref'ing |
|
308 # triggered a wipe. |
|
309 if receiver is None: |
|
310 continue |
|
311 receivers[receiver] = 1 |
|
312 yield receiver |
|
313 except TypeError: |
|
314 # dead weakrefs raise TypeError on hash... |
|
315 pass |
|
316 |
|
317 def send(signal=Any, sender=Anonymous, *arguments, **named): |
|
318 """Send signal from sender to all connected receivers. |
|
319 |
|
320 signal -- (hashable) signal value, see connect for details |
|
321 |
|
322 sender -- the sender of the signal |
|
323 |
|
324 if Any, only receivers registered for Any will receive |
|
325 the message. |
|
326 |
|
327 if Anonymous, only receivers registered to receive |
|
328 messages from Anonymous or Any will receive the message |
|
329 |
|
330 Otherwise can be any python object (normally one |
|
331 registered with a connect if you actually want |
|
332 something to occur). |
|
333 |
|
334 arguments -- positional arguments which will be passed to |
|
335 *all* receivers. Note that this may raise TypeErrors |
|
336 if the receivers do not allow the particular arguments. |
|
337 Note also that arguments are applied before named |
|
338 arguments, so they should be used with care. |
|
339 |
|
340 named -- named arguments which will be filtered according |
|
341 to the parameters of the receivers to only provide those |
|
342 acceptable to the receiver. |
|
343 |
|
344 Return a list of tuple pairs [(receiver, response), ... ] |
|
345 |
|
346 if any receiver raises an error, the error propagates back |
|
347 through send, terminating the dispatch loop, so it is quite |
|
348 possible to not have all receivers called if a raises an |
|
349 error. |
|
350 """ |
|
351 # Call each receiver with whatever arguments it can accept. |
|
352 # Return a list of tuple pairs [(receiver, response), ... ]. |
|
353 responses = [] |
|
354 for receiver in getAllReceivers(sender, signal): |
|
355 response = robustapply.robustApply( |
|
356 receiver, |
|
357 signal=signal, |
|
358 sender=sender, |
|
359 *arguments, |
|
360 **named |
|
361 ) |
|
362 responses.append((receiver, response)) |
|
363 return responses |
|
364 |
|
365 |
|
366 def sendExact( signal=Any, sender=Anonymous, *arguments, **named ): |
|
367 """Send signal only to those receivers registered for exact message |
|
368 |
|
369 sendExact allows for avoiding Any/Anonymous registered |
|
370 handlers, sending only to those receivers explicitly |
|
371 registered for a particular signal on a particular |
|
372 sender. |
|
373 """ |
|
374 responses = [] |
|
375 for receiver in liveReceivers(getReceivers(sender, signal)): |
|
376 response = robustapply.robustApply( |
|
377 receiver, |
|
378 signal=signal, |
|
379 sender=sender, |
|
380 *arguments, |
|
381 **named |
|
382 ) |
|
383 responses.append((receiver, response)) |
|
384 return responses |
|
385 |
|
386 |
|
387 def _removeReceiver(receiver): |
|
388 """Remove receiver from connections.""" |
|
389 if not sendersBack: |
|
390 # During module cleanup the mapping will be replaced with None |
|
391 return False |
|
392 backKey = id(receiver) |
|
393 for senderkey in sendersBack.get(backKey,()): |
|
394 try: |
|
395 signals = connections[senderkey].keys() |
|
396 except KeyError,err: |
|
397 pass |
|
398 else: |
|
399 for signal in signals: |
|
400 try: |
|
401 receivers = connections[senderkey][signal] |
|
402 except KeyError: |
|
403 pass |
|
404 else: |
|
405 try: |
|
406 receivers.remove( receiver ) |
|
407 except Exception, err: |
|
408 pass |
|
409 _cleanupConnections(senderkey, signal) |
|
410 try: |
|
411 del sendersBack[ backKey ] |
|
412 except KeyError: |
|
413 pass |
|
414 |
|
415 def _cleanupConnections(senderkey, signal): |
|
416 """Delete any empty signals for senderkey. Delete senderkey if empty.""" |
|
417 try: |
|
418 receivers = connections[senderkey][signal] |
|
419 except: |
|
420 pass |
|
421 else: |
|
422 if not receivers: |
|
423 # No more connected receivers. Therefore, remove the signal. |
|
424 try: |
|
425 signals = connections[senderkey] |
|
426 except KeyError: |
|
427 pass |
|
428 else: |
|
429 del signals[signal] |
|
430 if not signals: |
|
431 # No more signal connections. Therefore, remove the sender. |
|
432 _removeSender(senderkey) |
|
433 |
|
434 def _removeSender(senderkey): |
|
435 """Remove senderkey from connections.""" |
|
436 _removeBackrefs(senderkey) |
|
437 |
|
438 connections.pop(senderkey, None) |
|
439 senders.pop(senderkey, None) |
|
440 |
|
441 |
|
442 def _removeBackrefs( senderkey): |
|
443 """Remove all back-references to this senderkey""" |
|
444 for receiver_list in connections.pop(senderkey, {}).values(): |
|
445 for receiver in receiver_list: |
|
446 _killBackref( receiver, senderkey ) |
|
447 |
|
448 |
|
449 def _removeOldBackRefs(senderkey, signal, receiver, receivers): |
|
450 """Kill old sendersBack references from receiver |
|
451 |
|
452 This guards against multiple registration of the same |
|
453 receiver for a given signal and sender leaking memory |
|
454 as old back reference records build up. |
|
455 |
|
456 Also removes old receiver instance from receivers |
|
457 """ |
|
458 try: |
|
459 index = receivers.index(receiver) |
|
460 # need to scan back references here and remove senderkey |
|
461 except ValueError: |
|
462 return False |
|
463 else: |
|
464 oldReceiver = receivers[index] |
|
465 del receivers[index] |
|
466 found = 0 |
|
467 signals = connections.get(signal) |
|
468 if signals is not None: |
|
469 for sig,recs in connections.get(signal,{}).iteritems(): |
|
470 if sig != signal: |
|
471 for rec in recs: |
|
472 if rec is oldReceiver: |
|
473 found = 1 |
|
474 break |
|
475 if not found: |
|
476 _killBackref( oldReceiver, senderkey ) |
|
477 return True |
|
478 return False |
|
479 |
|
480 |
|
481 def _killBackref( receiver, senderkey ): |
|
482 """Do the actual removal of back reference from receiver to senderkey""" |
|
483 receiverkey = id(receiver) |
|
484 receivers_list = sendersBack.get( receiverkey, () ) |
|
485 while senderkey in receivers_list: |
|
486 try: |
|
487 receivers_list.remove( senderkey ) |
|
488 except: |
|
489 break |
|
490 if not receivers_list: |
|
491 try: |
|
492 del sendersBack[ receiverkey ] |
|
493 except KeyError: |
|
494 pass |
|
495 return True |