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

1import logging 

2import threading 

3import weakref 

4 

5from django.utils.inspect import func_accepts_kwargs 

6 

7logger = logging.getLogger("django.dispatch") 

8 

9 

10def _make_id(target): 

11 if hasattr(target, "__func__"): 

12 return (id(target.__self__), id(target.__func__)) 

13 return id(target) 

14 

15 

16NONE_ID = _make_id(None) 

17 

18# A marker for caching 

19NO_RECEIVERS = object() 

20 

21 

22class Signal: 

23 """ 

24 Base class for all signals 

25 

26 Internal attributes: 

27 

28 receivers 

29 { receiverkey (id) : weakref(receiver) } 

30 """ 

31 

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 

46 

47 def connect(self, receiver, sender=None, weak=True, dispatch_uid=None): 

48 """ 

49 Connect receiver to sender for signal. 

50 

51 Arguments: 

52 

53 receiver 

54 A function or an instance method which is to receive signals. 

55 Receivers must be hashable objects. 

56 

57 If weak is True, then receiver must be weak referenceable. 

58 

59 Receivers must be able to accept keyword arguments. 

60 

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. 

64 

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. 

68 

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. 

74 

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 

81 

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 ) 

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: 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) 

106 

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() 

112 

113 def disconnect(self, receiver=None, sender=None, dispatch_uid=None): 

114 """ 

115 Disconnect receiver from sender for signal. 

116 

117 If weak references are used, disconnect need not be called. The receiver 

118 will be removed from dispatch automatically. 

119 

120 Arguments: 

121 

122 receiver 

123 The registered receiver to disconnect. May be none if 

124 dispatch_uid is specified. 

125 

126 sender 

127 The registered sender 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 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 

148 

149 def has_listeners(self, sender=None): 

150 return bool(self._live_receivers(sender)) 

151 

152 def send(self, sender, **named): 

153 """ 

154 Send signal from sender to all connected receivers. 

155 

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. 

159 

160 Arguments: 

161 

162 sender 

163 The sender of the signal. Either a specific object or None. 

164 

165 named 

166 Named arguments which will be passed to receivers. 

167 

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 [] 

175 

176 return [ 

177 (receiver, receiver(signal=self, sender=sender, **named)) 

178 for receiver in self._live_receivers(sender) 

179 ] 

180 

181 def send_robust(self, sender, **named): 

182 """ 

183 Send signal from sender to all connected receivers catching errors. 

184 

185 Arguments: 

186 

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). 

191 

192 named 

193 Named arguments which will be passed to receivers. 

194 

195 Return a list of tuple pairs [(receiver, response), ... ]. 

196 

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 [] 

205 

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 

223 

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 ] 

233 

234 def _live_receivers(self, sender): 

235 """ 

236 Filter sequence of receivers to get resolved, live receivers. 

237 

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 

272 

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 

281 

282 

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:: 

287 

288 @receiver(post_save, sender=MyModel) 

289 def signal_receiver(sender, **kwargs): 

290 ... 

291 

292 @receiver([post_save, post_delete], sender=MyModel) 

293 def signals_receiver(sender, **kwargs): 

294 ... 

295 """ 

296 

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 

304 

305 return _decorator