Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/events.py: 52%

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

447 statements  

1# orm/events.py 

2# Copyright (C) 2005-2025 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: https://www.opensource.org/licenses/mit-license.php 

7 

8"""ORM event interfaces.""" 

9from __future__ import annotations 

10 

11from typing import Any 

12from typing import Callable 

13from typing import Collection 

14from typing import Dict 

15from typing import Generic 

16from typing import Iterable 

17from typing import Optional 

18from typing import Sequence 

19from typing import Set 

20from typing import Type 

21from typing import TYPE_CHECKING 

22from typing import TypeVar 

23from typing import Union 

24import weakref 

25 

26from . import decl_api 

27from . import instrumentation 

28from . import interfaces 

29from . import mapperlib 

30from .attributes import QueryableAttribute 

31from .base import _mapper_or_none 

32from .base import NO_KEY 

33from .instrumentation import ClassManager 

34from .instrumentation import InstrumentationFactory 

35from .query import BulkDelete 

36from .query import BulkUpdate 

37from .query import Query 

38from .scoping import scoped_session 

39from .session import Session 

40from .session import sessionmaker 

41from .. import event 

42from .. import exc 

43from .. import util 

44from ..event import EventTarget 

45from ..event.registry import _ET 

46from ..util.compat import inspect_getfullargspec 

47 

48if TYPE_CHECKING: 

49 from weakref import ReferenceType 

50 

51 from ._typing import _InstanceDict 

52 from ._typing import _InternalEntityType 

53 from ._typing import _O 

54 from ._typing import _T 

55 from .attributes import Event 

56 from .base import EventConstants 

57 from .session import ORMExecuteState 

58 from .session import SessionTransaction 

59 from .unitofwork import UOWTransaction 

60 from ..engine import Connection 

61 from ..event.base import _Dispatch 

62 from ..event.base import _HasEventsDispatch 

63 from ..event.registry import _EventKey 

64 from ..orm.collections import CollectionAdapter 

65 from ..orm.context import QueryContext 

66 from ..orm.decl_api import DeclarativeAttributeIntercept 

67 from ..orm.decl_api import DeclarativeMeta 

68 from ..orm.decl_api import registry 

69 from ..orm.mapper import Mapper 

70 from ..orm.state import InstanceState 

71 

72_KT = TypeVar("_KT", bound=Any) 

73_ET2 = TypeVar("_ET2", bound=EventTarget) 

74 

75 

76class InstrumentationEvents(event.Events[InstrumentationFactory]): 

77 """Events related to class instrumentation events. 

78 

79 The listeners here support being established against 

80 any new style class, that is any object that is a subclass 

81 of 'type'. Events will then be fired off for events 

82 against that class. If the "propagate=True" flag is passed 

83 to event.listen(), the event will fire off for subclasses 

84 of that class as well. 

85 

86 The Python ``type`` builtin is also accepted as a target, 

87 which when used has the effect of events being emitted 

88 for all classes. 

89 

90 Note the "propagate" flag here is defaulted to ``True``, 

91 unlike the other class level events where it defaults 

92 to ``False``. This means that new subclasses will also 

93 be the subject of these events, when a listener 

94 is established on a superclass. 

95 

96 """ 

97 

98 _target_class_doc = "SomeBaseClass" 

99 _dispatch_target = InstrumentationFactory 

100 

101 @classmethod 

102 def _accept_with( 

103 cls, 

104 target: Union[ 

105 InstrumentationFactory, 

106 Type[InstrumentationFactory], 

107 ], 

108 identifier: str, 

109 ) -> Optional[ 

110 Union[ 

111 InstrumentationFactory, 

112 Type[InstrumentationFactory], 

113 ] 

114 ]: 

115 if isinstance(target, type): 

116 return _InstrumentationEventsHold(target) # type: ignore [return-value] # noqa: E501 

117 else: 

118 return None 

119 

120 @classmethod 

121 def _listen( 

122 cls, event_key: _EventKey[_T], propagate: bool = True, **kw: Any 

123 ) -> None: 

124 target, identifier, fn = ( 

125 event_key.dispatch_target, 

126 event_key.identifier, 

127 event_key._listen_fn, 

128 ) 

129 

130 def listen(target_cls: type, *arg: Any) -> Optional[Any]: 

131 listen_cls = target() 

132 

133 # if weakref were collected, however this is not something 

134 # that normally happens. it was occurring during test teardown 

135 # between mapper/registry/instrumentation_manager, however this 

136 # interaction was changed to not rely upon the event system. 

137 if listen_cls is None: 

138 return None 

139 

140 if propagate and issubclass(target_cls, listen_cls): 

141 return fn(target_cls, *arg) 

142 elif not propagate and target_cls is listen_cls: 

143 return fn(target_cls, *arg) 

144 else: 

145 return None 

146 

147 def remove(ref: ReferenceType[_T]) -> None: 

148 key = event.registry._EventKey( # type: ignore [type-var] 

149 None, 

150 identifier, 

151 listen, 

152 instrumentation._instrumentation_factory, 

153 ) 

154 getattr( 

155 instrumentation._instrumentation_factory.dispatch, identifier 

156 ).remove(key) 

157 

158 target = weakref.ref(target.class_, remove) 

159 

160 event_key.with_dispatch_target( 

161 instrumentation._instrumentation_factory 

162 ).with_wrapper(listen).base_listen(**kw) 

163 

164 @classmethod 

165 def _clear(cls) -> None: 

166 super()._clear() 

167 instrumentation._instrumentation_factory.dispatch._clear() 

168 

169 def class_instrument(self, cls: ClassManager[_O]) -> None: 

170 """Called after the given class is instrumented. 

171 

172 To get at the :class:`.ClassManager`, use 

173 :func:`.manager_of_class`. 

174 

175 """ 

176 

177 def class_uninstrument(self, cls: ClassManager[_O]) -> None: 

178 """Called before the given class is uninstrumented. 

179 

180 To get at the :class:`.ClassManager`, use 

181 :func:`.manager_of_class`. 

182 

183 """ 

184 

185 def attribute_instrument( 

186 self, cls: ClassManager[_O], key: _KT, inst: _O 

187 ) -> None: 

188 """Called when an attribute is instrumented.""" 

189 

190 

191class _InstrumentationEventsHold: 

192 """temporary marker object used to transfer from _accept_with() to 

193 _listen() on the InstrumentationEvents class. 

194 

195 """ 

196 

197 def __init__(self, class_: type) -> None: 

198 self.class_ = class_ 

199 

200 dispatch = event.dispatcher(InstrumentationEvents) 

201 

202 

203class InstanceEvents(event.Events[ClassManager[Any]]): 

204 """Define events specific to object lifecycle. 

205 

206 e.g.:: 

207 

208 from sqlalchemy import event 

209 

210 

211 def my_load_listener(target, context): 

212 print("on load!") 

213 

214 

215 event.listen(SomeClass, "load", my_load_listener) 

216 

217 Available targets include: 

218 

219 * mapped classes 

220 * unmapped superclasses of mapped or to-be-mapped classes 

221 (using the ``propagate=True`` flag) 

222 * :class:`_orm.Mapper` objects 

223 * the :class:`_orm.Mapper` class itself indicates listening for all 

224 mappers. 

225 

226 Instance events are closely related to mapper events, but 

227 are more specific to the instance and its instrumentation, 

228 rather than its system of persistence. 

229 

230 When using :class:`.InstanceEvents`, several modifiers are 

231 available to the :func:`.event.listen` function. 

232 

233 :param propagate=False: When True, the event listener should 

234 be applied to all inheriting classes as well as the 

235 class which is the target of this listener. 

236 :param raw=False: When True, the "target" argument passed 

237 to applicable event listener functions will be the 

238 instance's :class:`.InstanceState` management 

239 object, rather than the mapped instance itself. 

240 :param restore_load_context=False: Applies to the 

241 :meth:`.InstanceEvents.load` and :meth:`.InstanceEvents.refresh` 

242 events. Restores the loader context of the object when the event 

243 hook is complete, so that ongoing eager load operations continue 

244 to target the object appropriately. A warning is emitted if the 

245 object is moved to a new loader context from within one of these 

246 events if this flag is not set. 

247 

248 """ 

249 

250 _target_class_doc = "SomeClass" 

251 

252 _dispatch_target = ClassManager 

253 

254 @classmethod 

255 def _new_classmanager_instance( 

256 cls, 

257 class_: Union[DeclarativeAttributeIntercept, DeclarativeMeta, type], 

258 classmanager: ClassManager[_O], 

259 ) -> None: 

260 _InstanceEventsHold.populate(class_, classmanager) 

261 

262 @classmethod 

263 @util.preload_module("sqlalchemy.orm") 

264 def _accept_with( 

265 cls, 

266 target: Union[ 

267 ClassManager[Any], 

268 Type[ClassManager[Any]], 

269 ], 

270 identifier: str, 

271 ) -> Optional[Union[ClassManager[Any], Type[ClassManager[Any]]]]: 

272 orm = util.preloaded.orm 

273 

274 if isinstance(target, ClassManager): 

275 return target 

276 elif isinstance(target, mapperlib.Mapper): 

277 return target.class_manager 

278 elif target is orm.mapper: # type: ignore [attr-defined] 

279 util.warn_deprecated( 

280 "The `sqlalchemy.orm.mapper()` symbol is deprecated and " 

281 "will be removed in a future release. For the mapper-wide " 

282 "event target, use the 'sqlalchemy.orm.Mapper' class.", 

283 "2.0", 

284 ) 

285 return ClassManager 

286 elif isinstance(target, type): 

287 if issubclass(target, mapperlib.Mapper): 

288 return ClassManager 

289 else: 

290 manager = instrumentation.opt_manager_of_class(target) 

291 if manager: 

292 return manager 

293 else: 

294 return _InstanceEventsHold(target) # type: ignore [return-value] # noqa: E501 

295 return None 

296 

297 @classmethod 

298 def _listen( 

299 cls, 

300 event_key: _EventKey[ClassManager[Any]], 

301 raw: bool = False, 

302 propagate: bool = False, 

303 restore_load_context: bool = False, 

304 **kw: Any, 

305 ) -> None: 

306 target, fn = (event_key.dispatch_target, event_key._listen_fn) 

307 

308 if not raw or restore_load_context: 

309 

310 def wrap( 

311 state: InstanceState[_O], *arg: Any, **kw: Any 

312 ) -> Optional[Any]: 

313 if not raw: 

314 target: Any = state.obj() 

315 else: 

316 target = state 

317 if restore_load_context: 

318 runid = state.runid 

319 try: 

320 return fn(target, *arg, **kw) 

321 finally: 

322 if restore_load_context: 

323 state.runid = runid 

324 

325 event_key = event_key.with_wrapper(wrap) 

326 

327 event_key.base_listen(propagate=propagate, **kw) 

328 

329 if propagate: 

330 for mgr in target.subclass_managers(True): 

331 event_key.with_dispatch_target(mgr).base_listen(propagate=True) 

332 

333 @classmethod 

334 def _clear(cls) -> None: 

335 super()._clear() 

336 _InstanceEventsHold._clear() 

337 

338 def init(self, target: _O, args: Any, kwargs: Any) -> None: 

339 """Receive an instance when its constructor is called. 

340 

341 This method is only called during a userland construction of 

342 an object, in conjunction with the object's constructor, e.g. 

343 its ``__init__`` method. It is not called when an object is 

344 loaded from the database; see the :meth:`.InstanceEvents.load` 

345 event in order to intercept a database load. 

346 

347 The event is called before the actual ``__init__`` constructor 

348 of the object is called. The ``kwargs`` dictionary may be 

349 modified in-place in order to affect what is passed to 

350 ``__init__``. 

351 

352 :param target: the mapped instance. If 

353 the event is configured with ``raw=True``, this will 

354 instead be the :class:`.InstanceState` state-management 

355 object associated with the instance. 

356 :param args: positional arguments passed to the ``__init__`` method. 

357 This is passed as a tuple and is currently immutable. 

358 :param kwargs: keyword arguments passed to the ``__init__`` method. 

359 This structure *can* be altered in place. 

360 

361 .. seealso:: 

362 

363 :meth:`.InstanceEvents.init_failure` 

364 

365 :meth:`.InstanceEvents.load` 

366 

367 """ 

368 

369 def init_failure(self, target: _O, args: Any, kwargs: Any) -> None: 

370 """Receive an instance when its constructor has been called, 

371 and raised an exception. 

372 

373 This method is only called during a userland construction of 

374 an object, in conjunction with the object's constructor, e.g. 

375 its ``__init__`` method. It is not called when an object is loaded 

376 from the database. 

377 

378 The event is invoked after an exception raised by the ``__init__`` 

379 method is caught. After the event 

380 is invoked, the original exception is re-raised outwards, so that 

381 the construction of the object still raises an exception. The 

382 actual exception and stack trace raised should be present in 

383 ``sys.exc_info()``. 

384 

385 :param target: the mapped instance. If 

386 the event is configured with ``raw=True``, this will 

387 instead be the :class:`.InstanceState` state-management 

388 object associated with the instance. 

389 :param args: positional arguments that were passed to the ``__init__`` 

390 method. 

391 :param kwargs: keyword arguments that were passed to the ``__init__`` 

392 method. 

393 

394 .. seealso:: 

395 

396 :meth:`.InstanceEvents.init` 

397 

398 :meth:`.InstanceEvents.load` 

399 

400 """ 

401 

402 def _sa_event_merge_wo_load( 

403 self, target: _O, context: QueryContext 

404 ) -> None: 

405 """receive an object instance after it was the subject of a merge() 

406 call, when load=False was passed. 

407 

408 The target would be the already-loaded object in the Session which 

409 would have had its attributes overwritten by the incoming object. This 

410 overwrite operation does not use attribute events, instead just 

411 populating dict directly. Therefore the purpose of this event is so 

412 that extensions like sqlalchemy.ext.mutable know that object state has 

413 changed and incoming state needs to be set up for "parents" etc. 

414 

415 This functionality is acceptable to be made public in a later release. 

416 

417 .. versionadded:: 1.4.41 

418 

419 """ 

420 

421 def load(self, target: _O, context: QueryContext) -> None: 

422 """Receive an object instance after it has been created via 

423 ``__new__``, and after initial attribute population has 

424 occurred. 

425 

426 This typically occurs when the instance is created based on 

427 incoming result rows, and is only called once for that 

428 instance's lifetime. 

429 

430 .. warning:: 

431 

432 During a result-row load, this event is invoked when the 

433 first row received for this instance is processed. When using 

434 eager loading with collection-oriented attributes, the additional 

435 rows that are to be loaded / processed in order to load subsequent 

436 collection items have not occurred yet. This has the effect 

437 both that collections will not be fully loaded, as well as that 

438 if an operation occurs within this event handler that emits 

439 another database load operation for the object, the "loading 

440 context" for the object can change and interfere with the 

441 existing eager loaders still in progress. 

442 

443 Examples of what can cause the "loading context" to change within 

444 the event handler include, but are not necessarily limited to: 

445 

446 * accessing deferred attributes that weren't part of the row, 

447 will trigger an "undefer" operation and refresh the object 

448 

449 * accessing attributes on a joined-inheritance subclass that 

450 weren't part of the row, will trigger a refresh operation. 

451 

452 As of SQLAlchemy 1.3.14, a warning is emitted when this occurs. The 

453 :paramref:`.InstanceEvents.restore_load_context` option may be 

454 used on the event to prevent this warning; this will ensure that 

455 the existing loading context is maintained for the object after the 

456 event is called:: 

457 

458 @event.listens_for(SomeClass, "load", restore_load_context=True) 

459 def on_load(instance, context): 

460 instance.some_unloaded_attribute 

461 

462 The :meth:`.InstanceEvents.load` event is also available in a 

463 class-method decorator format called :func:`_orm.reconstructor`. 

464 

465 :param target: the mapped instance. If 

466 the event is configured with ``raw=True``, this will 

467 instead be the :class:`.InstanceState` state-management 

468 object associated with the instance. 

469 :param context: the :class:`.QueryContext` corresponding to the 

470 current :class:`_query.Query` in progress. This argument may be 

471 ``None`` if the load does not correspond to a :class:`_query.Query`, 

472 such as during :meth:`.Session.merge`. 

473 

474 .. seealso:: 

475 

476 :ref:`mapped_class_load_events` 

477 

478 :meth:`.InstanceEvents.init` 

479 

480 :meth:`.InstanceEvents.refresh` 

481 

482 :meth:`.SessionEvents.loaded_as_persistent` 

483 

484 """ # noqa: E501 

485 

486 def refresh( 

487 self, target: _O, context: QueryContext, attrs: Optional[Iterable[str]] 

488 ) -> None: 

489 """Receive an object instance after one or more attributes have 

490 been refreshed from a query. 

491 

492 Contrast this to the :meth:`.InstanceEvents.load` method, which 

493 is invoked when the object is first loaded from a query. 

494 

495 .. note:: This event is invoked within the loader process before 

496 eager loaders may have been completed, and the object's state may 

497 not be complete. Additionally, invoking row-level refresh 

498 operations on the object will place the object into a new loader 

499 context, interfering with the existing load context. See the note 

500 on :meth:`.InstanceEvents.load` for background on making use of the 

501 :paramref:`.InstanceEvents.restore_load_context` parameter, in 

502 order to resolve this scenario. 

503 

504 :param target: the mapped instance. If 

505 the event is configured with ``raw=True``, this will 

506 instead be the :class:`.InstanceState` state-management 

507 object associated with the instance. 

508 :param context: the :class:`.QueryContext` corresponding to the 

509 current :class:`_query.Query` in progress. 

510 :param attrs: sequence of attribute names which 

511 were populated, or None if all column-mapped, non-deferred 

512 attributes were populated. 

513 

514 .. seealso:: 

515 

516 :ref:`mapped_class_load_events` 

517 

518 :meth:`.InstanceEvents.load` 

519 

520 """ 

521 

522 def refresh_flush( 

523 self, 

524 target: _O, 

525 flush_context: UOWTransaction, 

526 attrs: Optional[Iterable[str]], 

527 ) -> None: 

528 """Receive an object instance after one or more attributes that 

529 contain a column-level default or onupdate handler have been refreshed 

530 during persistence of the object's state. 

531 

532 This event is the same as :meth:`.InstanceEvents.refresh` except 

533 it is invoked within the unit of work flush process, and includes 

534 only non-primary-key columns that have column level default or 

535 onupdate handlers, including Python callables as well as server side 

536 defaults and triggers which may be fetched via the RETURNING clause. 

537 

538 .. note:: 

539 

540 While the :meth:`.InstanceEvents.refresh_flush` event is triggered 

541 for an object that was INSERTed as well as for an object that was 

542 UPDATEd, the event is geared primarily towards the UPDATE process; 

543 it is mostly an internal artifact that INSERT actions can also 

544 trigger this event, and note that **primary key columns for an 

545 INSERTed row are explicitly omitted** from this event. In order to 

546 intercept the newly INSERTed state of an object, the 

547 :meth:`.SessionEvents.pending_to_persistent` and 

548 :meth:`.MapperEvents.after_insert` are better choices. 

549 

550 :param target: the mapped instance. If 

551 the event is configured with ``raw=True``, this will 

552 instead be the :class:`.InstanceState` state-management 

553 object associated with the instance. 

554 :param flush_context: Internal :class:`.UOWTransaction` object 

555 which handles the details of the flush. 

556 :param attrs: sequence of attribute names which 

557 were populated. 

558 

559 .. seealso:: 

560 

561 :ref:`mapped_class_load_events` 

562 

563 :ref:`orm_server_defaults` 

564 

565 :ref:`metadata_defaults_toplevel` 

566 

567 """ 

568 

569 def expire(self, target: _O, attrs: Optional[Iterable[str]]) -> None: 

570 """Receive an object instance after its attributes or some subset 

571 have been expired. 

572 

573 'keys' is a list of attribute names. If None, the entire 

574 state was expired. 

575 

576 :param target: the mapped instance. If 

577 the event is configured with ``raw=True``, this will 

578 instead be the :class:`.InstanceState` state-management 

579 object associated with the instance. 

580 :param attrs: sequence of attribute 

581 names which were expired, or None if all attributes were 

582 expired. 

583 

584 """ 

585 

586 def pickle(self, target: _O, state_dict: _InstanceDict) -> None: 

587 """Receive an object instance when its associated state is 

588 being pickled. 

589 

590 :param target: the mapped instance. If 

591 the event is configured with ``raw=True``, this will 

592 instead be the :class:`.InstanceState` state-management 

593 object associated with the instance. 

594 :param state_dict: the dictionary returned by 

595 :class:`.InstanceState.__getstate__`, containing the state 

596 to be pickled. 

597 

598 """ 

599 

600 def unpickle(self, target: _O, state_dict: _InstanceDict) -> None: 

601 """Receive an object instance after its associated state has 

602 been unpickled. 

603 

604 :param target: the mapped instance. If 

605 the event is configured with ``raw=True``, this will 

606 instead be the :class:`.InstanceState` state-management 

607 object associated with the instance. 

608 :param state_dict: the dictionary sent to 

609 :class:`.InstanceState.__setstate__`, containing the state 

610 dictionary which was pickled. 

611 

612 """ 

613 

614 

615class _EventsHold(event.RefCollection[_ET]): 

616 """Hold onto listeners against unmapped, uninstrumented classes. 

617 

618 Establish _listen() for that class' mapper/instrumentation when 

619 those objects are created for that class. 

620 

621 """ 

622 

623 all_holds: weakref.WeakKeyDictionary[Any, Any] 

624 

625 def __init__( 

626 self, 

627 class_: Union[DeclarativeAttributeIntercept, DeclarativeMeta, type], 

628 ) -> None: 

629 self.class_ = class_ 

630 

631 @classmethod 

632 def _clear(cls) -> None: 

633 cls.all_holds.clear() 

634 

635 class HoldEvents(Generic[_ET2]): 

636 _dispatch_target: Optional[Type[_ET2]] = None 

637 

638 @classmethod 

639 def _listen( 

640 cls, 

641 event_key: _EventKey[_ET2], 

642 raw: bool = False, 

643 propagate: bool = False, 

644 retval: bool = False, 

645 **kw: Any, 

646 ) -> None: 

647 target = event_key.dispatch_target 

648 

649 if target.class_ in target.all_holds: 

650 collection = target.all_holds[target.class_] 

651 else: 

652 collection = target.all_holds[target.class_] = {} 

653 

654 event.registry._stored_in_collection(event_key, target) 

655 collection[event_key._key] = ( 

656 event_key, 

657 raw, 

658 propagate, 

659 retval, 

660 kw, 

661 ) 

662 

663 if propagate: 

664 stack = list(target.class_.__subclasses__()) 

665 while stack: 

666 subclass = stack.pop(0) 

667 stack.extend(subclass.__subclasses__()) 

668 subject = target.resolve(subclass) 

669 if subject is not None: 

670 # we are already going through __subclasses__() 

671 # so leave generic propagate flag False 

672 event_key.with_dispatch_target(subject).listen( 

673 raw=raw, propagate=False, retval=retval, **kw 

674 ) 

675 

676 def remove(self, event_key: _EventKey[_ET]) -> None: 

677 target = event_key.dispatch_target 

678 

679 if isinstance(target, _EventsHold): 

680 collection = target.all_holds[target.class_] 

681 del collection[event_key._key] 

682 

683 @classmethod 

684 def populate( 

685 cls, 

686 class_: Union[DeclarativeAttributeIntercept, DeclarativeMeta, type], 

687 subject: Union[ClassManager[_O], Mapper[_O]], 

688 ) -> None: 

689 for subclass in class_.__mro__: 

690 if subclass in cls.all_holds: 

691 collection = cls.all_holds[subclass] 

692 for ( 

693 event_key, 

694 raw, 

695 propagate, 

696 retval, 

697 kw, 

698 ) in collection.values(): 

699 if propagate or subclass is class_: 

700 # since we can't be sure in what order different 

701 # classes in a hierarchy are triggered with 

702 # populate(), we rely upon _EventsHold for all event 

703 # assignment, instead of using the generic propagate 

704 # flag. 

705 event_key.with_dispatch_target(subject).listen( 

706 raw=raw, propagate=False, retval=retval, **kw 

707 ) 

708 

709 

710class _InstanceEventsHold(_EventsHold[_ET]): 

711 all_holds: weakref.WeakKeyDictionary[Any, Any] = ( 

712 weakref.WeakKeyDictionary() 

713 ) 

714 

715 def resolve(self, class_: Type[_O]) -> Optional[ClassManager[_O]]: 

716 return instrumentation.opt_manager_of_class(class_) 

717 

718 # this fails on pyright if you use Any. Fails on mypy if you use _ET 

719 class HoldInstanceEvents(_EventsHold.HoldEvents[_ET], InstanceEvents): # type: ignore[valid-type,misc] # noqa: E501 

720 pass 

721 

722 dispatch = event.dispatcher(HoldInstanceEvents) 

723 

724 

725class MapperEvents(event.Events[mapperlib.Mapper[Any]]): 

726 """Define events specific to mappings. 

727 

728 e.g.:: 

729 

730 from sqlalchemy import event 

731 

732 

733 def my_before_insert_listener(mapper, connection, target): 

734 # execute a stored procedure upon INSERT, 

735 # apply the value to the row to be inserted 

736 target.calculated_value = connection.execute( 

737 text("select my_special_function(%d)" % target.special_number) 

738 ).scalar() 

739 

740 

741 # associate the listener function with SomeClass, 

742 # to execute during the "before_insert" hook 

743 event.listen(SomeClass, "before_insert", my_before_insert_listener) 

744 

745 Available targets include: 

746 

747 * mapped classes 

748 * unmapped superclasses of mapped or to-be-mapped classes 

749 (using the ``propagate=True`` flag) 

750 * :class:`_orm.Mapper` objects 

751 * the :class:`_orm.Mapper` class itself indicates listening for all 

752 mappers. 

753 

754 Mapper events provide hooks into critical sections of the 

755 mapper, including those related to object instrumentation, 

756 object loading, and object persistence. In particular, the 

757 persistence methods :meth:`~.MapperEvents.before_insert`, 

758 and :meth:`~.MapperEvents.before_update` are popular 

759 places to augment the state being persisted - however, these 

760 methods operate with several significant restrictions. The 

761 user is encouraged to evaluate the 

762 :meth:`.SessionEvents.before_flush` and 

763 :meth:`.SessionEvents.after_flush` methods as more 

764 flexible and user-friendly hooks in which to apply 

765 additional database state during a flush. 

766 

767 When using :class:`.MapperEvents`, several modifiers are 

768 available to the :func:`.event.listen` function. 

769 

770 :param propagate=False: When True, the event listener should 

771 be applied to all inheriting mappers and/or the mappers of 

772 inheriting classes, as well as any 

773 mapper which is the target of this listener. 

774 :param raw=False: When True, the "target" argument passed 

775 to applicable event listener functions will be the 

776 instance's :class:`.InstanceState` management 

777 object, rather than the mapped instance itself. 

778 :param retval=False: when True, the user-defined event function 

779 must have a return value, the purpose of which is either to 

780 control subsequent event propagation, or to otherwise alter 

781 the operation in progress by the mapper. Possible return 

782 values are: 

783 

784 * ``sqlalchemy.orm.interfaces.EXT_CONTINUE`` - continue event 

785 processing normally. 

786 * ``sqlalchemy.orm.interfaces.EXT_STOP`` - cancel all subsequent 

787 event handlers in the chain. 

788 * other values - the return value specified by specific listeners. 

789 

790 """ 

791 

792 _target_class_doc = "SomeClass" 

793 _dispatch_target = mapperlib.Mapper 

794 

795 @classmethod 

796 def _new_mapper_instance( 

797 cls, 

798 class_: Union[DeclarativeAttributeIntercept, DeclarativeMeta, type], 

799 mapper: Mapper[_O], 

800 ) -> None: 

801 _MapperEventsHold.populate(class_, mapper) 

802 

803 @classmethod 

804 @util.preload_module("sqlalchemy.orm") 

805 def _accept_with( 

806 cls, 

807 target: Union[mapperlib.Mapper[Any], Type[mapperlib.Mapper[Any]]], 

808 identifier: str, 

809 ) -> Optional[Union[mapperlib.Mapper[Any], Type[mapperlib.Mapper[Any]]]]: 

810 orm = util.preloaded.orm 

811 

812 if target is orm.mapper: # type: ignore [attr-defined] 

813 util.warn_deprecated( 

814 "The `sqlalchemy.orm.mapper()` symbol is deprecated and " 

815 "will be removed in a future release. For the mapper-wide " 

816 "event target, use the 'sqlalchemy.orm.Mapper' class.", 

817 "2.0", 

818 ) 

819 target = mapperlib.Mapper 

820 

821 if identifier in ("before_configured", "after_configured"): 

822 if target is mapperlib.Mapper: 

823 return target 

824 else: 

825 return None 

826 

827 elif isinstance(target, type): 

828 if issubclass(target, mapperlib.Mapper): 

829 return target 

830 else: 

831 mapper = _mapper_or_none(target) 

832 if mapper is not None: 

833 return mapper 

834 else: 

835 return _MapperEventsHold(target) 

836 else: 

837 return target 

838 

839 @classmethod 

840 def _listen( 

841 cls, 

842 event_key: _EventKey[_ET], 

843 raw: bool = False, 

844 retval: bool = False, 

845 propagate: bool = False, 

846 **kw: Any, 

847 ) -> None: 

848 target, identifier, fn = ( 

849 event_key.dispatch_target, 

850 event_key.identifier, 

851 event_key._listen_fn, 

852 ) 

853 

854 if not raw or not retval: 

855 if not raw: 

856 meth = getattr(cls, identifier) 

857 try: 

858 target_index = ( 

859 inspect_getfullargspec(meth)[0].index("target") - 1 

860 ) 

861 except ValueError: 

862 target_index = None 

863 

864 def wrap(*arg: Any, **kw: Any) -> Any: 

865 if not raw and target_index is not None: 

866 arg = list(arg) # type: ignore [assignment] 

867 arg[target_index] = arg[target_index].obj() # type: ignore [index] # noqa: E501 

868 if not retval: 

869 fn(*arg, **kw) 

870 return interfaces.EXT_CONTINUE 

871 else: 

872 return fn(*arg, **kw) 

873 

874 event_key = event_key.with_wrapper(wrap) 

875 

876 if propagate: 

877 for mapper in target.self_and_descendants: 

878 event_key.with_dispatch_target(mapper).base_listen( 

879 propagate=True, **kw 

880 ) 

881 else: 

882 event_key.base_listen(**kw) 

883 

884 @classmethod 

885 def _clear(cls) -> None: 

886 super()._clear() 

887 _MapperEventsHold._clear() 

888 

889 def instrument_class(self, mapper: Mapper[_O], class_: Type[_O]) -> None: 

890 r"""Receive a class when the mapper is first constructed, 

891 before instrumentation is applied to the mapped class. 

892 

893 This event is the earliest phase of mapper construction. 

894 Most attributes of the mapper are not yet initialized. To 

895 receive an event within initial mapper construction where basic 

896 state is available such as the :attr:`_orm.Mapper.attrs` collection, 

897 the :meth:`_orm.MapperEvents.after_mapper_constructed` event may 

898 be a better choice. 

899 

900 This listener can either be applied to the :class:`_orm.Mapper` 

901 class overall, or to any un-mapped class which serves as a base 

902 for classes that will be mapped (using the ``propagate=True`` flag):: 

903 

904 Base = declarative_base() 

905 

906 

907 @event.listens_for(Base, "instrument_class", propagate=True) 

908 def on_new_class(mapper, cls_): 

909 "..." 

910 

911 :param mapper: the :class:`_orm.Mapper` which is the target 

912 of this event. 

913 :param class\_: the mapped class. 

914 

915 .. seealso:: 

916 

917 :meth:`_orm.MapperEvents.after_mapper_constructed` 

918 

919 """ 

920 

921 def after_mapper_constructed( 

922 self, mapper: Mapper[_O], class_: Type[_O] 

923 ) -> None: 

924 """Receive a class and mapper when the :class:`_orm.Mapper` has been 

925 fully constructed. 

926 

927 This event is called after the initial constructor for 

928 :class:`_orm.Mapper` completes. This occurs after the 

929 :meth:`_orm.MapperEvents.instrument_class` event and after the 

930 :class:`_orm.Mapper` has done an initial pass of its arguments 

931 to generate its collection of :class:`_orm.MapperProperty` objects, 

932 which are accessible via the :meth:`_orm.Mapper.get_property` 

933 method and the :attr:`_orm.Mapper.iterate_properties` attribute. 

934 

935 This event differs from the 

936 :meth:`_orm.MapperEvents.before_mapper_configured` event in that it 

937 is invoked within the constructor for :class:`_orm.Mapper`, rather 

938 than within the :meth:`_orm.registry.configure` process. Currently, 

939 this event is the only one which is appropriate for handlers that 

940 wish to create additional mapped classes in response to the 

941 construction of this :class:`_orm.Mapper`, which will be part of the 

942 same configure step when :meth:`_orm.registry.configure` next runs. 

943 

944 .. versionadded:: 2.0.2 

945 

946 .. seealso:: 

947 

948 :ref:`examples_versioning` - an example which illustrates the use 

949 of the :meth:`_orm.MapperEvents.before_mapper_configured` 

950 event to create new mappers to record change-audit histories on 

951 objects. 

952 

953 """ 

954 

955 @event._omit_standard_example 

956 def before_mapper_configured( 

957 self, mapper: Mapper[_O], class_: Type[_O] 

958 ) -> None: 

959 """Called right before a specific mapper is to be configured. 

960 

961 The :meth:`.MapperEvents.before_mapper_configured` event is invoked 

962 for each mapper that is encountered when the 

963 :func:`_orm.configure_mappers` function proceeds through the current 

964 list of not-yet-configured mappers. It is similar to the 

965 :meth:`.MapperEvents.mapper_configured` event, except that it's invoked 

966 right before the configuration occurs, rather than afterwards. 

967 

968 The :meth:`.MapperEvents.before_mapper_configured` event includes 

969 the special capability where it can force the configure step for a 

970 specific mapper to be skipped; to use this feature, establish 

971 the event using the ``retval=True`` parameter and return 

972 the :attr:`.orm.interfaces.EXT_SKIP` symbol to indicate the mapper 

973 should be left unconfigured:: 

974 

975 from sqlalchemy import event 

976 from sqlalchemy.orm import EXT_SKIP 

977 from sqlalchemy.orm import DeclarativeBase 

978 

979 

980 class DontConfigureBase(DeclarativeBase): 

981 pass 

982 

983 

984 @event.listens_for( 

985 DontConfigureBase, 

986 "before_mapper_configured", 

987 # support return values for the event 

988 retval=True, 

989 # propagate the listener to all subclasses of 

990 # DontConfigureBase 

991 propagate=True, 

992 ) 

993 def dont_configure(mapper, cls): 

994 return EXT_SKIP 

995 

996 .. seealso:: 

997 

998 :meth:`.MapperEvents.before_configured` 

999 

1000 :meth:`.MapperEvents.after_configured` 

1001 

1002 :meth:`.RegistryEvents.before_configured` 

1003 

1004 :meth:`.RegistryEvents.after_configured` 

1005 

1006 :meth:`.MapperEvents.mapper_configured` 

1007 

1008 """ 

1009 

1010 def mapper_configured(self, mapper: Mapper[_O], class_: Type[_O]) -> None: 

1011 r"""Called when a specific mapper has completed its own configuration 

1012 within the scope of the :func:`.configure_mappers` call. 

1013 

1014 The :meth:`.MapperEvents.mapper_configured` event is invoked 

1015 for each mapper that is encountered when the 

1016 :func:`_orm.configure_mappers` function proceeds through the current 

1017 list of not-yet-configured mappers. 

1018 :func:`_orm.configure_mappers` is typically invoked 

1019 automatically as mappings are first used, as well as each time 

1020 new mappers have been made available and new mapper use is 

1021 detected. 

1022 

1023 When the event is called, the mapper should be in its final 

1024 state, but **not including backrefs** that may be invoked from 

1025 other mappers; they might still be pending within the 

1026 configuration operation. Bidirectional relationships that 

1027 are instead configured via the 

1028 :paramref:`.orm.relationship.back_populates` argument 

1029 *will* be fully available, since this style of relationship does not 

1030 rely upon other possibly-not-configured mappers to know that they 

1031 exist. 

1032 

1033 For an event that is guaranteed to have **all** mappers ready 

1034 to go including backrefs that are defined only on other 

1035 mappings, use the :meth:`.MapperEvents.after_configured` 

1036 event; this event invokes only after all known mappings have been 

1037 fully configured. 

1038 

1039 The :meth:`.MapperEvents.mapper_configured` event, unlike the 

1040 :meth:`.MapperEvents.before_configured` or 

1041 :meth:`.MapperEvents.after_configured` events, is called for each 

1042 mapper/class individually, and the mapper is passed to the event 

1043 itself. It also is called exactly once for a particular mapper. The 

1044 event is therefore useful for configurational steps that benefit from 

1045 being invoked just once on a specific mapper basis, which don't require 

1046 that "backref" configurations are necessarily ready yet. 

1047 

1048 :param mapper: the :class:`_orm.Mapper` which is the target 

1049 of this event. 

1050 :param class\_: the mapped class. 

1051 

1052 .. seealso:: 

1053 

1054 :meth:`.MapperEvents.before_configured` 

1055 

1056 :meth:`.MapperEvents.after_configured` 

1057 

1058 :meth:`.RegistryEvents.before_configured` 

1059 

1060 :meth:`.RegistryEvents.after_configured` 

1061 

1062 :meth:`.MapperEvents.before_mapper_configured` 

1063 

1064 """ 

1065 # TODO: need coverage for this event 

1066 

1067 @event._omit_standard_example 

1068 def before_configured(self) -> None: 

1069 """Called before a series of mappers have been configured. 

1070 

1071 The :meth:`.MapperEvents.before_configured` event is invoked 

1072 each time the :func:`_orm.configure_mappers` function is 

1073 invoked, before the function has done any of its work. 

1074 :func:`_orm.configure_mappers` is typically invoked 

1075 automatically as mappings are first used, as well as each time 

1076 new mappers have been made available and new mapper use is 

1077 detected. 

1078 

1079 Similar events to this one include 

1080 :meth:`.MapperEvents.after_configured`, which is invoked after a series 

1081 of mappers has been configured, as well as 

1082 :meth:`.MapperEvents.before_mapper_configured` and 

1083 :meth:`.MapperEvents.mapper_configured`, which are both invoked on a 

1084 per-mapper basis. 

1085 

1086 This event can **only** be applied to the :class:`_orm.Mapper` class, 

1087 and not to individual mappings or mapped classes:: 

1088 

1089 from sqlalchemy.orm import Mapper 

1090 

1091 

1092 @event.listens_for(Mapper, "before_configured") 

1093 def go(): ... 

1094 

1095 Typically, this event is called once per application, but in practice 

1096 may be called more than once, any time new mappers are to be affected 

1097 by a :func:`_orm.configure_mappers` call. If new mappings are 

1098 constructed after existing ones have already been used, this event will 

1099 likely be called again. 

1100 

1101 .. seealso:: 

1102 

1103 :meth:`.MapperEvents.before_mapper_configured` 

1104 

1105 :meth:`.MapperEvents.mapper_configured` 

1106 

1107 :meth:`.MapperEvents.after_configured` 

1108 

1109 :meth:`.RegistryEvents.before_configured` 

1110 

1111 :meth:`.RegistryEvents.after_configured` 

1112 

1113 """ 

1114 

1115 @event._omit_standard_example 

1116 def after_configured(self) -> None: 

1117 """Called after a series of mappers have been configured. 

1118 

1119 The :meth:`.MapperEvents.after_configured` event is invoked 

1120 each time the :func:`_orm.configure_mappers` function is 

1121 invoked, after the function has completed its work. 

1122 :func:`_orm.configure_mappers` is typically invoked 

1123 automatically as mappings are first used, as well as each time 

1124 new mappers have been made available and new mapper use is 

1125 detected. 

1126 

1127 Similar events to this one include 

1128 :meth:`.MapperEvents.before_configured`, which is invoked before a 

1129 series of mappers are configured, as well as 

1130 :meth:`.MapperEvents.before_mapper_configured` and 

1131 :meth:`.MapperEvents.mapper_configured`, which are both invoked on a 

1132 per-mapper basis. 

1133 

1134 This event can **only** be applied to the :class:`_orm.Mapper` class, 

1135 and not to individual mappings or mapped classes:: 

1136 

1137 from sqlalchemy.orm import Mapper 

1138 

1139 

1140 @event.listens_for(Mapper, "after_configured") 

1141 def go(): ... 

1142 

1143 Typically, this event is called once per application, but in practice 

1144 may be called more than once, any time new mappers are to be affected 

1145 by a :func:`_orm.configure_mappers` call. If new mappings are 

1146 constructed after existing ones have already been used, this event will 

1147 likely be called again. 

1148 

1149 .. seealso:: 

1150 

1151 :meth:`.MapperEvents.before_mapper_configured` 

1152 

1153 :meth:`.MapperEvents.mapper_configured` 

1154 

1155 :meth:`.MapperEvents.before_configured` 

1156 

1157 :meth:`.RegistryEvents.before_configured` 

1158 

1159 :meth:`.RegistryEvents.after_configured` 

1160 

1161 """ 

1162 

1163 def before_insert( 

1164 self, mapper: Mapper[_O], connection: Connection, target: _O 

1165 ) -> None: 

1166 """Receive an object instance before an INSERT statement 

1167 is emitted corresponding to that instance. 

1168 

1169 .. note:: this event **only** applies to the 

1170 :ref:`session flush operation <session_flushing>` 

1171 and does **not** apply to the ORM DML operations described at 

1172 :ref:`orm_expression_update_delete`. To intercept ORM 

1173 DML events, use :meth:`_orm.SessionEvents.do_orm_execute`. 

1174 

1175 This event is used to modify local, non-object related 

1176 attributes on the instance before an INSERT occurs, as well 

1177 as to emit additional SQL statements on the given 

1178 connection. 

1179 

1180 The event is often called for a batch of objects of the 

1181 same class before their INSERT statements are emitted at 

1182 once in a later step. In the extremely rare case that 

1183 this is not desirable, the :class:`_orm.Mapper` object can be 

1184 configured with ``batch=False``, which will cause 

1185 batches of instances to be broken up into individual 

1186 (and more poorly performing) event->persist->event 

1187 steps. 

1188 

1189 .. warning:: 

1190 

1191 Mapper-level flush events only allow **very limited operations**, 

1192 on attributes local to the row being operated upon only, 

1193 as well as allowing any SQL to be emitted on the given 

1194 :class:`_engine.Connection`. **Please read fully** the notes 

1195 at :ref:`session_persistence_mapper` for guidelines on using 

1196 these methods; generally, the :meth:`.SessionEvents.before_flush` 

1197 method should be preferred for general on-flush changes. 

1198 

1199 :param mapper: the :class:`_orm.Mapper` which is the target 

1200 of this event. 

1201 :param connection: the :class:`_engine.Connection` being used to 

1202 emit INSERT statements for this instance. This 

1203 provides a handle into the current transaction on the 

1204 target database specific to this instance. 

1205 :param target: the mapped instance being persisted. If 

1206 the event is configured with ``raw=True``, this will 

1207 instead be the :class:`.InstanceState` state-management 

1208 object associated with the instance. 

1209 :return: No return value is supported by this event. 

1210 

1211 .. seealso:: 

1212 

1213 :ref:`session_persistence_events` 

1214 

1215 """ 

1216 

1217 def after_insert( 

1218 self, mapper: Mapper[_O], connection: Connection, target: _O 

1219 ) -> None: 

1220 """Receive an object instance after an INSERT statement 

1221 is emitted corresponding to that instance. 

1222 

1223 .. note:: this event **only** applies to the 

1224 :ref:`session flush operation <session_flushing>` 

1225 and does **not** apply to the ORM DML operations described at 

1226 :ref:`orm_expression_update_delete`. To intercept ORM 

1227 DML events, use :meth:`_orm.SessionEvents.do_orm_execute`. 

1228 

1229 This event is used to modify in-Python-only 

1230 state on the instance after an INSERT occurs, as well 

1231 as to emit additional SQL statements on the given 

1232 connection. 

1233 

1234 The event is often called for a batch of objects of the 

1235 same class after their INSERT statements have been 

1236 emitted at once in a previous step. In the extremely 

1237 rare case that this is not desirable, the 

1238 :class:`_orm.Mapper` object can be configured with ``batch=False``, 

1239 which will cause batches of instances to be broken up 

1240 into individual (and more poorly performing) 

1241 event->persist->event steps. 

1242 

1243 .. warning:: 

1244 

1245 Mapper-level flush events only allow **very limited operations**, 

1246 on attributes local to the row being operated upon only, 

1247 as well as allowing any SQL to be emitted on the given 

1248 :class:`_engine.Connection`. **Please read fully** the notes 

1249 at :ref:`session_persistence_mapper` for guidelines on using 

1250 these methods; generally, the :meth:`.SessionEvents.before_flush` 

1251 method should be preferred for general on-flush changes. 

1252 

1253 :param mapper: the :class:`_orm.Mapper` which is the target 

1254 of this event. 

1255 :param connection: the :class:`_engine.Connection` being used to 

1256 emit INSERT statements for this instance. This 

1257 provides a handle into the current transaction on the 

1258 target database specific to this instance. 

1259 :param target: the mapped instance being persisted. If 

1260 the event is configured with ``raw=True``, this will 

1261 instead be the :class:`.InstanceState` state-management 

1262 object associated with the instance. 

1263 :return: No return value is supported by this event. 

1264 

1265 .. seealso:: 

1266 

1267 :ref:`session_persistence_events` 

1268 

1269 """ 

1270 

1271 def before_update( 

1272 self, mapper: Mapper[_O], connection: Connection, target: _O 

1273 ) -> None: 

1274 """Receive an object instance before an UPDATE statement 

1275 is emitted corresponding to that instance. 

1276 

1277 .. note:: this event **only** applies to the 

1278 :ref:`session flush operation <session_flushing>` 

1279 and does **not** apply to the ORM DML operations described at 

1280 :ref:`orm_expression_update_delete`. To intercept ORM 

1281 DML events, use :meth:`_orm.SessionEvents.do_orm_execute`. 

1282 

1283 This event is used to modify local, non-object related 

1284 attributes on the instance before an UPDATE occurs, as well 

1285 as to emit additional SQL statements on the given 

1286 connection. 

1287 

1288 This method is called for all instances that are 

1289 marked as "dirty", *even those which have no net changes 

1290 to their column-based attributes*. An object is marked 

1291 as dirty when any of its column-based attributes have a 

1292 "set attribute" operation called or when any of its 

1293 collections are modified. If, at update time, no 

1294 column-based attributes have any net changes, no UPDATE 

1295 statement will be issued. This means that an instance 

1296 being sent to :meth:`~.MapperEvents.before_update` is 

1297 *not* a guarantee that an UPDATE statement will be 

1298 issued, although you can affect the outcome here by 

1299 modifying attributes so that a net change in value does 

1300 exist. 

1301 

1302 To detect if the column-based attributes on the object have net 

1303 changes, and will therefore generate an UPDATE statement, use 

1304 ``object_session(instance).is_modified(instance, 

1305 include_collections=False)``. 

1306 

1307 The event is often called for a batch of objects of the 

1308 same class before their UPDATE statements are emitted at 

1309 once in a later step. In the extremely rare case that 

1310 this is not desirable, the :class:`_orm.Mapper` can be 

1311 configured with ``batch=False``, which will cause 

1312 batches of instances to be broken up into individual 

1313 (and more poorly performing) event->persist->event 

1314 steps. 

1315 

1316 .. warning:: 

1317 

1318 Mapper-level flush events only allow **very limited operations**, 

1319 on attributes local to the row being operated upon only, 

1320 as well as allowing any SQL to be emitted on the given 

1321 :class:`_engine.Connection`. **Please read fully** the notes 

1322 at :ref:`session_persistence_mapper` for guidelines on using 

1323 these methods; generally, the :meth:`.SessionEvents.before_flush` 

1324 method should be preferred for general on-flush changes. 

1325 

1326 :param mapper: the :class:`_orm.Mapper` which is the target 

1327 of this event. 

1328 :param connection: the :class:`_engine.Connection` being used to 

1329 emit UPDATE statements for this instance. This 

1330 provides a handle into the current transaction on the 

1331 target database specific to this instance. 

1332 :param target: the mapped instance being persisted. If 

1333 the event is configured with ``raw=True``, this will 

1334 instead be the :class:`.InstanceState` state-management 

1335 object associated with the instance. 

1336 :return: No return value is supported by this event. 

1337 

1338 .. seealso:: 

1339 

1340 :ref:`session_persistence_events` 

1341 

1342 """ 

1343 

1344 def after_update( 

1345 self, mapper: Mapper[_O], connection: Connection, target: _O 

1346 ) -> None: 

1347 """Receive an object instance after an UPDATE statement 

1348 is emitted corresponding to that instance. 

1349 

1350 .. note:: this event **only** applies to the 

1351 :ref:`session flush operation <session_flushing>` 

1352 and does **not** apply to the ORM DML operations described at 

1353 :ref:`orm_expression_update_delete`. To intercept ORM 

1354 DML events, use :meth:`_orm.SessionEvents.do_orm_execute`. 

1355 

1356 This event is used to modify in-Python-only 

1357 state on the instance after an UPDATE occurs, as well 

1358 as to emit additional SQL statements on the given 

1359 connection. 

1360 

1361 This method is called for all instances that are 

1362 marked as "dirty", *even those which have no net changes 

1363 to their column-based attributes*, and for which 

1364 no UPDATE statement has proceeded. An object is marked 

1365 as dirty when any of its column-based attributes have a 

1366 "set attribute" operation called or when any of its 

1367 collections are modified. If, at update time, no 

1368 column-based attributes have any net changes, no UPDATE 

1369 statement will be issued. This means that an instance 

1370 being sent to :meth:`~.MapperEvents.after_update` is 

1371 *not* a guarantee that an UPDATE statement has been 

1372 issued. 

1373 

1374 To detect if the column-based attributes on the object have net 

1375 changes, and therefore resulted in an UPDATE statement, use 

1376 ``object_session(instance).is_modified(instance, 

1377 include_collections=False)``. 

1378 

1379 The event is often called for a batch of objects of the 

1380 same class after their UPDATE statements have been emitted at 

1381 once in a previous step. In the extremely rare case that 

1382 this is not desirable, the :class:`_orm.Mapper` can be 

1383 configured with ``batch=False``, which will cause 

1384 batches of instances to be broken up into individual 

1385 (and more poorly performing) event->persist->event 

1386 steps. 

1387 

1388 .. warning:: 

1389 

1390 Mapper-level flush events only allow **very limited operations**, 

1391 on attributes local to the row being operated upon only, 

1392 as well as allowing any SQL to be emitted on the given 

1393 :class:`_engine.Connection`. **Please read fully** the notes 

1394 at :ref:`session_persistence_mapper` for guidelines on using 

1395 these methods; generally, the :meth:`.SessionEvents.before_flush` 

1396 method should be preferred for general on-flush changes. 

1397 

1398 :param mapper: the :class:`_orm.Mapper` which is the target 

1399 of this event. 

1400 :param connection: the :class:`_engine.Connection` being used to 

1401 emit UPDATE statements for this instance. This 

1402 provides a handle into the current transaction on the 

1403 target database specific to this instance. 

1404 :param target: the mapped instance being persisted. If 

1405 the event is configured with ``raw=True``, this will 

1406 instead be the :class:`.InstanceState` state-management 

1407 object associated with the instance. 

1408 :return: No return value is supported by this event. 

1409 

1410 .. seealso:: 

1411 

1412 :ref:`session_persistence_events` 

1413 

1414 """ 

1415 

1416 def before_delete( 

1417 self, mapper: Mapper[_O], connection: Connection, target: _O 

1418 ) -> None: 

1419 """Receive an object instance before a DELETE statement 

1420 is emitted corresponding to that instance. 

1421 

1422 .. note:: this event **only** applies to the 

1423 :ref:`session flush operation <session_flushing>` 

1424 and does **not** apply to the ORM DML operations described at 

1425 :ref:`orm_expression_update_delete`. To intercept ORM 

1426 DML events, use :meth:`_orm.SessionEvents.do_orm_execute`. 

1427 

1428 This event is used to emit additional SQL statements on 

1429 the given connection as well as to perform application 

1430 specific bookkeeping related to a deletion event. 

1431 

1432 The event is often called for a batch of objects of the 

1433 same class before their DELETE statements are emitted at 

1434 once in a later step. 

1435 

1436 .. warning:: 

1437 

1438 Mapper-level flush events only allow **very limited operations**, 

1439 on attributes local to the row being operated upon only, 

1440 as well as allowing any SQL to be emitted on the given 

1441 :class:`_engine.Connection`. **Please read fully** the notes 

1442 at :ref:`session_persistence_mapper` for guidelines on using 

1443 these methods; generally, the :meth:`.SessionEvents.before_flush` 

1444 method should be preferred for general on-flush changes. 

1445 

1446 :param mapper: the :class:`_orm.Mapper` which is the target 

1447 of this event. 

1448 :param connection: the :class:`_engine.Connection` being used to 

1449 emit DELETE statements for this instance. This 

1450 provides a handle into the current transaction on the 

1451 target database specific to this instance. 

1452 :param target: the mapped instance being deleted. If 

1453 the event is configured with ``raw=True``, this will 

1454 instead be the :class:`.InstanceState` state-management 

1455 object associated with the instance. 

1456 :return: No return value is supported by this event. 

1457 

1458 .. seealso:: 

1459 

1460 :ref:`session_persistence_events` 

1461 

1462 """ 

1463 

1464 def after_delete( 

1465 self, mapper: Mapper[_O], connection: Connection, target: _O 

1466 ) -> None: 

1467 """Receive an object instance after a DELETE statement 

1468 has been emitted corresponding to that instance. 

1469 

1470 .. note:: this event **only** applies to the 

1471 :ref:`session flush operation <session_flushing>` 

1472 and does **not** apply to the ORM DML operations described at 

1473 :ref:`orm_expression_update_delete`. To intercept ORM 

1474 DML events, use :meth:`_orm.SessionEvents.do_orm_execute`. 

1475 

1476 This event is used to emit additional SQL statements on 

1477 the given connection as well as to perform application 

1478 specific bookkeeping related to a deletion event. 

1479 

1480 The event is often called for a batch of objects of the 

1481 same class after their DELETE statements have been emitted at 

1482 once in a previous step. 

1483 

1484 .. warning:: 

1485 

1486 Mapper-level flush events only allow **very limited operations**, 

1487 on attributes local to the row being operated upon only, 

1488 as well as allowing any SQL to be emitted on the given 

1489 :class:`_engine.Connection`. **Please read fully** the notes 

1490 at :ref:`session_persistence_mapper` for guidelines on using 

1491 these methods; generally, the :meth:`.SessionEvents.before_flush` 

1492 method should be preferred for general on-flush changes. 

1493 

1494 :param mapper: the :class:`_orm.Mapper` which is the target 

1495 of this event. 

1496 :param connection: the :class:`_engine.Connection` being used to 

1497 emit DELETE statements for this instance. This 

1498 provides a handle into the current transaction on the 

1499 target database specific to this instance. 

1500 :param target: the mapped instance being deleted. If 

1501 the event is configured with ``raw=True``, this will 

1502 instead be the :class:`.InstanceState` state-management 

1503 object associated with the instance. 

1504 :return: No return value is supported by this event. 

1505 

1506 .. seealso:: 

1507 

1508 :ref:`session_persistence_events` 

1509 

1510 """ 

1511 

1512 

1513class _MapperEventsHold(_EventsHold[_ET]): 

1514 all_holds = weakref.WeakKeyDictionary() 

1515 

1516 def resolve( 

1517 self, class_: Union[Type[_T], _InternalEntityType[_T]] 

1518 ) -> Optional[Mapper[_T]]: 

1519 return _mapper_or_none(class_) 

1520 

1521 # this fails on pyright if you use Any. Fails on mypy if you use _ET 

1522 class HoldMapperEvents(_EventsHold.HoldEvents[_ET], MapperEvents): # type: ignore[valid-type,misc] # noqa: E501 

1523 pass 

1524 

1525 dispatch = event.dispatcher(HoldMapperEvents) 

1526 

1527 

1528_sessionevents_lifecycle_event_names: Set[str] = set() 

1529 

1530 

1531class SessionEvents(event.Events[Session]): 

1532 """Define events specific to :class:`.Session` lifecycle. 

1533 

1534 e.g.:: 

1535 

1536 from sqlalchemy import event 

1537 from sqlalchemy.orm import sessionmaker 

1538 

1539 

1540 def my_before_commit(session): 

1541 print("before commit!") 

1542 

1543 

1544 Session = sessionmaker() 

1545 

1546 event.listen(Session, "before_commit", my_before_commit) 

1547 

1548 The :func:`~.event.listen` function will accept 

1549 :class:`.Session` objects as well as the return result 

1550 of :class:`~.sessionmaker()` and :class:`~.scoped_session()`. 

1551 

1552 Additionally, it accepts the :class:`.Session` class which 

1553 will apply listeners to all :class:`.Session` instances 

1554 globally. 

1555 

1556 :param raw=False: When True, the "target" argument passed 

1557 to applicable event listener functions that work on individual 

1558 objects will be the instance's :class:`.InstanceState` management 

1559 object, rather than the mapped instance itself. 

1560 

1561 :param restore_load_context=False: Applies to the 

1562 :meth:`.SessionEvents.loaded_as_persistent` event. Restores the loader 

1563 context of the object when the event hook is complete, so that ongoing 

1564 eager load operations continue to target the object appropriately. A 

1565 warning is emitted if the object is moved to a new loader context from 

1566 within this event if this flag is not set. 

1567 

1568 """ 

1569 

1570 _target_class_doc = "SomeSessionClassOrObject" 

1571 

1572 _dispatch_target = Session 

1573 

1574 def _lifecycle_event( # type: ignore [misc] 

1575 fn: Callable[[SessionEvents, Session, Any], None], 

1576 ) -> Callable[[SessionEvents, Session, Any], None]: 

1577 _sessionevents_lifecycle_event_names.add(fn.__name__) 

1578 return fn 

1579 

1580 @classmethod 

1581 def _accept_with( # type: ignore [return] 

1582 cls, target: Any, identifier: str 

1583 ) -> Union[Session, type]: 

1584 if isinstance(target, scoped_session): 

1585 target = target.session_factory 

1586 if not isinstance(target, sessionmaker) and ( 

1587 not isinstance(target, type) or not issubclass(target, Session) 

1588 ): 

1589 raise exc.ArgumentError( 

1590 "Session event listen on a scoped_session " 

1591 "requires that its creation callable " 

1592 "is associated with the Session class." 

1593 ) 

1594 

1595 if isinstance(target, sessionmaker): 

1596 return target.class_ 

1597 elif isinstance(target, type): 

1598 if issubclass(target, scoped_session): 

1599 return Session 

1600 elif issubclass(target, Session): 

1601 return target 

1602 elif isinstance(target, Session): 

1603 return target 

1604 elif hasattr(target, "_no_async_engine_events"): 

1605 target._no_async_engine_events() 

1606 else: 

1607 # allows alternate SessionEvents-like-classes to be consulted 

1608 return event.Events._accept_with(target, identifier) # type: ignore [return-value] # noqa: E501 

1609 

1610 @classmethod 

1611 def _listen( 

1612 cls, 

1613 event_key: Any, 

1614 *, 

1615 raw: bool = False, 

1616 restore_load_context: bool = False, 

1617 **kw: Any, 

1618 ) -> None: 

1619 is_instance_event = ( 

1620 event_key.identifier in _sessionevents_lifecycle_event_names 

1621 ) 

1622 

1623 if is_instance_event: 

1624 if not raw or restore_load_context: 

1625 fn = event_key._listen_fn 

1626 

1627 def wrap( 

1628 session: Session, 

1629 state: InstanceState[_O], 

1630 *arg: Any, 

1631 **kw: Any, 

1632 ) -> Optional[Any]: 

1633 if not raw: 

1634 target = state.obj() 

1635 if target is None: 

1636 # existing behavior is that if the object is 

1637 # garbage collected, no event is emitted 

1638 return None 

1639 else: 

1640 target = state # type: ignore [assignment] 

1641 if restore_load_context: 

1642 runid = state.runid 

1643 try: 

1644 return fn(session, target, *arg, **kw) 

1645 finally: 

1646 if restore_load_context: 

1647 state.runid = runid 

1648 

1649 event_key = event_key.with_wrapper(wrap) 

1650 

1651 event_key.base_listen(**kw) 

1652 

1653 def do_orm_execute(self, orm_execute_state: ORMExecuteState) -> None: 

1654 """Intercept statement executions that occur on behalf of an 

1655 ORM :class:`.Session` object. 

1656 

1657 This event is invoked for all top-level SQL statements invoked from the 

1658 :meth:`_orm.Session.execute` method, as well as related methods such as 

1659 :meth:`_orm.Session.scalars` and :meth:`_orm.Session.scalar`. As of 

1660 SQLAlchemy 1.4, all ORM queries that run through the 

1661 :meth:`_orm.Session.execute` method as well as related methods 

1662 :meth:`_orm.Session.scalars`, :meth:`_orm.Session.scalar` etc. 

1663 will participate in this event. 

1664 This event hook does **not** apply to the queries that are 

1665 emitted internally within the ORM flush process, i.e. the 

1666 process described at :ref:`session_flushing`. 

1667 

1668 .. note:: The :meth:`_orm.SessionEvents.do_orm_execute` event hook 

1669 is triggered **for ORM statement executions only**, meaning those 

1670 invoked via the :meth:`_orm.Session.execute` and similar methods on 

1671 the :class:`_orm.Session` object. It does **not** trigger for 

1672 statements that are invoked by SQLAlchemy Core only, i.e. statements 

1673 invoked directly using :meth:`_engine.Connection.execute` or 

1674 otherwise originating from an :class:`_engine.Engine` object without 

1675 any :class:`_orm.Session` involved. To intercept **all** SQL 

1676 executions regardless of whether the Core or ORM APIs are in use, 

1677 see the event hooks at :class:`.ConnectionEvents`, such as 

1678 :meth:`.ConnectionEvents.before_execute` and 

1679 :meth:`.ConnectionEvents.before_cursor_execute`. 

1680 

1681 Also, this event hook does **not** apply to queries that are 

1682 emitted internally within the ORM flush process, 

1683 i.e. the process described at :ref:`session_flushing`; to 

1684 intercept steps within the flush process, see the event 

1685 hooks described at :ref:`session_persistence_events` as 

1686 well as :ref:`session_persistence_mapper`. 

1687 

1688 This event is a ``do_`` event, meaning it has the capability to replace 

1689 the operation that the :meth:`_orm.Session.execute` method normally 

1690 performs. The intended use for this includes sharding and 

1691 result-caching schemes which may seek to invoke the same statement 

1692 across multiple database connections, returning a result that is 

1693 merged from each of them, or which don't invoke the statement at all, 

1694 instead returning data from a cache. 

1695 

1696 The hook intends to replace the use of the 

1697 ``Query._execute_and_instances`` method that could be subclassed prior 

1698 to SQLAlchemy 1.4. 

1699 

1700 :param orm_execute_state: an instance of :class:`.ORMExecuteState` 

1701 which contains all information about the current execution, as well 

1702 as helper functions used to derive other commonly required 

1703 information. See that object for details. 

1704 

1705 .. seealso:: 

1706 

1707 :ref:`session_execute_events` - top level documentation on how 

1708 to use :meth:`_orm.SessionEvents.do_orm_execute` 

1709 

1710 :class:`.ORMExecuteState` - the object passed to the 

1711 :meth:`_orm.SessionEvents.do_orm_execute` event which contains 

1712 all information about the statement to be invoked. It also 

1713 provides an interface to extend the current statement, options, 

1714 and parameters as well as an option that allows programmatic 

1715 invocation of the statement at any point. 

1716 

1717 :ref:`examples_session_orm_events` - includes examples of using 

1718 :meth:`_orm.SessionEvents.do_orm_execute` 

1719 

1720 :ref:`examples_caching` - an example of how to integrate 

1721 Dogpile caching with the ORM :class:`_orm.Session` making use 

1722 of the :meth:`_orm.SessionEvents.do_orm_execute` event hook. 

1723 

1724 :ref:`examples_sharding` - the Horizontal Sharding example / 

1725 extension relies upon the 

1726 :meth:`_orm.SessionEvents.do_orm_execute` event hook to invoke a 

1727 SQL statement on multiple backends and return a merged result. 

1728 

1729 

1730 .. versionadded:: 1.4 

1731 

1732 """ 

1733 

1734 def after_transaction_create( 

1735 self, session: Session, transaction: SessionTransaction 

1736 ) -> None: 

1737 """Execute when a new :class:`.SessionTransaction` is created. 

1738 

1739 This event differs from :meth:`~.SessionEvents.after_begin` 

1740 in that it occurs for each :class:`.SessionTransaction` 

1741 overall, as opposed to when transactions are begun 

1742 on individual database connections. It is also invoked 

1743 for nested transactions and subtransactions, and is always 

1744 matched by a corresponding 

1745 :meth:`~.SessionEvents.after_transaction_end` event 

1746 (assuming normal operation of the :class:`.Session`). 

1747 

1748 :param session: the target :class:`.Session`. 

1749 :param transaction: the target :class:`.SessionTransaction`. 

1750 

1751 To detect if this is the outermost 

1752 :class:`.SessionTransaction`, as opposed to a "subtransaction" or a 

1753 SAVEPOINT, test that the :attr:`.SessionTransaction.parent` attribute 

1754 is ``None``:: 

1755 

1756 @event.listens_for(session, "after_transaction_create") 

1757 def after_transaction_create(session, transaction): 

1758 if transaction.parent is None: 

1759 ... # work with top-level transaction 

1760 

1761 To detect if the :class:`.SessionTransaction` is a SAVEPOINT, use the 

1762 :attr:`.SessionTransaction.nested` attribute:: 

1763 

1764 @event.listens_for(session, "after_transaction_create") 

1765 def after_transaction_create(session, transaction): 

1766 if transaction.nested: 

1767 ... # work with SAVEPOINT transaction 

1768 

1769 .. seealso:: 

1770 

1771 :class:`.SessionTransaction` 

1772 

1773 :meth:`~.SessionEvents.after_transaction_end` 

1774 

1775 """ 

1776 

1777 def after_transaction_end( 

1778 self, session: Session, transaction: SessionTransaction 

1779 ) -> None: 

1780 """Execute when the span of a :class:`.SessionTransaction` ends. 

1781 

1782 This event differs from :meth:`~.SessionEvents.after_commit` 

1783 in that it corresponds to all :class:`.SessionTransaction` 

1784 objects in use, including those for nested transactions 

1785 and subtransactions, and is always matched by a corresponding 

1786 :meth:`~.SessionEvents.after_transaction_create` event. 

1787 

1788 :param session: the target :class:`.Session`. 

1789 :param transaction: the target :class:`.SessionTransaction`. 

1790 

1791 To detect if this is the outermost 

1792 :class:`.SessionTransaction`, as opposed to a "subtransaction" or a 

1793 SAVEPOINT, test that the :attr:`.SessionTransaction.parent` attribute 

1794 is ``None``:: 

1795 

1796 @event.listens_for(session, "after_transaction_create") 

1797 def after_transaction_end(session, transaction): 

1798 if transaction.parent is None: 

1799 ... # work with top-level transaction 

1800 

1801 To detect if the :class:`.SessionTransaction` is a SAVEPOINT, use the 

1802 :attr:`.SessionTransaction.nested` attribute:: 

1803 

1804 @event.listens_for(session, "after_transaction_create") 

1805 def after_transaction_end(session, transaction): 

1806 if transaction.nested: 

1807 ... # work with SAVEPOINT transaction 

1808 

1809 .. seealso:: 

1810 

1811 :class:`.SessionTransaction` 

1812 

1813 :meth:`~.SessionEvents.after_transaction_create` 

1814 

1815 """ 

1816 

1817 def before_commit(self, session: Session) -> None: 

1818 """Execute before commit is called. 

1819 

1820 .. note:: 

1821 

1822 The :meth:`~.SessionEvents.before_commit` hook is *not* per-flush, 

1823 that is, the :class:`.Session` can emit SQL to the database 

1824 many times within the scope of a transaction. 

1825 For interception of these events, use the 

1826 :meth:`~.SessionEvents.before_flush`, 

1827 :meth:`~.SessionEvents.after_flush`, or 

1828 :meth:`~.SessionEvents.after_flush_postexec` 

1829 events. 

1830 

1831 :param session: The target :class:`.Session`. 

1832 

1833 .. seealso:: 

1834 

1835 :meth:`~.SessionEvents.after_commit` 

1836 

1837 :meth:`~.SessionEvents.after_begin` 

1838 

1839 :meth:`~.SessionEvents.after_transaction_create` 

1840 

1841 :meth:`~.SessionEvents.after_transaction_end` 

1842 

1843 """ 

1844 

1845 def after_commit(self, session: Session) -> None: 

1846 """Execute after a commit has occurred. 

1847 

1848 .. note:: 

1849 

1850 The :meth:`~.SessionEvents.after_commit` hook is *not* per-flush, 

1851 that is, the :class:`.Session` can emit SQL to the database 

1852 many times within the scope of a transaction. 

1853 For interception of these events, use the 

1854 :meth:`~.SessionEvents.before_flush`, 

1855 :meth:`~.SessionEvents.after_flush`, or 

1856 :meth:`~.SessionEvents.after_flush_postexec` 

1857 events. 

1858 

1859 .. note:: 

1860 

1861 The :class:`.Session` is not in an active transaction 

1862 when the :meth:`~.SessionEvents.after_commit` event is invoked, 

1863 and therefore can not emit SQL. To emit SQL corresponding to 

1864 every transaction, use the :meth:`~.SessionEvents.before_commit` 

1865 event. 

1866 

1867 :param session: The target :class:`.Session`. 

1868 

1869 .. seealso:: 

1870 

1871 :meth:`~.SessionEvents.before_commit` 

1872 

1873 :meth:`~.SessionEvents.after_begin` 

1874 

1875 :meth:`~.SessionEvents.after_transaction_create` 

1876 

1877 :meth:`~.SessionEvents.after_transaction_end` 

1878 

1879 """ 

1880 

1881 def after_rollback(self, session: Session) -> None: 

1882 """Execute after a real DBAPI rollback has occurred. 

1883 

1884 Note that this event only fires when the *actual* rollback against 

1885 the database occurs - it does *not* fire each time the 

1886 :meth:`.Session.rollback` method is called, if the underlying 

1887 DBAPI transaction has already been rolled back. In many 

1888 cases, the :class:`.Session` will not be in 

1889 an "active" state during this event, as the current 

1890 transaction is not valid. To acquire a :class:`.Session` 

1891 which is active after the outermost rollback has proceeded, 

1892 use the :meth:`.SessionEvents.after_soft_rollback` event, checking the 

1893 :attr:`.Session.is_active` flag. 

1894 

1895 :param session: The target :class:`.Session`. 

1896 

1897 """ 

1898 

1899 def after_soft_rollback( 

1900 self, session: Session, previous_transaction: SessionTransaction 

1901 ) -> None: 

1902 """Execute after any rollback has occurred, including "soft" 

1903 rollbacks that don't actually emit at the DBAPI level. 

1904 

1905 This corresponds to both nested and outer rollbacks, i.e. 

1906 the innermost rollback that calls the DBAPI's 

1907 rollback() method, as well as the enclosing rollback 

1908 calls that only pop themselves from the transaction stack. 

1909 

1910 The given :class:`.Session` can be used to invoke SQL and 

1911 :meth:`.Session.query` operations after an outermost rollback 

1912 by first checking the :attr:`.Session.is_active` flag:: 

1913 

1914 @event.listens_for(Session, "after_soft_rollback") 

1915 def do_something(session, previous_transaction): 

1916 if session.is_active: 

1917 session.execute(text("select * from some_table")) 

1918 

1919 :param session: The target :class:`.Session`. 

1920 :param previous_transaction: The :class:`.SessionTransaction` 

1921 transactional marker object which was just closed. The current 

1922 :class:`.SessionTransaction` for the given :class:`.Session` is 

1923 available via the :attr:`.Session.transaction` attribute. 

1924 

1925 """ 

1926 

1927 def before_flush( 

1928 self, 

1929 session: Session, 

1930 flush_context: UOWTransaction, 

1931 instances: Optional[Sequence[_O]], 

1932 ) -> None: 

1933 """Execute before flush process has started. 

1934 

1935 :param session: The target :class:`.Session`. 

1936 :param flush_context: Internal :class:`.UOWTransaction` object 

1937 which handles the details of the flush. 

1938 :param instances: Usually ``None``, this is the collection of 

1939 objects which can be passed to the :meth:`.Session.flush` method 

1940 (note this usage is deprecated). 

1941 

1942 .. seealso:: 

1943 

1944 :meth:`~.SessionEvents.after_flush` 

1945 

1946 :meth:`~.SessionEvents.after_flush_postexec` 

1947 

1948 :ref:`session_persistence_events` 

1949 

1950 """ 

1951 

1952 def after_flush( 

1953 self, session: Session, flush_context: UOWTransaction 

1954 ) -> None: 

1955 """Execute after flush has completed, but before commit has been 

1956 called. 

1957 

1958 Note that the session's state is still in pre-flush, i.e. 'new', 

1959 'dirty', and 'deleted' lists still show pre-flush state as well 

1960 as the history settings on instance attributes. 

1961 

1962 .. warning:: This event runs after the :class:`.Session` has emitted 

1963 SQL to modify the database, but **before** it has altered its 

1964 internal state to reflect those changes, including that newly 

1965 inserted objects are placed into the identity map. ORM operations 

1966 emitted within this event such as loads of related items 

1967 may produce new identity map entries that will immediately 

1968 be replaced, sometimes causing confusing results. SQLAlchemy will 

1969 emit a warning for this condition as of version 1.3.9. 

1970 

1971 :param session: The target :class:`.Session`. 

1972 :param flush_context: Internal :class:`.UOWTransaction` object 

1973 which handles the details of the flush. 

1974 

1975 .. seealso:: 

1976 

1977 :meth:`~.SessionEvents.before_flush` 

1978 

1979 :meth:`~.SessionEvents.after_flush_postexec` 

1980 

1981 :ref:`session_persistence_events` 

1982 

1983 """ 

1984 

1985 def after_flush_postexec( 

1986 self, session: Session, flush_context: UOWTransaction 

1987 ) -> None: 

1988 """Execute after flush has completed, and after the post-exec 

1989 state occurs. 

1990 

1991 This will be when the 'new', 'dirty', and 'deleted' lists are in 

1992 their final state. An actual commit() may or may not have 

1993 occurred, depending on whether or not the flush started its own 

1994 transaction or participated in a larger transaction. 

1995 

1996 :param session: The target :class:`.Session`. 

1997 :param flush_context: Internal :class:`.UOWTransaction` object 

1998 which handles the details of the flush. 

1999 

2000 

2001 .. seealso:: 

2002 

2003 :meth:`~.SessionEvents.before_flush` 

2004 

2005 :meth:`~.SessionEvents.after_flush` 

2006 

2007 :ref:`session_persistence_events` 

2008 

2009 """ 

2010 

2011 def after_begin( 

2012 self, 

2013 session: Session, 

2014 transaction: SessionTransaction, 

2015 connection: Connection, 

2016 ) -> None: 

2017 """Execute after a transaction is begun on a connection. 

2018 

2019 .. note:: This event is called within the process of the 

2020 :class:`_orm.Session` modifying its own internal state. 

2021 To invoke SQL operations within this hook, use the 

2022 :class:`_engine.Connection` provided to the event; 

2023 do not run SQL operations using the :class:`_orm.Session` 

2024 directly. 

2025 

2026 :param session: The target :class:`.Session`. 

2027 :param transaction: The :class:`.SessionTransaction`. 

2028 :param connection: The :class:`_engine.Connection` object 

2029 which will be used for SQL statements. 

2030 

2031 .. seealso:: 

2032 

2033 :meth:`~.SessionEvents.before_commit` 

2034 

2035 :meth:`~.SessionEvents.after_commit` 

2036 

2037 :meth:`~.SessionEvents.after_transaction_create` 

2038 

2039 :meth:`~.SessionEvents.after_transaction_end` 

2040 

2041 """ 

2042 

2043 @_lifecycle_event 

2044 def before_attach(self, session: Session, instance: _O) -> None: 

2045 """Execute before an instance is attached to a session. 

2046 

2047 This is called before an add, delete or merge causes 

2048 the object to be part of the session. 

2049 

2050 .. seealso:: 

2051 

2052 :meth:`~.SessionEvents.after_attach` 

2053 

2054 :ref:`session_lifecycle_events` 

2055 

2056 """ 

2057 

2058 @_lifecycle_event 

2059 def after_attach(self, session: Session, instance: _O) -> None: 

2060 """Execute after an instance is attached to a session. 

2061 

2062 This is called after an add, delete or merge. 

2063 

2064 .. note:: 

2065 

2066 As of 0.8, this event fires off *after* the item 

2067 has been fully associated with the session, which is 

2068 different than previous releases. For event 

2069 handlers that require the object not yet 

2070 be part of session state (such as handlers which 

2071 may autoflush while the target object is not 

2072 yet complete) consider the 

2073 new :meth:`.before_attach` event. 

2074 

2075 .. seealso:: 

2076 

2077 :meth:`~.SessionEvents.before_attach` 

2078 

2079 :ref:`session_lifecycle_events` 

2080 

2081 """ 

2082 

2083 def after_bulk_update(self, update_context: _O) -> None: 

2084 """Event for after the legacy :meth:`_orm.Query.update` method 

2085 has been called. 

2086 

2087 .. legacy:: The :meth:`_orm.SessionEvents.after_bulk_update` method 

2088 is a legacy event hook as of SQLAlchemy 2.0. The event 

2089 **does not participate** in :term:`2.0 style` invocations 

2090 using :func:`_dml.update` documented at 

2091 :ref:`orm_queryguide_update_delete_where`. For 2.0 style use, 

2092 the :meth:`_orm.SessionEvents.do_orm_execute` hook will intercept 

2093 these calls. 

2094 

2095 :param update_context: an "update context" object which contains 

2096 details about the update, including these attributes: 

2097 

2098 * ``session`` - the :class:`.Session` involved 

2099 * ``query`` -the :class:`_query.Query` 

2100 object that this update operation 

2101 was called upon. 

2102 * ``values`` The "values" dictionary that was passed to 

2103 :meth:`_query.Query.update`. 

2104 * ``result`` the :class:`_engine.CursorResult` 

2105 returned as a result of the 

2106 bulk UPDATE operation. 

2107 

2108 .. versionchanged:: 1.4 the update_context no longer has a 

2109 ``QueryContext`` object associated with it. 

2110 

2111 .. seealso:: 

2112 

2113 :meth:`.QueryEvents.before_compile_update` 

2114 

2115 :meth:`.SessionEvents.after_bulk_delete` 

2116 

2117 """ 

2118 

2119 def after_bulk_delete(self, delete_context: _O) -> None: 

2120 """Event for after the legacy :meth:`_orm.Query.delete` method 

2121 has been called. 

2122 

2123 .. legacy:: The :meth:`_orm.SessionEvents.after_bulk_delete` method 

2124 is a legacy event hook as of SQLAlchemy 2.0. The event 

2125 **does not participate** in :term:`2.0 style` invocations 

2126 using :func:`_dml.delete` documented at 

2127 :ref:`orm_queryguide_update_delete_where`. For 2.0 style use, 

2128 the :meth:`_orm.SessionEvents.do_orm_execute` hook will intercept 

2129 these calls. 

2130 

2131 :param delete_context: a "delete context" object which contains 

2132 details about the update, including these attributes: 

2133 

2134 * ``session`` - the :class:`.Session` involved 

2135 * ``query`` -the :class:`_query.Query` 

2136 object that this update operation 

2137 was called upon. 

2138 * ``result`` the :class:`_engine.CursorResult` 

2139 returned as a result of the 

2140 bulk DELETE operation. 

2141 

2142 .. versionchanged:: 1.4 the update_context no longer has a 

2143 ``QueryContext`` object associated with it. 

2144 

2145 .. seealso:: 

2146 

2147 :meth:`.QueryEvents.before_compile_delete` 

2148 

2149 :meth:`.SessionEvents.after_bulk_update` 

2150 

2151 """ 

2152 

2153 @_lifecycle_event 

2154 def transient_to_pending(self, session: Session, instance: _O) -> None: 

2155 """Intercept the "transient to pending" transition for a specific 

2156 object. 

2157 

2158 This event is a specialization of the 

2159 :meth:`.SessionEvents.after_attach` event which is only invoked 

2160 for this specific transition. It is invoked typically during the 

2161 :meth:`.Session.add` call. 

2162 

2163 :param session: target :class:`.Session` 

2164 

2165 :param instance: the ORM-mapped instance being operated upon. 

2166 

2167 .. seealso:: 

2168 

2169 :ref:`session_lifecycle_events` 

2170 

2171 """ 

2172 

2173 @_lifecycle_event 

2174 def pending_to_transient(self, session: Session, instance: _O) -> None: 

2175 """Intercept the "pending to transient" transition for a specific 

2176 object. 

2177 

2178 This less common transition occurs when an pending object that has 

2179 not been flushed is evicted from the session; this can occur 

2180 when the :meth:`.Session.rollback` method rolls back the transaction, 

2181 or when the :meth:`.Session.expunge` method is used. 

2182 

2183 :param session: target :class:`.Session` 

2184 

2185 :param instance: the ORM-mapped instance being operated upon. 

2186 

2187 .. seealso:: 

2188 

2189 :ref:`session_lifecycle_events` 

2190 

2191 """ 

2192 

2193 @_lifecycle_event 

2194 def persistent_to_transient(self, session: Session, instance: _O) -> None: 

2195 """Intercept the "persistent to transient" transition for a specific 

2196 object. 

2197 

2198 This less common transition occurs when an pending object that has 

2199 has been flushed is evicted from the session; this can occur 

2200 when the :meth:`.Session.rollback` method rolls back the transaction. 

2201 

2202 :param session: target :class:`.Session` 

2203 

2204 :param instance: the ORM-mapped instance being operated upon. 

2205 

2206 .. seealso:: 

2207 

2208 :ref:`session_lifecycle_events` 

2209 

2210 """ 

2211 

2212 @_lifecycle_event 

2213 def pending_to_persistent(self, session: Session, instance: _O) -> None: 

2214 """Intercept the "pending to persistent"" transition for a specific 

2215 object. 

2216 

2217 This event is invoked within the flush process, and is 

2218 similar to scanning the :attr:`.Session.new` collection within 

2219 the :meth:`.SessionEvents.after_flush` event. However, in this 

2220 case the object has already been moved to the persistent state 

2221 when the event is called. 

2222 

2223 :param session: target :class:`.Session` 

2224 

2225 :param instance: the ORM-mapped instance being operated upon. 

2226 

2227 .. seealso:: 

2228 

2229 :ref:`session_lifecycle_events` 

2230 

2231 """ 

2232 

2233 @_lifecycle_event 

2234 def detached_to_persistent(self, session: Session, instance: _O) -> None: 

2235 """Intercept the "detached to persistent" transition for a specific 

2236 object. 

2237 

2238 This event is a specialization of the 

2239 :meth:`.SessionEvents.after_attach` event which is only invoked 

2240 for this specific transition. It is invoked typically during the 

2241 :meth:`.Session.add` call, as well as during the 

2242 :meth:`.Session.delete` call if the object was not previously 

2243 associated with the 

2244 :class:`.Session` (note that an object marked as "deleted" remains 

2245 in the "persistent" state until the flush proceeds). 

2246 

2247 .. note:: 

2248 

2249 If the object becomes persistent as part of a call to 

2250 :meth:`.Session.delete`, the object is **not** yet marked as 

2251 deleted when this event is called. To detect deleted objects, 

2252 check the ``deleted`` flag sent to the 

2253 :meth:`.SessionEvents.persistent_to_detached` to event after the 

2254 flush proceeds, or check the :attr:`.Session.deleted` collection 

2255 within the :meth:`.SessionEvents.before_flush` event if deleted 

2256 objects need to be intercepted before the flush. 

2257 

2258 :param session: target :class:`.Session` 

2259 

2260 :param instance: the ORM-mapped instance being operated upon. 

2261 

2262 .. seealso:: 

2263 

2264 :ref:`session_lifecycle_events` 

2265 

2266 """ 

2267 

2268 @_lifecycle_event 

2269 def loaded_as_persistent(self, session: Session, instance: _O) -> None: 

2270 """Intercept the "loaded as persistent" transition for a specific 

2271 object. 

2272 

2273 This event is invoked within the ORM loading process, and is invoked 

2274 very similarly to the :meth:`.InstanceEvents.load` event. However, 

2275 the event here is linkable to a :class:`.Session` class or instance, 

2276 rather than to a mapper or class hierarchy, and integrates 

2277 with the other session lifecycle events smoothly. The object 

2278 is guaranteed to be present in the session's identity map when 

2279 this event is called. 

2280 

2281 .. note:: This event is invoked within the loader process before 

2282 eager loaders may have been completed, and the object's state may 

2283 not be complete. Additionally, invoking row-level refresh 

2284 operations on the object will place the object into a new loader 

2285 context, interfering with the existing load context. See the note 

2286 on :meth:`.InstanceEvents.load` for background on making use of the 

2287 :paramref:`.SessionEvents.restore_load_context` parameter, which 

2288 works in the same manner as that of 

2289 :paramref:`.InstanceEvents.restore_load_context`, in order to 

2290 resolve this scenario. 

2291 

2292 :param session: target :class:`.Session` 

2293 

2294 :param instance: the ORM-mapped instance being operated upon. 

2295 

2296 .. seealso:: 

2297 

2298 :ref:`session_lifecycle_events` 

2299 

2300 """ 

2301 

2302 @_lifecycle_event 

2303 def persistent_to_deleted(self, session: Session, instance: _O) -> None: 

2304 """Intercept the "persistent to deleted" transition for a specific 

2305 object. 

2306 

2307 This event is invoked when a persistent object's identity 

2308 is deleted from the database within a flush, however the object 

2309 still remains associated with the :class:`.Session` until the 

2310 transaction completes. 

2311 

2312 If the transaction is rolled back, the object moves again 

2313 to the persistent state, and the 

2314 :meth:`.SessionEvents.deleted_to_persistent` event is called. 

2315 If the transaction is committed, the object becomes detached, 

2316 which will emit the :meth:`.SessionEvents.deleted_to_detached` 

2317 event. 

2318 

2319 Note that while the :meth:`.Session.delete` method is the primary 

2320 public interface to mark an object as deleted, many objects 

2321 get deleted due to cascade rules, which are not always determined 

2322 until flush time. Therefore, there's no way to catch 

2323 every object that will be deleted until the flush has proceeded. 

2324 the :meth:`.SessionEvents.persistent_to_deleted` event is therefore 

2325 invoked at the end of a flush. 

2326 

2327 .. seealso:: 

2328 

2329 :ref:`session_lifecycle_events` 

2330 

2331 """ 

2332 

2333 @_lifecycle_event 

2334 def deleted_to_persistent(self, session: Session, instance: _O) -> None: 

2335 """Intercept the "deleted to persistent" transition for a specific 

2336 object. 

2337 

2338 This transition occurs only when an object that's been deleted 

2339 successfully in a flush is restored due to a call to 

2340 :meth:`.Session.rollback`. The event is not called under 

2341 any other circumstances. 

2342 

2343 .. seealso:: 

2344 

2345 :ref:`session_lifecycle_events` 

2346 

2347 """ 

2348 

2349 @_lifecycle_event 

2350 def deleted_to_detached(self, session: Session, instance: _O) -> None: 

2351 """Intercept the "deleted to detached" transition for a specific 

2352 object. 

2353 

2354 This event is invoked when a deleted object is evicted 

2355 from the session. The typical case when this occurs is when 

2356 the transaction for a :class:`.Session` in which the object 

2357 was deleted is committed; the object moves from the deleted 

2358 state to the detached state. 

2359 

2360 It is also invoked for objects that were deleted in a flush 

2361 when the :meth:`.Session.expunge_all` or :meth:`.Session.close` 

2362 events are called, as well as if the object is individually 

2363 expunged from its deleted state via :meth:`.Session.expunge`. 

2364 

2365 .. seealso:: 

2366 

2367 :ref:`session_lifecycle_events` 

2368 

2369 """ 

2370 

2371 @_lifecycle_event 

2372 def persistent_to_detached(self, session: Session, instance: _O) -> None: 

2373 """Intercept the "persistent to detached" transition for a specific 

2374 object. 

2375 

2376 This event is invoked when a persistent object is evicted 

2377 from the session. There are many conditions that cause this 

2378 to happen, including: 

2379 

2380 * using a method such as :meth:`.Session.expunge` 

2381 or :meth:`.Session.close` 

2382 

2383 * Calling the :meth:`.Session.rollback` method, when the object 

2384 was part of an INSERT statement for that session's transaction 

2385 

2386 

2387 :param session: target :class:`.Session` 

2388 

2389 :param instance: the ORM-mapped instance being operated upon. 

2390 

2391 :param deleted: boolean. If True, indicates this object moved 

2392 to the detached state because it was marked as deleted and flushed. 

2393 

2394 

2395 .. seealso:: 

2396 

2397 :ref:`session_lifecycle_events` 

2398 

2399 """ 

2400 

2401 

2402class AttributeEvents(event.Events[QueryableAttribute[Any]]): 

2403 r"""Define events for object attributes. 

2404 

2405 These are typically defined on the class-bound descriptor for the 

2406 target class. 

2407 

2408 For example, to register a listener that will receive the 

2409 :meth:`_orm.AttributeEvents.append` event:: 

2410 

2411 from sqlalchemy import event 

2412 

2413 

2414 @event.listens_for(MyClass.collection, "append", propagate=True) 

2415 def my_append_listener(target, value, initiator): 

2416 print("received append event for target: %s" % target) 

2417 

2418 Listeners have the option to return a possibly modified version of the 

2419 value, when the :paramref:`.AttributeEvents.retval` flag is passed to 

2420 :func:`.event.listen` or :func:`.event.listens_for`, such as below, 

2421 illustrated using the :meth:`_orm.AttributeEvents.set` event:: 

2422 

2423 def validate_phone(target, value, oldvalue, initiator): 

2424 "Strip non-numeric characters from a phone number" 

2425 

2426 return re.sub(r"\D", "", value) 

2427 

2428 

2429 # setup listener on UserContact.phone attribute, instructing 

2430 # it to use the return value 

2431 listen(UserContact.phone, "set", validate_phone, retval=True) 

2432 

2433 A validation function like the above can also raise an exception 

2434 such as :exc:`ValueError` to halt the operation. 

2435 

2436 The :paramref:`.AttributeEvents.propagate` flag is also important when 

2437 applying listeners to mapped classes that also have mapped subclasses, 

2438 as when using mapper inheritance patterns:: 

2439 

2440 

2441 @event.listens_for(MySuperClass.attr, "set", propagate=True) 

2442 def receive_set(target, value, initiator): 

2443 print("value set: %s" % target) 

2444 

2445 The full list of modifiers available to the :func:`.event.listen` 

2446 and :func:`.event.listens_for` functions are below. 

2447 

2448 :param active_history=False: When True, indicates that the 

2449 "set" event would like to receive the "old" value being 

2450 replaced unconditionally, even if this requires firing off 

2451 database loads. Note that ``active_history`` can also be 

2452 set directly via :func:`.column_property` and 

2453 :func:`_orm.relationship`. 

2454 

2455 :param propagate=False: When True, the listener function will 

2456 be established not just for the class attribute given, but 

2457 for attributes of the same name on all current subclasses 

2458 of that class, as well as all future subclasses of that 

2459 class, using an additional listener that listens for 

2460 instrumentation events. 

2461 :param raw=False: When True, the "target" argument to the 

2462 event will be the :class:`.InstanceState` management 

2463 object, rather than the mapped instance itself. 

2464 :param retval=False: when True, the user-defined event 

2465 listening must return the "value" argument from the 

2466 function. This gives the listening function the opportunity 

2467 to change the value that is ultimately used for a "set" 

2468 or "append" event. 

2469 

2470 """ 

2471 

2472 _target_class_doc = "SomeClass.some_attribute" 

2473 _dispatch_target = QueryableAttribute 

2474 

2475 @staticmethod 

2476 def _set_dispatch( 

2477 cls: Type[_HasEventsDispatch[Any]], dispatch_cls: Type[_Dispatch[Any]] 

2478 ) -> _Dispatch[Any]: 

2479 dispatch = event.Events._set_dispatch(cls, dispatch_cls) 

2480 dispatch_cls._active_history = False 

2481 return dispatch 

2482 

2483 @classmethod 

2484 def _accept_with( 

2485 cls, 

2486 target: Union[QueryableAttribute[Any], Type[QueryableAttribute[Any]]], 

2487 identifier: str, 

2488 ) -> Union[QueryableAttribute[Any], Type[QueryableAttribute[Any]]]: 

2489 # TODO: coverage 

2490 if isinstance(target, interfaces.MapperProperty): 

2491 return getattr(target.parent.class_, target.key) 

2492 else: 

2493 return target 

2494 

2495 @classmethod 

2496 def _listen( # type: ignore [override] 

2497 cls, 

2498 event_key: _EventKey[QueryableAttribute[Any]], 

2499 active_history: bool = False, 

2500 raw: bool = False, 

2501 retval: bool = False, 

2502 propagate: bool = False, 

2503 include_key: bool = False, 

2504 ) -> None: 

2505 target, fn = event_key.dispatch_target, event_key._listen_fn 

2506 

2507 if active_history: 

2508 target.dispatch._active_history = True 

2509 

2510 if not raw or not retval or not include_key: 

2511 

2512 def wrap(target: InstanceState[_O], *arg: Any, **kw: Any) -> Any: 

2513 if not raw: 

2514 target = target.obj() # type: ignore [assignment] 

2515 if not retval: 

2516 if arg: 

2517 value = arg[0] 

2518 else: 

2519 value = None 

2520 if include_key: 

2521 fn(target, *arg, **kw) 

2522 else: 

2523 fn(target, *arg) 

2524 return value 

2525 else: 

2526 if include_key: 

2527 return fn(target, *arg, **kw) 

2528 else: 

2529 return fn(target, *arg) 

2530 

2531 event_key = event_key.with_wrapper(wrap) 

2532 

2533 event_key.base_listen(propagate=propagate) 

2534 

2535 if propagate: 

2536 manager = instrumentation.manager_of_class(target.class_) 

2537 

2538 for mgr in manager.subclass_managers(True): # type: ignore [no-untyped-call] # noqa: E501 

2539 event_key.with_dispatch_target(mgr[target.key]).base_listen( 

2540 propagate=True 

2541 ) 

2542 if active_history: 

2543 mgr[target.key].dispatch._active_history = True 

2544 

2545 def append( 

2546 self, 

2547 target: _O, 

2548 value: _T, 

2549 initiator: Event, 

2550 *, 

2551 key: EventConstants = NO_KEY, 

2552 ) -> Optional[_T]: 

2553 """Receive a collection append event. 

2554 

2555 The append event is invoked for each element as it is appended 

2556 to the collection. This occurs for single-item appends as well 

2557 as for a "bulk replace" operation. 

2558 

2559 :param target: the object instance receiving the event. 

2560 If the listener is registered with ``raw=True``, this will 

2561 be the :class:`.InstanceState` object. 

2562 :param value: the value being appended. If this listener 

2563 is registered with ``retval=True``, the listener 

2564 function must return this value, or a new value which 

2565 replaces it. 

2566 :param initiator: An instance of :class:`.attributes.Event` 

2567 representing the initiation of the event. May be modified 

2568 from its original value by backref handlers in order to control 

2569 chained event propagation, as well as be inspected for information 

2570 about the source of the event. 

2571 :param key: When the event is established using the 

2572 :paramref:`.AttributeEvents.include_key` parameter set to 

2573 True, this will be the key used in the operation, such as 

2574 ``collection[some_key_or_index] = value``. 

2575 The parameter is not passed 

2576 to the event at all if the the 

2577 :paramref:`.AttributeEvents.include_key` 

2578 was not used to set up the event; this is to allow backwards 

2579 compatibility with existing event handlers that don't include the 

2580 ``key`` parameter. 

2581 

2582 .. versionadded:: 2.0 

2583 

2584 :return: if the event was registered with ``retval=True``, 

2585 the given value, or a new effective value, should be returned. 

2586 

2587 .. seealso:: 

2588 

2589 :class:`.AttributeEvents` - background on listener options such 

2590 as propagation to subclasses. 

2591 

2592 :meth:`.AttributeEvents.bulk_replace` 

2593 

2594 """ 

2595 

2596 def append_wo_mutation( 

2597 self, 

2598 target: _O, 

2599 value: _T, 

2600 initiator: Event, 

2601 *, 

2602 key: EventConstants = NO_KEY, 

2603 ) -> None: 

2604 """Receive a collection append event where the collection was not 

2605 actually mutated. 

2606 

2607 This event differs from :meth:`_orm.AttributeEvents.append` in that 

2608 it is fired off for de-duplicating collections such as sets and 

2609 dictionaries, when the object already exists in the target collection. 

2610 The event does not have a return value and the identity of the 

2611 given object cannot be changed. 

2612 

2613 The event is used for cascading objects into a :class:`_orm.Session` 

2614 when the collection has already been mutated via a backref event. 

2615 

2616 :param target: the object instance receiving the event. 

2617 If the listener is registered with ``raw=True``, this will 

2618 be the :class:`.InstanceState` object. 

2619 :param value: the value that would be appended if the object did not 

2620 already exist in the collection. 

2621 :param initiator: An instance of :class:`.attributes.Event` 

2622 representing the initiation of the event. May be modified 

2623 from its original value by backref handlers in order to control 

2624 chained event propagation, as well as be inspected for information 

2625 about the source of the event. 

2626 :param key: When the event is established using the 

2627 :paramref:`.AttributeEvents.include_key` parameter set to 

2628 True, this will be the key used in the operation, such as 

2629 ``collection[some_key_or_index] = value``. 

2630 The parameter is not passed 

2631 to the event at all if the the 

2632 :paramref:`.AttributeEvents.include_key` 

2633 was not used to set up the event; this is to allow backwards 

2634 compatibility with existing event handlers that don't include the 

2635 ``key`` parameter. 

2636 

2637 .. versionadded:: 2.0 

2638 

2639 :return: No return value is defined for this event. 

2640 

2641 .. versionadded:: 1.4.15 

2642 

2643 """ 

2644 

2645 def bulk_replace( 

2646 self, 

2647 target: _O, 

2648 values: Iterable[_T], 

2649 initiator: Event, 

2650 *, 

2651 keys: Optional[Iterable[EventConstants]] = None, 

2652 ) -> None: 

2653 """Receive a collection 'bulk replace' event. 

2654 

2655 This event is invoked for a sequence of values as they are incoming 

2656 to a bulk collection set operation, which can be 

2657 modified in place before the values are treated as ORM objects. 

2658 This is an "early hook" that runs before the bulk replace routine 

2659 attempts to reconcile which objects are already present in the 

2660 collection and which are being removed by the net replace operation. 

2661 

2662 It is typical that this method be combined with use of the 

2663 :meth:`.AttributeEvents.append` event. When using both of these 

2664 events, note that a bulk replace operation will invoke 

2665 the :meth:`.AttributeEvents.append` event for all new items, 

2666 even after :meth:`.AttributeEvents.bulk_replace` has been invoked 

2667 for the collection as a whole. In order to determine if an 

2668 :meth:`.AttributeEvents.append` event is part of a bulk replace, 

2669 use the symbol :attr:`~.attributes.OP_BULK_REPLACE` to test the 

2670 incoming initiator:: 

2671 

2672 from sqlalchemy.orm.attributes import OP_BULK_REPLACE 

2673 

2674 

2675 @event.listens_for(SomeObject.collection, "bulk_replace") 

2676 def process_collection(target, values, initiator): 

2677 values[:] = [_make_value(value) for value in values] 

2678 

2679 

2680 @event.listens_for(SomeObject.collection, "append", retval=True) 

2681 def process_collection(target, value, initiator): 

2682 # make sure bulk_replace didn't already do it 

2683 if initiator is None or initiator.op is not OP_BULK_REPLACE: 

2684 return _make_value(value) 

2685 else: 

2686 return value 

2687 

2688 :param target: the object instance receiving the event. 

2689 If the listener is registered with ``raw=True``, this will 

2690 be the :class:`.InstanceState` object. 

2691 :param value: a sequence (e.g. a list) of the values being set. The 

2692 handler can modify this list in place. 

2693 :param initiator: An instance of :class:`.attributes.Event` 

2694 representing the initiation of the event. 

2695 :param keys: When the event is established using the 

2696 :paramref:`.AttributeEvents.include_key` parameter set to 

2697 True, this will be the sequence of keys used in the operation, 

2698 typically only for a dictionary update. The parameter is not passed 

2699 to the event at all if the the 

2700 :paramref:`.AttributeEvents.include_key` 

2701 was not used to set up the event; this is to allow backwards 

2702 compatibility with existing event handlers that don't include the 

2703 ``key`` parameter. 

2704 

2705 .. versionadded:: 2.0 

2706 

2707 .. seealso:: 

2708 

2709 :class:`.AttributeEvents` - background on listener options such 

2710 as propagation to subclasses. 

2711 

2712 

2713 """ 

2714 

2715 def remove( 

2716 self, 

2717 target: _O, 

2718 value: _T, 

2719 initiator: Event, 

2720 *, 

2721 key: EventConstants = NO_KEY, 

2722 ) -> None: 

2723 """Receive a collection remove event. 

2724 

2725 :param target: the object instance receiving the event. 

2726 If the listener is registered with ``raw=True``, this will 

2727 be the :class:`.InstanceState` object. 

2728 :param value: the value being removed. 

2729 :param initiator: An instance of :class:`.attributes.Event` 

2730 representing the initiation of the event. May be modified 

2731 from its original value by backref handlers in order to control 

2732 chained event propagation. 

2733 

2734 :param key: When the event is established using the 

2735 :paramref:`.AttributeEvents.include_key` parameter set to 

2736 True, this will be the key used in the operation, such as 

2737 ``del collection[some_key_or_index]``. The parameter is not passed 

2738 to the event at all if the the 

2739 :paramref:`.AttributeEvents.include_key` 

2740 was not used to set up the event; this is to allow backwards 

2741 compatibility with existing event handlers that don't include the 

2742 ``key`` parameter. 

2743 

2744 .. versionadded:: 2.0 

2745 

2746 :return: No return value is defined for this event. 

2747 

2748 

2749 .. seealso:: 

2750 

2751 :class:`.AttributeEvents` - background on listener options such 

2752 as propagation to subclasses. 

2753 

2754 """ 

2755 

2756 def set( 

2757 self, target: _O, value: _T, oldvalue: _T, initiator: Event 

2758 ) -> None: 

2759 """Receive a scalar set event. 

2760 

2761 :param target: the object instance receiving the event. 

2762 If the listener is registered with ``raw=True``, this will 

2763 be the :class:`.InstanceState` object. 

2764 :param value: the value being set. If this listener 

2765 is registered with ``retval=True``, the listener 

2766 function must return this value, or a new value which 

2767 replaces it. 

2768 :param oldvalue: the previous value being replaced. This 

2769 may also be the symbol ``NEVER_SET`` or ``NO_VALUE``. 

2770 If the listener is registered with ``active_history=True``, 

2771 the previous value of the attribute will be loaded from 

2772 the database if the existing value is currently unloaded 

2773 or expired. 

2774 :param initiator: An instance of :class:`.attributes.Event` 

2775 representing the initiation of the event. May be modified 

2776 from its original value by backref handlers in order to control 

2777 chained event propagation. 

2778 

2779 :return: if the event was registered with ``retval=True``, 

2780 the given value, or a new effective value, should be returned. 

2781 

2782 .. seealso:: 

2783 

2784 :class:`.AttributeEvents` - background on listener options such 

2785 as propagation to subclasses. 

2786 

2787 """ 

2788 

2789 def init_scalar( 

2790 self, target: _O, value: _T, dict_: Dict[Any, Any] 

2791 ) -> None: 

2792 r"""Receive a scalar "init" event. 

2793 

2794 This event is invoked when an uninitialized, unpersisted scalar 

2795 attribute is accessed, e.g. read:: 

2796 

2797 

2798 x = my_object.some_attribute 

2799 

2800 The ORM's default behavior when this occurs for an un-initialized 

2801 attribute is to return the value ``None``; note this differs from 

2802 Python's usual behavior of raising ``AttributeError``. The 

2803 event here can be used to customize what value is actually returned, 

2804 with the assumption that the event listener would be mirroring 

2805 a default generator that is configured on the Core 

2806 :class:`_schema.Column` 

2807 object as well. 

2808 

2809 Since a default generator on a :class:`_schema.Column` 

2810 might also produce 

2811 a changing value such as a timestamp, the 

2812 :meth:`.AttributeEvents.init_scalar` 

2813 event handler can also be used to **set** the newly returned value, so 

2814 that a Core-level default generation function effectively fires off 

2815 only once, but at the moment the attribute is accessed on the 

2816 non-persisted object. Normally, no change to the object's state 

2817 is made when an uninitialized attribute is accessed (much older 

2818 SQLAlchemy versions did in fact change the object's state). 

2819 

2820 If a default generator on a column returned a particular constant, 

2821 a handler might be used as follows:: 

2822 

2823 SOME_CONSTANT = 3.1415926 

2824 

2825 

2826 class MyClass(Base): 

2827 # ... 

2828 

2829 some_attribute = Column(Numeric, default=SOME_CONSTANT) 

2830 

2831 

2832 @event.listens_for( 

2833 MyClass.some_attribute, "init_scalar", retval=True, propagate=True 

2834 ) 

2835 def _init_some_attribute(target, dict_, value): 

2836 dict_["some_attribute"] = SOME_CONSTANT 

2837 return SOME_CONSTANT 

2838 

2839 Above, we initialize the attribute ``MyClass.some_attribute`` to the 

2840 value of ``SOME_CONSTANT``. The above code includes the following 

2841 features: 

2842 

2843 * By setting the value ``SOME_CONSTANT`` in the given ``dict_``, 

2844 we indicate that this value is to be persisted to the database. 

2845 This supersedes the use of ``SOME_CONSTANT`` in the default generator 

2846 for the :class:`_schema.Column`. The ``active_column_defaults.py`` 

2847 example given at :ref:`examples_instrumentation` illustrates using 

2848 the same approach for a changing default, e.g. a timestamp 

2849 generator. In this particular example, it is not strictly 

2850 necessary to do this since ``SOME_CONSTANT`` would be part of the 

2851 INSERT statement in either case. 

2852 

2853 * By establishing the ``retval=True`` flag, the value we return 

2854 from the function will be returned by the attribute getter. 

2855 Without this flag, the event is assumed to be a passive observer 

2856 and the return value of our function is ignored. 

2857 

2858 * The ``propagate=True`` flag is significant if the mapped class 

2859 includes inheriting subclasses, which would also make use of this 

2860 event listener. Without this flag, an inheriting subclass will 

2861 not use our event handler. 

2862 

2863 In the above example, the attribute set event 

2864 :meth:`.AttributeEvents.set` as well as the related validation feature 

2865 provided by :obj:`_orm.validates` is **not** invoked when we apply our 

2866 value to the given ``dict_``. To have these events to invoke in 

2867 response to our newly generated value, apply the value to the given 

2868 object as a normal attribute set operation:: 

2869 

2870 SOME_CONSTANT = 3.1415926 

2871 

2872 

2873 @event.listens_for( 

2874 MyClass.some_attribute, "init_scalar", retval=True, propagate=True 

2875 ) 

2876 def _init_some_attribute(target, dict_, value): 

2877 # will also fire off attribute set events 

2878 target.some_attribute = SOME_CONSTANT 

2879 return SOME_CONSTANT 

2880 

2881 When multiple listeners are set up, the generation of the value 

2882 is "chained" from one listener to the next by passing the value 

2883 returned by the previous listener that specifies ``retval=True`` 

2884 as the ``value`` argument of the next listener. 

2885 

2886 :param target: the object instance receiving the event. 

2887 If the listener is registered with ``raw=True``, this will 

2888 be the :class:`.InstanceState` object. 

2889 :param value: the value that is to be returned before this event 

2890 listener were invoked. This value begins as the value ``None``, 

2891 however will be the return value of the previous event handler 

2892 function if multiple listeners are present. 

2893 :param dict\_: the attribute dictionary of this mapped object. 

2894 This is normally the ``__dict__`` of the object, but in all cases 

2895 represents the destination that the attribute system uses to get 

2896 at the actual value of this attribute. Placing the value in this 

2897 dictionary has the effect that the value will be used in the 

2898 INSERT statement generated by the unit of work. 

2899 

2900 

2901 .. seealso:: 

2902 

2903 :meth:`.AttributeEvents.init_collection` - collection version 

2904 of this event 

2905 

2906 :class:`.AttributeEvents` - background on listener options such 

2907 as propagation to subclasses. 

2908 

2909 :ref:`examples_instrumentation` - see the 

2910 ``active_column_defaults.py`` example. 

2911 

2912 """ # noqa: E501 

2913 

2914 def init_collection( 

2915 self, 

2916 target: _O, 

2917 collection: Type[Collection[Any]], 

2918 collection_adapter: CollectionAdapter, 

2919 ) -> None: 

2920 """Receive a 'collection init' event. 

2921 

2922 This event is triggered for a collection-based attribute, when 

2923 the initial "empty collection" is first generated for a blank 

2924 attribute, as well as for when the collection is replaced with 

2925 a new one, such as via a set event. 

2926 

2927 E.g., given that ``User.addresses`` is a relationship-based 

2928 collection, the event is triggered here:: 

2929 

2930 u1 = User() 

2931 u1.addresses.append(a1) # <- new collection 

2932 

2933 and also during replace operations:: 

2934 

2935 u1.addresses = [a2, a3] # <- new collection 

2936 

2937 :param target: the object instance receiving the event. 

2938 If the listener is registered with ``raw=True``, this will 

2939 be the :class:`.InstanceState` object. 

2940 :param collection: the new collection. This will always be generated 

2941 from what was specified as 

2942 :paramref:`_orm.relationship.collection_class`, and will always 

2943 be empty. 

2944 :param collection_adapter: the :class:`.CollectionAdapter` that will 

2945 mediate internal access to the collection. 

2946 

2947 .. seealso:: 

2948 

2949 :class:`.AttributeEvents` - background on listener options such 

2950 as propagation to subclasses. 

2951 

2952 :meth:`.AttributeEvents.init_scalar` - "scalar" version of this 

2953 event. 

2954 

2955 """ 

2956 

2957 def dispose_collection( 

2958 self, 

2959 target: _O, 

2960 collection: Collection[Any], 

2961 collection_adapter: CollectionAdapter, 

2962 ) -> None: 

2963 """Receive a 'collection dispose' event. 

2964 

2965 This event is triggered for a collection-based attribute when 

2966 a collection is replaced, that is:: 

2967 

2968 u1.addresses.append(a1) 

2969 

2970 u1.addresses = [a2, a3] # <- old collection is disposed 

2971 

2972 The old collection received will contain its previous contents. 

2973 

2974 .. seealso:: 

2975 

2976 :class:`.AttributeEvents` - background on listener options such 

2977 as propagation to subclasses. 

2978 

2979 """ 

2980 

2981 def modified(self, target: _O, initiator: Event) -> None: 

2982 """Receive a 'modified' event. 

2983 

2984 This event is triggered when the :func:`.attributes.flag_modified` 

2985 function is used to trigger a modify event on an attribute without 

2986 any specific value being set. 

2987 

2988 :param target: the object instance receiving the event. 

2989 If the listener is registered with ``raw=True``, this will 

2990 be the :class:`.InstanceState` object. 

2991 

2992 :param initiator: An instance of :class:`.attributes.Event` 

2993 representing the initiation of the event. 

2994 

2995 .. seealso:: 

2996 

2997 :class:`.AttributeEvents` - background on listener options such 

2998 as propagation to subclasses. 

2999 

3000 """ 

3001 

3002 

3003class QueryEvents(event.Events[Query[Any]]): 

3004 """Represent events within the construction of a :class:`_query.Query` 

3005 object. 

3006 

3007 .. legacy:: The :class:`_orm.QueryEvents` event methods are legacy 

3008 as of SQLAlchemy 2.0, and only apply to direct use of the 

3009 :class:`_orm.Query` object. They are not used for :term:`2.0 style` 

3010 statements. For events to intercept and modify 2.0 style ORM use, 

3011 use the :meth:`_orm.SessionEvents.do_orm_execute` hook. 

3012 

3013 

3014 The :class:`_orm.QueryEvents` hooks are now superseded by the 

3015 :meth:`_orm.SessionEvents.do_orm_execute` event hook. 

3016 

3017 """ 

3018 

3019 _target_class_doc = "SomeQuery" 

3020 _dispatch_target = Query 

3021 

3022 def before_compile(self, query: Query[Any]) -> None: 

3023 """Receive the :class:`_query.Query` 

3024 object before it is composed into a 

3025 core :class:`_expression.Select` object. 

3026 

3027 .. deprecated:: 1.4 The :meth:`_orm.QueryEvents.before_compile` event 

3028 is superseded by the much more capable 

3029 :meth:`_orm.SessionEvents.do_orm_execute` hook. In version 1.4, 

3030 the :meth:`_orm.QueryEvents.before_compile` event is **no longer 

3031 used** for ORM-level attribute loads, such as loads of deferred 

3032 or expired attributes as well as relationship loaders. See the 

3033 new examples in :ref:`examples_session_orm_events` which 

3034 illustrate new ways of intercepting and modifying ORM queries 

3035 for the most common purpose of adding arbitrary filter criteria. 

3036 

3037 

3038 This event is intended to allow changes to the query given:: 

3039 

3040 @event.listens_for(Query, "before_compile", retval=True) 

3041 def no_deleted(query): 

3042 for desc in query.column_descriptions: 

3043 if desc["type"] is User: 

3044 entity = desc["entity"] 

3045 query = query.filter(entity.deleted == False) 

3046 return query 

3047 

3048 The event should normally be listened with the ``retval=True`` 

3049 parameter set, so that the modified query may be returned. 

3050 

3051 The :meth:`.QueryEvents.before_compile` event by default 

3052 will disallow "baked" queries from caching a query, if the event 

3053 hook returns a new :class:`_query.Query` object. 

3054 This affects both direct 

3055 use of the baked query extension as well as its operation within 

3056 lazy loaders and eager loaders for relationships. In order to 

3057 re-establish the query being cached, apply the event adding the 

3058 ``bake_ok`` flag:: 

3059 

3060 @event.listens_for(Query, "before_compile", retval=True, bake_ok=True) 

3061 def my_event(query): 

3062 for desc in query.column_descriptions: 

3063 if desc["type"] is User: 

3064 entity = desc["entity"] 

3065 query = query.filter(entity.deleted == False) 

3066 return query 

3067 

3068 When ``bake_ok`` is set to True, the event hook will only be invoked 

3069 once, and not called for subsequent invocations of a particular query 

3070 that is being cached. 

3071 

3072 .. seealso:: 

3073 

3074 :meth:`.QueryEvents.before_compile_update` 

3075 

3076 :meth:`.QueryEvents.before_compile_delete` 

3077 

3078 :ref:`baked_with_before_compile` 

3079 

3080 """ # noqa: E501 

3081 

3082 def before_compile_update( 

3083 self, query: Query[Any], update_context: BulkUpdate 

3084 ) -> None: 

3085 """Allow modifications to the :class:`_query.Query` object within 

3086 :meth:`_query.Query.update`. 

3087 

3088 .. deprecated:: 1.4 The :meth:`_orm.QueryEvents.before_compile_update` 

3089 event is superseded by the much more capable 

3090 :meth:`_orm.SessionEvents.do_orm_execute` hook. 

3091 

3092 Like the :meth:`.QueryEvents.before_compile` event, if the event 

3093 is to be used to alter the :class:`_query.Query` object, it should 

3094 be configured with ``retval=True``, and the modified 

3095 :class:`_query.Query` object returned, as in :: 

3096 

3097 @event.listens_for(Query, "before_compile_update", retval=True) 

3098 def no_deleted(query, update_context): 

3099 for desc in query.column_descriptions: 

3100 if desc["type"] is User: 

3101 entity = desc["entity"] 

3102 query = query.filter(entity.deleted == False) 

3103 

3104 update_context.values["timestamp"] = datetime.datetime.now( 

3105 datetime.UTC 

3106 ) 

3107 return query 

3108 

3109 The ``.values`` dictionary of the "update context" object can also 

3110 be modified in place as illustrated above. 

3111 

3112 :param query: a :class:`_query.Query` instance; this is also 

3113 the ``.query`` attribute of the given "update context" 

3114 object. 

3115 

3116 :param update_context: an "update context" object which is 

3117 the same kind of object as described in 

3118 :paramref:`.QueryEvents.after_bulk_update.update_context`. 

3119 The object has a ``.values`` attribute in an UPDATE context which is 

3120 the dictionary of parameters passed to :meth:`_query.Query.update`. 

3121 This 

3122 dictionary can be modified to alter the VALUES clause of the 

3123 resulting UPDATE statement. 

3124 

3125 .. seealso:: 

3126 

3127 :meth:`.QueryEvents.before_compile` 

3128 

3129 :meth:`.QueryEvents.before_compile_delete` 

3130 

3131 

3132 """ # noqa: E501 

3133 

3134 def before_compile_delete( 

3135 self, query: Query[Any], delete_context: BulkDelete 

3136 ) -> None: 

3137 """Allow modifications to the :class:`_query.Query` object within 

3138 :meth:`_query.Query.delete`. 

3139 

3140 .. deprecated:: 1.4 The :meth:`_orm.QueryEvents.before_compile_delete` 

3141 event is superseded by the much more capable 

3142 :meth:`_orm.SessionEvents.do_orm_execute` hook. 

3143 

3144 Like the :meth:`.QueryEvents.before_compile` event, this event 

3145 should be configured with ``retval=True``, and the modified 

3146 :class:`_query.Query` object returned, as in :: 

3147 

3148 @event.listens_for(Query, "before_compile_delete", retval=True) 

3149 def no_deleted(query, delete_context): 

3150 for desc in query.column_descriptions: 

3151 if desc["type"] is User: 

3152 entity = desc["entity"] 

3153 query = query.filter(entity.deleted == False) 

3154 return query 

3155 

3156 :param query: a :class:`_query.Query` instance; this is also 

3157 the ``.query`` attribute of the given "delete context" 

3158 object. 

3159 

3160 :param delete_context: a "delete context" object which is 

3161 the same kind of object as described in 

3162 :paramref:`.QueryEvents.after_bulk_delete.delete_context`. 

3163 

3164 .. seealso:: 

3165 

3166 :meth:`.QueryEvents.before_compile` 

3167 

3168 :meth:`.QueryEvents.before_compile_update` 

3169 

3170 

3171 """ 

3172 

3173 @classmethod 

3174 def _listen( 

3175 cls, 

3176 event_key: _EventKey[_ET], 

3177 retval: bool = False, 

3178 bake_ok: bool = False, 

3179 **kw: Any, 

3180 ) -> None: 

3181 fn = event_key._listen_fn 

3182 

3183 if not retval: 

3184 

3185 def wrap(*arg: Any, **kw: Any) -> Any: 

3186 if not retval: 

3187 query = arg[0] 

3188 fn(*arg, **kw) 

3189 return query 

3190 else: 

3191 return fn(*arg, **kw) 

3192 

3193 event_key = event_key.with_wrapper(wrap) 

3194 else: 

3195 # don't assume we can apply an attribute to the callable 

3196 def wrap(*arg: Any, **kw: Any) -> Any: 

3197 return fn(*arg, **kw) 

3198 

3199 event_key = event_key.with_wrapper(wrap) 

3200 

3201 wrap._bake_ok = bake_ok # type: ignore [attr-defined] 

3202 

3203 event_key.base_listen(**kw) 

3204 

3205 

3206class RegistryEvents(event.Events["registry"]): 

3207 """Define events specific to :class:`_orm.registry` lifecycle. 

3208 

3209 The :class:`_orm.RegistryEvents` class defines events that are specific 

3210 to the lifecycle and operation of the :class:`_orm.registry` object. 

3211 

3212 e.g.:: 

3213 

3214 from typing import Any 

3215 

3216 from sqlalchemy import event 

3217 from sqlalchemy.orm import registry 

3218 from sqlalchemy.orm import TypeResolve 

3219 from sqlalchemy.types import TypeEngine 

3220 

3221 reg = registry() 

3222 

3223 

3224 @event.listens_for(reg, "resolve_type_annotation") 

3225 def resolve_custom_type( 

3226 resolve_type: TypeResolve, 

3227 ) -> TypeEngine[Any] | None: 

3228 if python_type is MyCustomType: 

3229 return MyCustomSQLType() 

3230 return None 

3231 

3232 The events defined by :class:`_orm.RegistryEvents` include 

3233 :meth:`_orm.RegistryEvents.resolve_type_annotation`, 

3234 :meth:`_orm.RegistryEvents.before_configured`, and 

3235 :meth:`_orm.RegistryEvents.after_configured`.`. These events may be 

3236 applied to a :class:`_orm.registry` object as shown in the preceding 

3237 example, as well as to a declarative base class directly, which will 

3238 automtically locate the registry for the event to be applied:: 

3239 

3240 from typing import Any 

3241 

3242 from sqlalchemy import event 

3243 from sqlalchemy.orm import DeclarativeBase 

3244 from sqlalchemy.orm import registry as RegistryType 

3245 from sqlalchemy.orm import TypeResolve 

3246 from sqlalchemy.types import TypeEngine 

3247 

3248 

3249 class Base(DeclarativeBase): 

3250 pass 

3251 

3252 

3253 @event.listens_for(Base, "resolve_type_annotation") 

3254 def resolve_custom_type( 

3255 resolve_type: TypeResolve, 

3256 ) -> TypeEngine[Any] | None: 

3257 if resolve_type.resolved_type is MyCustomType: 

3258 return MyCustomSQLType() 

3259 else: 

3260 return None 

3261 

3262 

3263 @event.listens_for(Base, "after_configured") 

3264 def after_base_configured(registry: RegistryType) -> None: 

3265 print(f"Registry {registry} fully configured") 

3266 

3267 .. versionadded:: 2.1 

3268 

3269 

3270 """ 

3271 

3272 _target_class_doc = "SomeRegistry" 

3273 _dispatch_target = decl_api.registry 

3274 

3275 @classmethod 

3276 def _accept_with( 

3277 cls, 

3278 target: Any, 

3279 identifier: str, 

3280 ) -> Any: 

3281 if isinstance(target, decl_api.registry): 

3282 return target 

3283 elif ( 

3284 isinstance(target, type) 

3285 and "_sa_registry" in target.__dict__ 

3286 and isinstance(target.__dict__["_sa_registry"], decl_api.registry) 

3287 ): 

3288 return target._sa_registry # type: ignore[attr-defined] 

3289 else: 

3290 return None 

3291 

3292 @classmethod 

3293 def _listen( 

3294 cls, 

3295 event_key: _EventKey["registry"], 

3296 **kw: Any, 

3297 ) -> None: 

3298 identifier = event_key.identifier 

3299 

3300 # Only resolve_type_annotation needs retval=True 

3301 if identifier == "resolve_type_annotation": 

3302 kw["retval"] = True 

3303 

3304 event_key.base_listen(**kw) 

3305 

3306 def resolve_type_annotation( 

3307 self, resolve_type: decl_api.TypeResolve 

3308 ) -> Optional[Any]: 

3309 """Intercept and customize type annotation resolution. 

3310 

3311 This event is fired when the :class:`_orm.registry` attempts to 

3312 resolve a Python type annotation to a SQLAlchemy type. This is 

3313 particularly useful for handling advanced typing scenarios such as 

3314 nested :pep:`695` type aliases. 

3315 

3316 The :meth:`.RegistryEvents.resolve_type_annotation` event automatically 

3317 sets up ``retval=True`` when the event is set up, so that implementing 

3318 functions may return a resolved type, or ``None`` to indicate no type 

3319 was resolved, and the default resolution for the type should proceed. 

3320 

3321 :param resolve_type: A :class:`_orm.TypeResolve` object which contains 

3322 all the relevant information about the type, including a link to the 

3323 registry and its resolver function. 

3324 

3325 :return: A SQLAlchemy type to use for the given Python type. If 

3326 ``None`` is returned, the default resolution behavior will proceed 

3327 from there. 

3328 

3329 .. versionadded:: 2.1 

3330 

3331 .. seealso:: 

3332 

3333 :ref:`orm_declarative_resolve_type_event` 

3334 

3335 """ 

3336 

3337 def before_configured(self, registry: "registry") -> None: 

3338 """Called before a series of mappers in this registry are configured. 

3339 

3340 This event is invoked each time the :func:`_orm.configure_mappers` 

3341 function is invoked and this registry has mappers that are part of 

3342 the configuration process. 

3343 

3344 Compared to the :meth:`.MapperEvents.before_configured` event hook, 

3345 this event is local to the mappers within a specific 

3346 :class:`_orm.registry` and not for all :class:`.Mapper` objects 

3347 globally. 

3348 

3349 :param registry: The :class:`_orm.registry` instance. 

3350 

3351 .. versionadded:: 2.1 

3352 

3353 .. seealso:: 

3354 

3355 :meth:`.RegistryEvents.after_configured` 

3356 

3357 :meth:`.MapperEvents.before_configured` 

3358 

3359 :meth:`.MapperEvents.after_configured` 

3360 

3361 """ 

3362 

3363 def after_configured(self, registry: "registry") -> None: 

3364 """Called after a series of mappers in this registry are configured. 

3365 

3366 This event is invoked each time the :func:`_orm.configure_mappers` 

3367 function completes and this registry had mappers that were part of 

3368 the configuration process. 

3369 

3370 Compared to the :meth:`.MapperEvents.after_configured` event hook, this 

3371 event is local to the mappers within a specific :class:`_orm.registry` 

3372 and not for all :class:`.Mapper` objects globally. 

3373 

3374 :param registry: The :class:`_orm.registry` instance. 

3375 

3376 .. versionadded:: 2.1 

3377 

3378 .. seealso:: 

3379 

3380 :meth:`.RegistryEvents.before_configured` 

3381 

3382 :meth:`.MapperEvents.before_configured` 

3383 

3384 :meth:`.MapperEvents.after_configured` 

3385 

3386 """