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