Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django/dispatch/dispatcher.py: 81%
114 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1import logging
2import threading
3import weakref
5from django.utils.inspect import func_accepts_kwargs
7logger = logging.getLogger("django.dispatch")
10def _make_id(target):
11 if hasattr(target, "__func__"):
12 return (id(target.__self__), id(target.__func__))
13 return id(target)
16NONE_ID = _make_id(None)
18# A marker for caching
19NO_RECEIVERS = object()
22class Signal:
23 """
24 Base class for all signals
26 Internal attributes:
28 receivers
29 { receiverkey (id) : weakref(receiver) }
30 """
32 def __init__(self, use_caching=False):
33 """
34 Create a new signal.
35 """
36 self.receivers = []
37 self.lock = threading.Lock()
38 self.use_caching = use_caching
39 # For convenience we create empty caches even if they are not used.
40 # A note about caching: if use_caching is defined, then for each
41 # distinct sender we cache the receivers that sender has in
42 # 'sender_receivers_cache'. The cache is cleaned when .connect() or
43 # .disconnect() is called and populated on send().
44 self.sender_receivers_cache = weakref.WeakKeyDictionary() if use_caching else {}
45 self._dead_receivers = False
47 def connect(self, receiver, sender=None, weak=True, dispatch_uid=None):
48 """
49 Connect receiver to sender for signal.
51 Arguments:
53 receiver
54 A function or an instance method which is to receive signals.
55 Receivers must be hashable objects.
57 If weak is True, then receiver must be weak referenceable.
59 Receivers must be able to accept keyword arguments.
61 If a receiver is connected with a dispatch_uid argument, it
62 will not be added if another receiver was already connected
63 with that dispatch_uid.
65 sender
66 The sender to which the receiver should respond. Must either be
67 a Python object, or None to receive events from any sender.
69 weak
70 Whether to use weak references to the receiver. By default, the
71 module will attempt to use weak references to the receiver
72 objects. If this parameter is false, then strong references will
73 be used.
75 dispatch_uid
76 An identifier used to uniquely identify a particular instance of
77 a receiver. This will usually be a string, though it may be
78 anything hashable.
79 """
80 from django.conf import settings
82 # If DEBUG is on, check that we got a good receiver
83 if settings.configured and settings.DEBUG: 83 ↛ 84line 83 didn't jump to line 84, because the condition on line 83 was never true
84 if not callable(receiver):
85 raise TypeError("Signal receivers must be callable.")
86 # Check for **kwargs
87 if not func_accepts_kwargs(receiver):
88 raise ValueError(
89 "Signal receivers must accept keyword arguments (**kwargs)."
90 )
92 if dispatch_uid:
93 lookup_key = (dispatch_uid, _make_id(sender))
94 else:
95 lookup_key = (_make_id(receiver), _make_id(sender))
97 if weak: 97 ↛ 107line 97 didn't jump to line 107, because the condition on line 97 was never false
98 ref = weakref.ref
99 receiver_object = receiver
100 # Check for bound methods
101 if hasattr(receiver, "__self__") and hasattr(receiver, "__func__"):
102 ref = weakref.WeakMethod
103 receiver_object = receiver.__self__
104 receiver = ref(receiver)
105 weakref.finalize(receiver_object, self._remove_receiver)
107 with self.lock:
108 self._clear_dead_receivers()
109 if not any(r_key == lookup_key for r_key, _ in self.receivers):
110 self.receivers.append((lookup_key, receiver))
111 self.sender_receivers_cache.clear()
113 def disconnect(self, receiver=None, sender=None, dispatch_uid=None):
114 """
115 Disconnect receiver from sender for signal.
117 If weak references are used, disconnect need not be called. The receiver
118 will be removed from dispatch automatically.
120 Arguments:
122 receiver
123 The registered receiver to disconnect. May be none if
124 dispatch_uid is specified.
126 sender
127 The registered sender to disconnect
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))
137 disconnected = False
138 with self.lock:
139 self._clear_dead_receivers()
140 for index in range(len(self.receivers)): 140 ↛ 146line 140 didn't jump to line 146, because the loop on line 140 didn't complete
141 (r_key, _) = self.receivers[index]
142 if r_key == lookup_key:
143 disconnected = True
144 del self.receivers[index]
145 break
146 self.sender_receivers_cache.clear()
147 return disconnected
149 def has_listeners(self, sender=None):
150 return bool(self._live_receivers(sender))
152 def send(self, sender, **named):
153 """
154 Send signal from sender to all connected receivers.
156 If any receiver raises an error, the error propagates back through send,
157 terminating the dispatch loop. So it's possible that all receivers
158 won't be called if an error is raised.
160 Arguments:
162 sender
163 The sender of the signal. Either a specific object or None.
165 named
166 Named arguments which will be passed to receivers.
168 Return a list of tuple pairs [(receiver, response), ... ].
169 """
170 if (
171 not self.receivers
172 or self.sender_receivers_cache.get(sender) is NO_RECEIVERS
173 ):
174 return []
176 return [
177 (receiver, receiver(signal=self, sender=sender, **named))
178 for receiver in self._live_receivers(sender)
179 ]
181 def send_robust(self, sender, **named):
182 """
183 Send signal from sender to all connected receivers catching errors.
185 Arguments:
187 sender
188 The sender of the signal. Can be any Python object (normally one
189 registered with a connect if you actually want something to
190 occur).
192 named
193 Named arguments which will be passed to receivers.
195 Return a list of tuple pairs [(receiver, response), ... ].
197 If any receiver raises an error (specifically any subclass of
198 Exception), return the error instance as the result for that receiver.
199 """
200 if (
201 not self.receivers
202 or self.sender_receivers_cache.get(sender) is NO_RECEIVERS
203 ):
204 return []
206 # Call each receiver with whatever arguments it can accept.
207 # Return a list of tuple pairs [(receiver, response), ... ].
208 responses = []
209 for receiver in self._live_receivers(sender):
210 try:
211 response = receiver(signal=self, sender=sender, **named)
212 except Exception as err:
213 logger.error(
214 "Error calling %s in Signal.send_robust() (%s)",
215 receiver.__qualname__,
216 err,
217 exc_info=err,
218 )
219 responses.append((receiver, err))
220 else:
221 responses.append((receiver, response))
222 return responses
224 def _clear_dead_receivers(self):
225 # Note: caller is assumed to hold self.lock.
226 if self._dead_receivers:
227 self._dead_receivers = False
228 self.receivers = [
229 r
230 for r in self.receivers
231 if not (isinstance(r[1], weakref.ReferenceType) and r[1]() is None)
232 ]
234 def _live_receivers(self, sender):
235 """
236 Filter sequence of receivers to get resolved, live receivers.
238 This checks for weak references and resolves them, then returning only
239 live receivers.
240 """
241 receivers = None
242 if self.use_caching and not self._dead_receivers:
243 receivers = self.sender_receivers_cache.get(sender)
244 # We could end up here with NO_RECEIVERS even if we do check this case in
245 # .send() prior to calling _live_receivers() due to concurrent .send() call.
246 if receivers is NO_RECEIVERS:
247 return []
248 if receivers is None:
249 with self.lock:
250 self._clear_dead_receivers()
251 senderkey = _make_id(sender)
252 receivers = []
253 for (receiverkey, r_senderkey), receiver in self.receivers:
254 if r_senderkey == NONE_ID or r_senderkey == senderkey:
255 receivers.append(receiver)
256 if self.use_caching:
257 if not receivers:
258 self.sender_receivers_cache[sender] = NO_RECEIVERS
259 else:
260 # Note, we must cache the weakref versions.
261 self.sender_receivers_cache[sender] = receivers
262 non_weak_receivers = []
263 for receiver in receivers:
264 if isinstance(receiver, weakref.ReferenceType): 264 ↛ 270line 264 didn't jump to line 270, because the condition on line 264 was never false
265 # Dereference the weak reference.
266 receiver = receiver()
267 if receiver is not None:
268 non_weak_receivers.append(receiver)
269 else:
270 non_weak_receivers.append(receiver)
271 return non_weak_receivers
273 def _remove_receiver(self, receiver=None):
274 # Mark that the self.receivers list has dead weakrefs. If so, we will
275 # clean those up in connect, disconnect and _live_receivers while
276 # holding self.lock. Note that doing the cleanup here isn't a good
277 # idea, _remove_receiver() will be called as side effect of garbage
278 # collection, and so the call can happen while we are already holding
279 # self.lock.
280 self._dead_receivers = True
283def receiver(signal, **kwargs):
284 """
285 A decorator for connecting receivers to signals. Used by passing in the
286 signal (or list of signals) and keyword arguments to connect::
288 @receiver(post_save, sender=MyModel)
289 def signal_receiver(sender, **kwargs):
290 ...
292 @receiver([post_save, post_delete], sender=MyModel)
293 def signals_receiver(sender, **kwargs):
294 ...
295 """
297 def _decorator(func):
298 if isinstance(signal, (list, tuple)): 298 ↛ 299line 298 didn't jump to line 299, because the condition on line 298 was never true
299 for s in signal:
300 s.connect(func, **kwargs)
301 else:
302 signal.connect(func, **kwargs)
303 return func
305 return _decorator