Django

Code

root/django/branches/newforms-admin/django/dispatch/dispatcher.py

Revision 4617, 16.8 kB (checked in by adrian, 2 years ago)

newforms-admin: Merged to [4616]

  • Property svn:eol-style set to native
Line 
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
Note: See TracBrowser for help on using the browser.