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