Django

Code

root/django/trunk/django/dispatch/dispatcher.py

Revision 8546, 7.7 kB (checked in by jacob, 3 months ago)

Fixed #8285: signal handlers that aren't functions work under DEBUG. This slightly loosens the sanity check, but things that are valid under production shouldn't fail under debug.

  • Property svn:eol-style set to native
Line 
1 import weakref
2 try:
3     set
4 except NameError:
5     from sets import Set as set # Python 2.3 fallback
6
7 from django.dispatch import saferef
8
9 WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref)
10
11 def _make_id(target):
12     if hasattr(target, 'im_func'):
13         return (id(target.im_self), id(target.im_func))
14     return id(target)
15
16 class Signal(object):
17     """Base class for all signals
18     
19     Internal attributes:
20         receivers -- { receriverkey (id) : weakref(receiver) }
21     """
22    
23     def __init__(self, providing_args=None):
24         """providing_args -- A list of the arguments this signal can pass along in
25                        a send() call.
26         """
27         self.receivers = []
28         if providing_args is None:
29             providing_args = []
30         self.providing_args = set(providing_args)
31
32     def connect(self, receiver, sender=None, weak=True, dispatch_uid=None):
33         """Connect receiver to sender for signal
34     
35         receiver -- a function or an instance method which is to
36             receive signals.  Receivers must be
37             hashable objects.
38
39             if weak is True, then receiver must be weak-referencable
40             (more precisely saferef.safeRef() must be able to create
41             a reference to the receiver).
42         
43             Receivers must be able to accept keyword arguments.
44
45             If receivers have a dispatch_uid attribute, the receiver will
46               not be added if another receiver already exists with that
47               dispatch_uid.
48
49         sender -- the sender to which the receiver should respond
50             Must either be of type Signal, or None to receive events
51             from any sender.
52
53         weak -- whether to use weak references to the receiver
54             By default, the module will attempt to use weak
55             references to the receiver objects.  If this parameter
56             is false, then strong references will be used.
57         
58         dispatch_uid -- an identifier used to uniquely identify a particular
59             instance of a receiver. This will usually be a string, though it
60             may be anything hashable.
61
62         returns None
63         """
64         from django.conf import settings
65        
66         # If DEBUG is on, check that we got a good receiver
67         if settings.DEBUG:
68             import inspect
69             assert callable(receiver), "Signal receivers must be callable."
70            
71             # Check for **kwargs
72             # Not all callables are inspectable with getargspec, so we'll
73             # try a couple different ways but in the end fall back on assuming
74             # it is -- we don't want to prevent registration of valid but weird
75             # callables.
76             try:
77                 argspec = inspect.getargspec(receiver)
78             except TypeError:
79                 try:
80                     argspec = inspect.getargspec(receiver.__call__)
81                 except (TypeError, AttributeError):
82                     argspec = None
83             if argspec:
84                 assert argspec[2] is not None, \
85                     "Signal receivers must accept keyword arguments (**kwargs)."
86        
87         if dispatch_uid:
88             lookup_key = (dispatch_uid, _make_id(sender))
89         else:
90             lookup_key = (_make_id(receiver), _make_id(sender))
91
92         if weak:
93             receiver = saferef.safeRef(receiver, onDelete=self._remove_receiver)
94
95         for r_key, _ in self.receivers:
96             if r_key == lookup_key:
97                 break
98         else:
99             self.receivers.append((lookup_key, receiver))
100
101     def disconnect(self, receiver=None, sender=None, weak=True, dispatch_uid=None):
102         """Disconnect receiver from sender for signal
103     
104         receiver -- the registered receiver to disconnect. May be none if
105             dispatch_uid is specified.
106         sender -- the registered sender to disconnect
107         weak -- the weakref state to disconnect
108         dispatch_uid -- the unique identifier of the receiver to disconnect
109     
110         disconnect reverses the process of connect.
111
112         If weak references are used, disconnect need not be called.
113           The receiver will be remove from dispatch automatically.
114
115         returns None
116         """
117
118         if dispatch_uid:
119             lookup_key = (dispatch_uid, _make_id(sender))
120         else:
121             lookup_key = (_make_id(receiver), _make_id(sender))
122
123         for idx, (r_key, _) in enumerate(self.receivers):
124             if r_key == lookup_key:
125                 del self.receivers[idx]
126
127     def send(self, sender, **named):
128         """Send signal from sender to all connected receivers.
129
130         sender -- the sender of the signal
131             Either a specific object or None.
132     
133         named -- named arguments which will be passed to receivers.
134
135         Returns a list of tuple pairs [(receiver, response), ... ].
136
137         If any receiver raises an error, the error propagates back
138         through send, terminating the dispatch loop, so it is quite
139         possible to not have all receivers called if a raises an
140         error.
141         """
142
143         responses = []
144         if not self.receivers:
145             return responses
146
147         for receiver in self._live_receivers(_make_id(sender)):
148             response = receiver(signal=self, sender=sender, **named)
149             responses.append((receiver, response))
150         return responses
151
152     def send_robust(self, sender, **named):
153         """Send signal from sender to all connected receivers catching errors
154
155         sender -- the sender of the signal
156             Can be any python object (normally one registered with
157             a connect if you actually want something to occur).
158
159         named -- named arguments which will be passed to receivers.
160             These arguments must be a subset of the argument names
161             defined in providing_args.
162
163         Return a list of tuple pairs [(receiver, response), ... ],
164         may raise DispatcherKeyError
165
166         if any receiver raises an error (specifically any subclass of Exception),
167         the error instance is returned as the result for that receiver.
168         """
169
170         responses = []
171         if not self.receivers:
172             return responses
173
174         # Call each receiver with whatever arguments it can accept.
175         # Return a list of tuple pairs [(receiver, response), ... ].
176         for receiver in self._live_receivers(_make_id(sender)):
177             try:
178                 response = receiver(signal=self, sender=sender, **named)
179             except Exception, err:
180                 responses.append((receiver, err))
181             else:
182                 responses.append((receiver, response))
183         return responses
184
185     def _live_receivers(self, senderkey):
186         """Filter sequence of receivers to get resolved, live receivers
187
188         This checks for weak references
189         and resolves them, then returning only live
190         receivers.
191         """
192         none_senderkey = _make_id(None)
193
194         for (receiverkey, r_senderkey), receiver in self.receivers:
195             if r_senderkey == none_senderkey or r_senderkey == senderkey:
196                 if isinstance(receiver, WEAKREF_TYPES):
197                     # Dereference the weak reference.
198                     receiver = receiver()
199                     if receiver is not None:
200                         yield receiver
201                 else:
202                     yield receiver
203
204     def _remove_receiver(self, receiver):
205         """Remove dead receivers from connections."""
206
207         to_remove = []
208         for key, connected_receiver in self.receivers:
209             if connected_receiver == receiver:
210                 to_remove.append(key)
211         for key in to_remove:
212             for idx, (r_key, _) in enumerate(self.receivers):
213                 if r_key == key:
214                     del self.receivers[idx]
Note: See TracBrowser for help on using the browser.