Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/SQLAlchemy-1.3.25.dev0-py3.11-linux-x86_64.egg/sqlalchemy/event/attr.py: 62%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

238 statements  

1# event/attr.py 

2# Copyright (C) 2005-2021 the SQLAlchemy authors and contributors 

3# <see AUTHORS file> 

4# 

5# This module is part of SQLAlchemy and is released under 

6# the MIT License: http://www.opensource.org/licenses/mit-license.php 

7 

8"""Attribute implementation for _Dispatch classes. 

9 

10The various listener targets for a particular event class are represented 

11as attributes, which refer to collections of listeners to be fired off. 

12These collections can exist at the class level as well as at the instance 

13level. An event is fired off using code like this:: 

14 

15 some_object.dispatch.first_connect(arg1, arg2) 

16 

17Above, ``some_object.dispatch`` would be an instance of ``_Dispatch`` and 

18``first_connect`` is typically an instance of ``_ListenerCollection`` 

19if event listeners are present, or ``_EmptyListener`` if none are present. 

20 

21The attribute mechanics here spend effort trying to ensure listener functions 

22are available with a minimum of function call overhead, that unnecessary 

23objects aren't created (i.e. many empty per-instance listener collections), 

24as well as that everything is garbage collectable when owning references are 

25lost. Other features such as "propagation" of listener functions across 

26many ``_Dispatch`` instances, "joining" of multiple ``_Dispatch`` instances, 

27as well as support for subclass propagation (e.g. events assigned to 

28``Pool`` vs. ``QueuePool``) are all implemented here. 

29 

30""" 

31 

32from __future__ import absolute_import 

33from __future__ import with_statement 

34 

35import collections 

36from itertools import chain 

37import weakref 

38 

39from . import legacy 

40from . import registry 

41from .. import exc 

42from .. import util 

43from ..util import threading 

44 

45 

46class RefCollection(util.MemoizedSlots): 

47 __slots__ = ("ref",) 

48 

49 def _memoized_attr_ref(self): 

50 return weakref.ref(self, registry._collection_gced) 

51 

52 

53class _empty_collection(object): 

54 def append(self, element): 

55 pass 

56 

57 def extend(self, other): 

58 pass 

59 

60 def remove(self, element): 

61 pass 

62 

63 def __iter__(self): 

64 return iter([]) 

65 

66 def clear(self): 

67 pass 

68 

69 

70class _ClsLevelDispatch(RefCollection): 

71 """Class-level events on :class:`._Dispatch` classes.""" 

72 

73 __slots__ = ( 

74 "name", 

75 "arg_names", 

76 "has_kw", 

77 "legacy_signatures", 

78 "_clslevel", 

79 "__weakref__", 

80 ) 

81 

82 def __init__(self, parent_dispatch_cls, fn): 

83 self.name = fn.__name__ 

84 argspec = util.inspect_getfullargspec(fn) 

85 self.arg_names = argspec.args[1:] 

86 self.has_kw = bool(argspec.varkw) 

87 self.legacy_signatures = list( 

88 reversed( 

89 sorted( 

90 getattr(fn, "_legacy_signatures", []), key=lambda s: s[0] 

91 ) 

92 ) 

93 ) 

94 fn.__doc__ = legacy._augment_fn_docs(self, parent_dispatch_cls, fn) 

95 

96 self._clslevel = weakref.WeakKeyDictionary() 

97 

98 def _adjust_fn_spec(self, fn, named): 

99 if named: 

100 fn = self._wrap_fn_for_kw(fn) 

101 if self.legacy_signatures: 

102 try: 

103 argspec = util.get_callable_argspec(fn, no_self=True) 

104 except TypeError: 

105 pass 

106 else: 

107 fn = legacy._wrap_fn_for_legacy(self, fn, argspec) 

108 return fn 

109 

110 def _wrap_fn_for_kw(self, fn): 

111 def wrap_kw(*args, **kw): 

112 argdict = dict(zip(self.arg_names, args)) 

113 argdict.update(kw) 

114 return fn(**argdict) 

115 

116 return wrap_kw 

117 

118 def insert(self, event_key, propagate): 

119 target = event_key.dispatch_target 

120 assert isinstance( 

121 target, type 

122 ), "Class-level Event targets must be classes." 

123 if not getattr(target, "_sa_propagate_class_events", True): 

124 raise exc.InvalidRequestError( 

125 "Can't assign an event directly to the %s class" % target 

126 ) 

127 stack = [target] 

128 while stack: 

129 cls = stack.pop(0) 

130 stack.extend(cls.__subclasses__()) 

131 if cls is not target and cls not in self._clslevel: 

132 self.update_subclass(cls) 

133 else: 

134 if cls not in self._clslevel: 

135 self._assign_cls_collection(cls) 

136 self._clslevel[cls].appendleft(event_key._listen_fn) 

137 registry._stored_in_collection(event_key, self) 

138 

139 def append(self, event_key, propagate): 

140 target = event_key.dispatch_target 

141 assert isinstance( 

142 target, type 

143 ), "Class-level Event targets must be classes." 

144 if not getattr(target, "_sa_propagate_class_events", True): 

145 raise exc.InvalidRequestError( 

146 "Can't assign an event directly to the %s class" % target 

147 ) 

148 stack = [target] 

149 while stack: 

150 cls = stack.pop(0) 

151 stack.extend(cls.__subclasses__()) 

152 if cls is not target and cls not in self._clslevel: 

153 self.update_subclass(cls) 

154 else: 

155 if cls not in self._clslevel: 

156 self._assign_cls_collection(cls) 

157 self._clslevel[cls].append(event_key._listen_fn) 

158 registry._stored_in_collection(event_key, self) 

159 

160 def _assign_cls_collection(self, target): 

161 if getattr(target, "_sa_propagate_class_events", True): 

162 self._clslevel[target] = collections.deque() 

163 else: 

164 self._clslevel[target] = _empty_collection() 

165 

166 def update_subclass(self, target): 

167 if target not in self._clslevel: 

168 self._assign_cls_collection(target) 

169 clslevel = self._clslevel[target] 

170 for cls in target.__mro__[1:]: 

171 if cls in self._clslevel: 

172 clslevel.extend( 

173 [fn for fn in self._clslevel[cls] if fn not in clslevel] 

174 ) 

175 

176 def remove(self, event_key): 

177 target = event_key.dispatch_target 

178 stack = [target] 

179 while stack: 

180 cls = stack.pop(0) 

181 stack.extend(cls.__subclasses__()) 

182 if cls in self._clslevel: 

183 self._clslevel[cls].remove(event_key._listen_fn) 

184 registry._removed_from_collection(event_key, self) 

185 

186 def clear(self): 

187 """Clear all class level listeners""" 

188 

189 to_clear = set() 

190 for dispatcher in self._clslevel.values(): 

191 to_clear.update(dispatcher) 

192 dispatcher.clear() 

193 registry._clear(self, to_clear) 

194 

195 def for_modify(self, obj): 

196 """Return an event collection which can be modified. 

197 

198 For _ClsLevelDispatch at the class level of 

199 a dispatcher, this returns self. 

200 

201 """ 

202 return self 

203 

204 

205class _InstanceLevelDispatch(RefCollection): 

206 __slots__ = () 

207 

208 def _adjust_fn_spec(self, fn, named): 

209 return self.parent._adjust_fn_spec(fn, named) 

210 

211 

212class _EmptyListener(_InstanceLevelDispatch): 

213 """Serves as a proxy interface to the events 

214 served by a _ClsLevelDispatch, when there are no 

215 instance-level events present. 

216 

217 Is replaced by _ListenerCollection when instance-level 

218 events are added. 

219 

220 """ 

221 

222 propagate = frozenset() 

223 listeners = () 

224 

225 __slots__ = "parent", "parent_listeners", "name" 

226 

227 def __init__(self, parent, target_cls): 

228 if target_cls not in parent._clslevel: 

229 parent.update_subclass(target_cls) 

230 self.parent = parent # _ClsLevelDispatch 

231 self.parent_listeners = parent._clslevel[target_cls] 

232 self.name = parent.name 

233 

234 def for_modify(self, obj): 

235 """Return an event collection which can be modified. 

236 

237 For _EmptyListener at the instance level of 

238 a dispatcher, this generates a new 

239 _ListenerCollection, applies it to the instance, 

240 and returns it. 

241 

242 """ 

243 result = _ListenerCollection(self.parent, obj._instance_cls) 

244 if getattr(obj, self.name) is self: 

245 setattr(obj, self.name, result) 

246 else: 

247 assert isinstance(getattr(obj, self.name), _JoinedListener) 

248 return result 

249 

250 def _needs_modify(self, *args, **kw): 

251 raise NotImplementedError("need to call for_modify()") 

252 

253 exec_once = ( 

254 exec_once_unless_exception 

255 ) = insert = append = remove = clear = _needs_modify 

256 

257 def __call__(self, *args, **kw): 

258 """Execute this event.""" 

259 

260 for fn in self.parent_listeners: 

261 fn(*args, **kw) 

262 

263 def __len__(self): 

264 return len(self.parent_listeners) 

265 

266 def __iter__(self): 

267 return iter(self.parent_listeners) 

268 

269 def __bool__(self): 

270 return bool(self.parent_listeners) 

271 

272 __nonzero__ = __bool__ 

273 

274 

275class _CompoundListener(_InstanceLevelDispatch): 

276 __slots__ = "_exec_once_mutex", "_exec_once" 

277 

278 def _memoized_attr__exec_once_mutex(self): 

279 return threading.Lock() 

280 

281 def _exec_once_impl(self, retry_on_exception, *args, **kw): 

282 with self._exec_once_mutex: 

283 if not self._exec_once: 

284 try: 

285 self(*args, **kw) 

286 exception = False 

287 except: 

288 exception = True 

289 raise 

290 finally: 

291 if not exception or not retry_on_exception: 

292 self._exec_once = True 

293 

294 def exec_once(self, *args, **kw): 

295 """Execute this event, but only if it has not been 

296 executed already for this collection.""" 

297 

298 if not self._exec_once: 

299 self._exec_once_impl(False, *args, **kw) 

300 

301 def exec_once_unless_exception(self, *args, **kw): 

302 """Execute this event, but only if it has not been 

303 executed already for this collection, or was called 

304 by a previous exec_once_unless_exception call and 

305 raised an exception. 

306 

307 If exec_once was already called, then this method will never run 

308 the callable regardless of whether it raised or not. 

309 

310 .. versionadded:: 1.3.8 

311 

312 """ 

313 if not self._exec_once: 

314 self._exec_once_impl(True, *args, **kw) 

315 

316 def __call__(self, *args, **kw): 

317 """Execute this event.""" 

318 

319 for fn in self.parent_listeners: 

320 fn(*args, **kw) 

321 for fn in self.listeners: 

322 fn(*args, **kw) 

323 

324 def __len__(self): 

325 return len(self.parent_listeners) + len(self.listeners) 

326 

327 def __iter__(self): 

328 return chain(self.parent_listeners, self.listeners) 

329 

330 def __bool__(self): 

331 return bool(self.listeners or self.parent_listeners) 

332 

333 __nonzero__ = __bool__ 

334 

335 

336class _ListenerCollection(_CompoundListener): 

337 """Instance-level attributes on instances of :class:`._Dispatch`. 

338 

339 Represents a collection of listeners. 

340 

341 As of 0.7.9, _ListenerCollection is only first 

342 created via the _EmptyListener.for_modify() method. 

343 

344 """ 

345 

346 __slots__ = ( 

347 "parent_listeners", 

348 "parent", 

349 "name", 

350 "listeners", 

351 "propagate", 

352 "__weakref__", 

353 ) 

354 

355 def __init__(self, parent, target_cls): 

356 if target_cls not in parent._clslevel: 

357 parent.update_subclass(target_cls) 

358 self._exec_once = False 

359 self.parent_listeners = parent._clslevel[target_cls] 

360 self.parent = parent 

361 self.name = parent.name 

362 self.listeners = collections.deque() 

363 self.propagate = set() 

364 

365 def for_modify(self, obj): 

366 """Return an event collection which can be modified. 

367 

368 For _ListenerCollection at the instance level of 

369 a dispatcher, this returns self. 

370 

371 """ 

372 return self 

373 

374 def _update(self, other, only_propagate=True): 

375 """Populate from the listeners in another :class:`_Dispatch` 

376 object.""" 

377 

378 existing_listeners = self.listeners 

379 existing_listener_set = set(existing_listeners) 

380 self.propagate.update(other.propagate) 

381 other_listeners = [ 

382 l 

383 for l in other.listeners 

384 if l not in existing_listener_set 

385 and not only_propagate 

386 or l in self.propagate 

387 ] 

388 

389 existing_listeners.extend(other_listeners) 

390 

391 to_associate = other.propagate.union(other_listeners) 

392 registry._stored_in_collection_multi(self, other, to_associate) 

393 

394 def insert(self, event_key, propagate): 

395 if event_key.prepend_to_list(self, self.listeners): 

396 if propagate: 

397 self.propagate.add(event_key._listen_fn) 

398 

399 def append(self, event_key, propagate): 

400 if event_key.append_to_list(self, self.listeners): 

401 if propagate: 

402 self.propagate.add(event_key._listen_fn) 

403 

404 def remove(self, event_key): 

405 self.listeners.remove(event_key._listen_fn) 

406 self.propagate.discard(event_key._listen_fn) 

407 registry._removed_from_collection(event_key, self) 

408 

409 def clear(self): 

410 registry._clear(self, self.listeners) 

411 self.propagate.clear() 

412 self.listeners.clear() 

413 

414 

415class _JoinedListener(_CompoundListener): 

416 __slots__ = "parent", "name", "local", "parent_listeners" 

417 

418 def __init__(self, parent, name, local): 

419 self._exec_once = False 

420 self.parent = parent 

421 self.name = name 

422 self.local = local 

423 self.parent_listeners = self.local 

424 

425 @property 

426 def listeners(self): 

427 return getattr(self.parent, self.name) 

428 

429 def _adjust_fn_spec(self, fn, named): 

430 return self.local._adjust_fn_spec(fn, named) 

431 

432 def for_modify(self, obj): 

433 self.local = self.parent_listeners = self.local.for_modify(obj) 

434 return self 

435 

436 def insert(self, event_key, propagate): 

437 self.local.insert(event_key, propagate) 

438 

439 def append(self, event_key, propagate): 

440 self.local.append(event_key, propagate) 

441 

442 def remove(self, event_key): 

443 self.local.remove(event_key) 

444 

445 def clear(self): 

446 raise NotImplementedError()