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-2026 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.""" 

9 

10from __future__ import annotations 

11 

12from typing import Any 

13from typing import Callable 

14from typing import Collection 

15from typing import Dict 

16from typing import Generic 

17from typing import Iterable 

18from typing import Optional 

19from typing import Sequence 

20from typing import Set 

21from typing import Type 

22from typing import TYPE_CHECKING 

23from typing import TypeVar 

24from typing import Union 

25import weakref 

26 

27from . import decl_api 

28from . import instrumentation 

29from . import interfaces 

30from . import mapperlib 

31from .attributes import QueryableAttribute 

32from .base import _mapper_or_none 

33from .base import NO_KEY 

34from .instrumentation import ClassManager 

35from .instrumentation import InstrumentationFactory 

36from .query import BulkDelete 

37from .query import BulkUpdate 

38from .query import Query 

39from .scoping import scoped_session 

40from .session import Session 

41from .session import sessionmaker 

42from .. import event 

43from .. import exc 

44from .. import util 

45from ..event import EventTarget 

46from ..event.registry import _ET 

47from ..util.compat import inspect_getfullargspec 

48 

49if TYPE_CHECKING: 

50 from weakref import ReferenceType 

51 

52 from ._typing import _InstanceDict 

53 from ._typing import _InternalEntityType 

54 from ._typing import _O 

55 from ._typing import _T 

56 from .attributes import Event 

57 from .base import EventConstants 

58 from .session import ORMExecuteState 

59 from .session import SessionTransaction 

60 from .unitofwork import UOWTransaction 

61 from ..engine import Connection 

62 from ..event.base import _Dispatch 

63 from ..event.base import _HasEventsDispatch 

64 from ..event.registry import _EventKey 

65 from ..orm.collections import CollectionAdapter 

66 from ..orm.context import QueryContext 

67 from ..orm.decl_api import DeclarativeAttributeIntercept 

68 from ..orm.decl_api import DeclarativeMeta 

69 from ..orm.decl_api import registry 

70 from ..orm.mapper import Mapper 

71 from ..orm.state import InstanceState 

72 

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

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

75 

76 

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

78 """Events related to class instrumentation events. 

79 

80 The listeners here support being established against 

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

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

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

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

85 of that class as well. 

86 

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

88 which when used has the effect of events being emitted 

89 for all classes. 

90 

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

92 unlike the other class level events where it defaults 

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

94 be the subject of these events, when a listener 

95 is established on a superclass. 

96 

97 """ 

98 

99 _target_class_doc = "SomeBaseClass" 

100 _dispatch_target = InstrumentationFactory 

101 

102 @classmethod 

103 def _accept_with( 

104 cls, 

105 target: Union[ 

106 InstrumentationFactory, 

107 Type[InstrumentationFactory], 

108 ], 

109 identifier: str, 

110 ) -> Optional[ 

111 Union[ 

112 InstrumentationFactory, 

113 Type[InstrumentationFactory], 

114 ] 

115 ]: 

116 if isinstance(target, type): 

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

118 else: 

119 return None 

120 

121 @classmethod 

122 def _listen( 

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

124 ) -> None: 

125 target, identifier, fn = ( 

126 event_key.dispatch_target, 

127 event_key.identifier, 

128 event_key._listen_fn, 

129 ) 

130 

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

132 listen_cls = target() 

133 

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

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

136 # between mapper/registry/instrumentation_manager, however this 

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

138 if listen_cls is None: 

139 return None 

140 

141 if propagate and issubclass(target_cls, listen_cls): 

142 return fn(target_cls, *arg) 

143 elif not propagate and target_cls is listen_cls: 

144 return fn(target_cls, *arg) 

145 else: 

146 return None 

147 

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

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

150 None, 

151 identifier, 

152 listen, 

153 instrumentation._instrumentation_factory, 

154 ) 

155 getattr( 

156 instrumentation._instrumentation_factory.dispatch, identifier 

157 ).remove(key) 

158 

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

160 

161 event_key.with_dispatch_target( 

162 instrumentation._instrumentation_factory 

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

164 

165 @classmethod 

166 def _clear(cls) -> None: 

167 super()._clear() 

168 instrumentation._instrumentation_factory.dispatch._clear() 

169 

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

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

172 

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

174 :func:`.manager_of_class`. 

175 

176 """ 

177 

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

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

180 

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

182 :func:`.manager_of_class`. 

183 

184 """ 

185 

186 def attribute_instrument( 

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

188 ) -> None: 

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

190 

191 

192class _InstrumentationEventsHold: 

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

194 _listen() on the InstrumentationEvents class. 

195 

196 """ 

197 

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

199 self.class_ = class_ 

200 

201 dispatch = event.dispatcher(InstrumentationEvents) 

202 

203 

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

205 """Define events specific to object lifecycle. 

206 

207 e.g.:: 

208 

209 from sqlalchemy import event 

210 

211 

212 def my_load_listener(target, context): 

213 print("on load!") 

214 

215 

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

217 

218 Available targets include: 

219 

220 * mapped classes 

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

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

223 * :class:`_orm.Mapper` objects 

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

225 mappers. 

226 

227 Instance events are closely related to mapper events, but 

228 are more specific to the instance and its instrumentation, 

229 rather than its system of persistence. 

230 

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

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

233 

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

235 be applied to all inheriting classes as well as the 

236 class which is the target of this listener. 

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

238 to applicable event listener functions will be the 

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

240 object, rather than the mapped instance itself. 

241 :param restore_load_context=False: Applies to the 

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

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

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

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

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

247 events if this flag is not set. 

248 

249 """ 

250 

251 _target_class_doc = "SomeClass" 

252 

253 _dispatch_target = ClassManager 

254 

255 @classmethod 

256 def _new_classmanager_instance( 

257 cls, 

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

259 classmanager: ClassManager[_O], 

260 ) -> None: 

261 _InstanceEventsHold.populate(class_, classmanager) 

262 

263 @classmethod 

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

265 def _accept_with( 

266 cls, 

267 target: Union[ 

268 ClassManager[Any], 

269 Type[ClassManager[Any]], 

270 ], 

271 identifier: str, 

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

273 orm = util.preloaded.orm 

274 

275 if isinstance(target, ClassManager): 

276 return target 

277 elif isinstance(target, mapperlib.Mapper): 

278 return target.class_manager 

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

280 util.warn_deprecated( 

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

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

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

284 "2.0", 

285 ) 

286 return ClassManager 

287 elif isinstance(target, type): 

288 if issubclass(target, mapperlib.Mapper): 

289 return ClassManager 

290 else: 

291 manager = instrumentation.opt_manager_of_class(target) 

292 if manager: 

293 return manager 

294 else: 

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

296 return None 

297 

298 @classmethod 

299 def _listen( 

300 cls, 

301 event_key: _EventKey[ClassManager[Any]], 

302 raw: bool = False, 

303 propagate: bool = False, 

304 restore_load_context: bool = False, 

305 **kw: Any, 

306 ) -> None: 

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

308 

309 if not raw or restore_load_context: 

310 

311 def wrap( 

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

313 ) -> Optional[Any]: 

314 if not raw: 

315 target: Any = state.obj() 

316 else: 

317 target = state 

318 if restore_load_context: 

319 runid = state.runid 

320 try: 

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

322 finally: 

323 if restore_load_context: 

324 state.runid = runid 

325 

326 event_key = event_key.with_wrapper(wrap) 

327 

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

329 

330 if propagate: 

331 for mgr in target.subclass_managers(True): 

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

333 

334 @classmethod 

335 def _clear(cls) -> None: 

336 super()._clear() 

337 _InstanceEventsHold._clear() 

338 

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

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

341 

342 This method is only called during a userland construction of 

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

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

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

346 event in order to intercept a database load. 

347 

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

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

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

351 ``__init__``. 

352 

353 :param target: the mapped instance. If 

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

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

356 object associated with the instance. 

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

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

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

360 This structure *can* be altered in place. 

361 

362 .. seealso:: 

363 

364 :meth:`.InstanceEvents.init_failure` 

365 

366 :meth:`.InstanceEvents.load` 

367 

368 """ 

369 

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

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

372 and raised an exception. 

373 

374 This method is only called during a userland construction of 

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

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

377 from the database. 

378 

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

380 method is caught. After the event 

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

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

383 actual exception and stack trace raised should be present in 

384 ``sys.exc_info()``. 

385 

386 :param target: the mapped instance. If 

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

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

389 object associated with the instance. 

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

391 method. 

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

393 method. 

394 

395 .. seealso:: 

396 

397 :meth:`.InstanceEvents.init` 

398 

399 :meth:`.InstanceEvents.load` 

400 

401 """ 

402 

403 def _sa_event_merge_wo_load( 

404 self, target: _O, context: QueryContext 

405 ) -> None: 

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

407 call, when load=False was passed. 

408 

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

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

411 overwrite operation does not use attribute events, instead just 

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

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

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

415 

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

417 

418 .. versionadded:: 1.4.41 

419 

420 """ 

421 

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

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

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

425 occurred. 

426 

427 This typically occurs when the instance is created based on 

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

429 instance's lifetime. 

430 

431 .. warning:: 

432 

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

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

435 eager loading with collection-oriented attributes, the additional 

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

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

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

439 if an operation occurs within this event handler that emits 

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

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

442 existing eager loaders still in progress. 

443 

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

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

446 

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

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

449 

450 * accessing attributes on a joined-inheritance subclass that 

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

452 

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

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

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

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

457 event is called:: 

458 

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

460 def on_load(instance, context): 

461 instance.some_unloaded_attribute 

462 

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

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

465 

466 :param target: the mapped instance. If 

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

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

469 object associated with the instance. 

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

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

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

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

474 

475 .. seealso:: 

476 

477 :ref:`mapped_class_load_events` 

478 

479 :meth:`.InstanceEvents.init` 

480 

481 :meth:`.InstanceEvents.refresh` 

482 

483 :meth:`.SessionEvents.loaded_as_persistent` 

484 

485 """ # noqa: E501 

486 

487 def refresh( 

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

489 ) -> None: 

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

491 been refreshed from a query. 

492 

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

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

495 

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

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

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

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

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

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

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

503 order to resolve this scenario. 

504 

505 :param target: the mapped instance. If 

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

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

508 object associated with the instance. 

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

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

511 :param attrs: sequence of attribute names which 

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

513 attributes were populated. 

514 

515 .. seealso:: 

516 

517 :ref:`mapped_class_load_events` 

518 

519 :meth:`.InstanceEvents.load` 

520 

521 """ 

522 

523 def refresh_flush( 

524 self, 

525 target: _O, 

526 flush_context: UOWTransaction, 

527 attrs: Optional[Iterable[str]], 

528 ) -> None: 

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

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

531 during persistence of the object's state. 

532 

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

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

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

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

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

538 

539 .. note:: 

540 

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

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

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

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

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

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

547 intercept the newly INSERTed state of an object, the 

548 :meth:`.SessionEvents.pending_to_persistent` and 

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

550 

551 :param target: the mapped instance. If 

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

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

554 object associated with the instance. 

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

556 which handles the details of the flush. 

557 :param attrs: sequence of attribute names which 

558 were populated. 

559 

560 .. seealso:: 

561 

562 :ref:`mapped_class_load_events` 

563 

564 :ref:`orm_server_defaults` 

565 

566 :ref:`metadata_defaults_toplevel` 

567 

568 """ 

569 

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

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

572 have been expired. 

573 

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

575 state was expired. 

576 

577 :param target: the mapped instance. If 

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

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

580 object associated with the instance. 

581 :param attrs: sequence of attribute 

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

583 expired. 

584 

585 """ 

586 

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

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

589 being pickled. 

590 

591 :param target: the mapped instance. If 

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

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

594 object associated with the instance. 

595 :param state_dict: the dictionary returned by 

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

597 to be pickled. 

598 

599 """ 

600 

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

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

603 been unpickled. 

604 

605 :param target: the mapped instance. If 

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

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

608 object associated with the instance. 

609 :param state_dict: the dictionary sent to 

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

611 dictionary which was pickled. 

612 

613 """ 

614 

615 

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

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

618 

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

620 those objects are created for that class. 

621 

622 """ 

623 

624 all_holds: weakref.WeakKeyDictionary[Any, Any] 

625 

626 def __init__( 

627 self, 

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

629 ) -> None: 

630 self.class_ = class_ 

631 

632 @classmethod 

633 def _clear(cls) -> None: 

634 cls.all_holds.clear() 

635 

636 class HoldEvents(Generic[_ET2]): 

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

638 

639 @classmethod 

640 def _listen( 

641 cls, 

642 event_key: _EventKey[_ET2], 

643 raw: bool = False, 

644 propagate: bool = False, 

645 retval: bool = False, 

646 **kw: Any, 

647 ) -> None: 

648 target = event_key.dispatch_target 

649 

650 if target.class_ in target.all_holds: 

651 collection = target.all_holds[target.class_] 

652 else: 

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

654 

655 event.registry._stored_in_collection(event_key, target) 

656 collection[event_key._key] = ( 

657 event_key, 

658 raw, 

659 propagate, 

660 retval, 

661 kw, 

662 ) 

663 

664 if propagate: 

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

666 while stack: 

667 subclass = stack.pop(0) 

668 stack.extend(subclass.__subclasses__()) 

669 subject = target.resolve(subclass) 

670 if subject is not None: 

671 # we are already going through __subclasses__() 

672 # so leave generic propagate flag False 

673 event_key.with_dispatch_target(subject).listen( 

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

675 ) 

676 

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

678 target = event_key.dispatch_target 

679 

680 if isinstance(target, _EventsHold): 

681 collection = target.all_holds[target.class_] 

682 del collection[event_key._key] 

683 

684 @classmethod 

685 def populate( 

686 cls, 

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

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

689 ) -> None: 

690 for subclass in class_.__mro__: 

691 if subclass in cls.all_holds: 

692 collection = cls.all_holds[subclass] 

693 for ( 

694 event_key, 

695 raw, 

696 propagate, 

697 retval, 

698 kw, 

699 ) in collection.values(): 

700 if propagate or subclass is class_: 

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

702 # classes in a hierarchy are triggered with 

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

704 # assignment, instead of using the generic propagate 

705 # flag. 

706 event_key.with_dispatch_target(subject).listen( 

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

708 ) 

709 

710 

711class _InstanceEventsHold(_EventsHold[_ET]): 

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

713 weakref.WeakKeyDictionary() 

714 ) 

715 

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

717 return instrumentation.opt_manager_of_class(class_) 

718 

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

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

721 pass 

722 

723 dispatch = event.dispatcher(HoldInstanceEvents) 

724 

725 

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

727 """Define events specific to mappings. 

728 

729 e.g.:: 

730 

731 from sqlalchemy import event 

732 

733 

734 def my_before_insert_listener(mapper, connection, target): 

735 # execute a stored procedure upon INSERT, 

736 # apply the value to the row to be inserted 

737 target.calculated_value = connection.execute( 

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

739 ).scalar() 

740 

741 

742 # associate the listener function with SomeClass, 

743 # to execute during the "before_insert" hook 

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

745 

746 Available targets include: 

747 

748 * mapped classes 

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

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

751 * :class:`_orm.Mapper` objects 

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

753 mappers. 

754 

755 Mapper events provide hooks into critical sections of the 

756 mapper, including those related to object instrumentation, 

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

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

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

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

761 methods operate with several significant restrictions. The 

762 user is encouraged to evaluate the 

763 :meth:`.SessionEvents.before_flush` and 

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

765 flexible and user-friendly hooks in which to apply 

766 additional database state during a flush. 

767 

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

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

770 

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

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

773 inheriting classes, as well as any 

774 mapper which is the target of this listener. 

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

776 to applicable event listener functions will be the 

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

778 object, rather than the mapped instance itself. 

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

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

781 control subsequent event propagation, or to otherwise alter 

782 the operation in progress by the mapper. Possible return 

783 values are: 

784 

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

786 processing normally. 

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

788 event handlers in the chain. 

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

790 

791 """ 

792 

793 _target_class_doc = "SomeClass" 

794 _dispatch_target = mapperlib.Mapper 

795 

796 @classmethod 

797 def _new_mapper_instance( 

798 cls, 

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

800 mapper: Mapper[_O], 

801 ) -> None: 

802 _MapperEventsHold.populate(class_, mapper) 

803 

804 @classmethod 

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

806 def _accept_with( 

807 cls, 

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

809 identifier: str, 

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

811 orm = util.preloaded.orm 

812 

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

814 util.warn_deprecated( 

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

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

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

818 "2.0", 

819 ) 

820 target = mapperlib.Mapper 

821 

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

823 if target is mapperlib.Mapper: 

824 return target 

825 else: 

826 return None 

827 

828 elif isinstance(target, type): 

829 if issubclass(target, mapperlib.Mapper): 

830 return target 

831 else: 

832 mapper = _mapper_or_none(target) 

833 if mapper is not None: 

834 return mapper 

835 else: 

836 return _MapperEventsHold(target) 

837 else: 

838 return target 

839 

840 @classmethod 

841 def _listen( 

842 cls, 

843 event_key: _EventKey[_ET], 

844 raw: bool = False, 

845 retval: bool = False, 

846 propagate: bool = False, 

847 **kw: Any, 

848 ) -> None: 

849 target, identifier, fn = ( 

850 event_key.dispatch_target, 

851 event_key.identifier, 

852 event_key._listen_fn, 

853 ) 

854 

855 if not raw or not retval: 

856 if not raw: 

857 meth = getattr(cls, identifier) 

858 try: 

859 target_index = ( 

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

861 ) 

862 except ValueError: 

863 target_index = None 

864 

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

866 if not raw and target_index is not None: 

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

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

869 if not retval: 

870 fn(*arg, **kw) 

871 return interfaces.EXT_CONTINUE 

872 else: 

873 return fn(*arg, **kw) 

874 

875 event_key = event_key.with_wrapper(wrap) 

876 

877 if propagate: 

878 for mapper in target.self_and_descendants: 

879 event_key.with_dispatch_target(mapper).base_listen( 

880 propagate=True, **kw 

881 ) 

882 else: 

883 event_key.base_listen(**kw) 

884 

885 @classmethod 

886 def _clear(cls) -> None: 

887 super()._clear() 

888 _MapperEventsHold._clear() 

889 

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

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

892 before instrumentation is applied to the mapped class. 

893 

894 This event is the earliest phase of mapper construction. 

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

896 receive an event within initial mapper construction where basic 

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

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

899 be a better choice. 

900 

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

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

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

904 

905 Base = declarative_base() 

906 

907 

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

909 def on_new_class(mapper, cls_): 

910 "..." 

911 

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

913 of this event. 

914 :param class\_: the mapped class. 

915 

916 .. seealso:: 

917 

918 :meth:`_orm.MapperEvents.after_mapper_constructed` 

919 

920 """ 

921 

922 def after_mapper_constructed( 

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

924 ) -> None: 

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

926 fully constructed. 

927 

928 This event is called after the initial constructor for 

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

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

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

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

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

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

935 

936 This event differs from the 

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

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

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

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

941 wish to create additional mapped classes in response to the 

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

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

944 

945 .. versionadded:: 2.0.2 

946 

947 .. seealso:: 

948 

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

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

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

952 objects. 

953 

954 """ 

955 

956 @event._omit_standard_example 

957 def before_mapper_configured( 

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

959 ) -> None: 

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

961 

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

963 for each mapper that is encountered when the 

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

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

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

967 right before the configuration occurs, rather than afterwards. 

968 

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

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

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

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

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

974 should be left unconfigured:: 

975 

976 from sqlalchemy import event 

977 from sqlalchemy.orm import EXT_SKIP 

978 from sqlalchemy.orm import DeclarativeBase 

979 

980 

981 class DontConfigureBase(DeclarativeBase): 

982 pass 

983 

984 

985 @event.listens_for( 

986 DontConfigureBase, 

987 "before_mapper_configured", 

988 # support return values for the event 

989 retval=True, 

990 # propagate the listener to all subclasses of 

991 # DontConfigureBase 

992 propagate=True, 

993 ) 

994 def dont_configure(mapper, cls): 

995 return EXT_SKIP 

996 

997 .. seealso:: 

998 

999 :meth:`.MapperEvents.before_configured` 

1000 

1001 :meth:`.MapperEvents.after_configured` 

1002 

1003 :meth:`.RegistryEvents.before_configured` 

1004 

1005 :meth:`.RegistryEvents.after_configured` 

1006 

1007 :meth:`.MapperEvents.mapper_configured` 

1008 

1009 """ 

1010 

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

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

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

1014 

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

1016 for each mapper that is encountered when the 

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

1018 list of not-yet-configured mappers. 

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

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

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

1022 detected. 

1023 

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

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

1026 other mappers; they might still be pending within the 

1027 configuration operation. Bidirectional relationships that 

1028 are instead configured via the 

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

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

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

1032 exist. 

1033 

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

1035 to go including backrefs that are defined only on other 

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

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

1038 fully configured. 

1039 

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

1041 :meth:`.MapperEvents.before_configured` or 

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

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

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

1045 event is therefore useful for configurational steps that benefit from 

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

1047 that "backref" configurations are necessarily ready yet. 

1048 

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

1050 of this event. 

1051 :param class\_: the mapped class. 

1052 

1053 .. seealso:: 

1054 

1055 :meth:`.MapperEvents.before_configured` 

1056 

1057 :meth:`.MapperEvents.after_configured` 

1058 

1059 :meth:`.RegistryEvents.before_configured` 

1060 

1061 :meth:`.RegistryEvents.after_configured` 

1062 

1063 :meth:`.MapperEvents.before_mapper_configured` 

1064 

1065 """ 

1066 # TODO: need coverage for this event 

1067 

1068 @event._omit_standard_example 

1069 def before_configured(self) -> None: 

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

1071 

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

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

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

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

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

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

1078 detected. 

1079 

1080 Similar events to this one include 

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

1082 of mappers has been configured, as well as 

1083 :meth:`.MapperEvents.before_mapper_configured` and 

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

1085 per-mapper basis. 

1086 

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

1088 and not to individual mappings or mapped classes:: 

1089 

1090 from sqlalchemy.orm import Mapper 

1091 

1092 

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

1094 def go(): ... 

1095 

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

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

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

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

1100 likely be called again. 

1101 

1102 .. seealso:: 

1103 

1104 :meth:`.MapperEvents.before_mapper_configured` 

1105 

1106 :meth:`.MapperEvents.mapper_configured` 

1107 

1108 :meth:`.MapperEvents.after_configured` 

1109 

1110 :meth:`.RegistryEvents.before_configured` 

1111 

1112 :meth:`.RegistryEvents.after_configured` 

1113 

1114 """ 

1115 

1116 @event._omit_standard_example 

1117 def after_configured(self) -> None: 

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

1119 

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

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

1122 invoked, after the function has completed its work. 

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

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

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

1126 detected. 

1127 

1128 Similar events to this one include 

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

1130 series of mappers are configured, as well as 

1131 :meth:`.MapperEvents.before_mapper_configured` and 

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

1133 per-mapper basis. 

1134 

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

1136 and not to individual mappings or mapped classes:: 

1137 

1138 from sqlalchemy.orm import Mapper 

1139 

1140 

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

1142 def go(): ... 

1143 

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

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

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

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

1148 likely be called again. 

1149 

1150 .. seealso:: 

1151 

1152 :meth:`.MapperEvents.before_mapper_configured` 

1153 

1154 :meth:`.MapperEvents.mapper_configured` 

1155 

1156 :meth:`.MapperEvents.before_configured` 

1157 

1158 :meth:`.RegistryEvents.before_configured` 

1159 

1160 :meth:`.RegistryEvents.after_configured` 

1161 

1162 """ 

1163 

1164 def before_insert( 

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

1166 ) -> None: 

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

1168 is emitted corresponding to that instance. 

1169 

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

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

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

1173 :ref:`orm_expression_update_delete`. To intercept ORM 

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

1175 

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

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

1178 as to emit additional SQL statements on the given 

1179 connection. 

1180 

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

1182 same class before their INSERT statements are emitted at 

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

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

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

1186 batches of instances to be broken up into individual 

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

1188 steps. 

1189 

1190 .. warning:: 

1191 

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

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

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

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

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

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

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

1199 

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

1201 of this event. 

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

1203 emit INSERT statements for this instance. This 

1204 provides a handle into the current transaction on the 

1205 target database specific to this instance. 

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

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

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

1209 object associated with the instance. 

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

1211 

1212 .. seealso:: 

1213 

1214 :ref:`session_persistence_events` 

1215 

1216 """ 

1217 

1218 def after_insert( 

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

1220 ) -> None: 

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

1222 is emitted corresponding to that instance. 

1223 

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

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

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

1227 :ref:`orm_expression_update_delete`. To intercept ORM 

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

1229 

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

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

1232 as to emit additional SQL statements on the given 

1233 connection. 

1234 

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

1236 same class after their INSERT statements have been 

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

1238 rare case that this is not desirable, the 

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

1240 which will cause batches of instances to be broken up 

1241 into individual (and more poorly performing) 

1242 event->persist->event steps. 

1243 

1244 .. warning:: 

1245 

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

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

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

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

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

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

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

1253 

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

1255 of this event. 

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

1257 emit INSERT statements for this instance. This 

1258 provides a handle into the current transaction on the 

1259 target database specific to this instance. 

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

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

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

1263 object associated with the instance. 

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

1265 

1266 .. seealso:: 

1267 

1268 :ref:`session_persistence_events` 

1269 

1270 """ 

1271 

1272 def before_update( 

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

1274 ) -> None: 

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

1276 is emitted corresponding to that instance. 

1277 

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

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

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

1281 :ref:`orm_expression_update_delete`. To intercept ORM 

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

1283 

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

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

1286 as to emit additional SQL statements on the given 

1287 connection. 

1288 

1289 This method is called for all instances that are 

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

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

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

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

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

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

1296 statement will be issued. This means that an instance 

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

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

1299 issued, although you can affect the outcome here by 

1300 modifying attributes so that a net change in value does 

1301 exist. 

1302 

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

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

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

1306 include_collections=False)``. 

1307 

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

1309 same class before their UPDATE statements are emitted at 

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

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

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

1313 batches of instances to be broken up into individual 

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

1315 steps. 

1316 

1317 .. warning:: 

1318 

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

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

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

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

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

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

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

1326 

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

1328 of this event. 

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

1330 emit UPDATE statements for this instance. This 

1331 provides a handle into the current transaction on the 

1332 target database specific to this instance. 

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

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

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

1336 object associated with the instance. 

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

1338 

1339 .. seealso:: 

1340 

1341 :ref:`session_persistence_events` 

1342 

1343 """ 

1344 

1345 def after_update( 

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

1347 ) -> None: 

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

1349 is emitted corresponding to that instance. 

1350 

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

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

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

1354 :ref:`orm_expression_update_delete`. To intercept ORM 

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

1356 

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

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

1359 as to emit additional SQL statements on the given 

1360 connection. 

1361 

1362 This method is called for all instances that are 

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

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

1365 no UPDATE statement has proceeded. An object is marked 

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

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

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

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

1370 statement will be issued. This means that an instance 

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

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

1373 issued. 

1374 

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

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

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

1378 include_collections=False)``. 

1379 

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

1381 same class after their UPDATE statements have been emitted at 

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

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

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

1385 batches of instances to be broken up into individual 

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

1387 steps. 

1388 

1389 .. warning:: 

1390 

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

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

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

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

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

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

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

1398 

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

1400 of this event. 

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

1402 emit UPDATE statements for this instance. This 

1403 provides a handle into the current transaction on the 

1404 target database specific to this instance. 

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

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

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

1408 object associated with the instance. 

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

1410 

1411 .. seealso:: 

1412 

1413 :ref:`session_persistence_events` 

1414 

1415 """ 

1416 

1417 def before_delete( 

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

1419 ) -> None: 

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

1421 is emitted corresponding to that instance. 

1422 

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

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

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

1426 :ref:`orm_expression_update_delete`. To intercept ORM 

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

1428 

1429 This event is used to emit additional SQL statements on 

1430 the given connection as well as to perform application 

1431 specific bookkeeping related to a deletion event. 

1432 

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

1434 same class before their DELETE statements are emitted at 

1435 once in a later step. 

1436 

1437 .. warning:: 

1438 

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

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

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

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

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

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

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

1446 

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

1448 of this event. 

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

1450 emit DELETE statements for this instance. This 

1451 provides a handle into the current transaction on the 

1452 target database specific to this instance. 

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

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

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

1456 object associated with the instance. 

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

1458 

1459 .. seealso:: 

1460 

1461 :ref:`session_persistence_events` 

1462 

1463 """ 

1464 

1465 def after_delete( 

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

1467 ) -> None: 

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

1469 has been emitted corresponding to that instance. 

1470 

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

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

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

1474 :ref:`orm_expression_update_delete`. To intercept ORM 

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

1476 

1477 This event is used to emit additional SQL statements on 

1478 the given connection as well as to perform application 

1479 specific bookkeeping related to a deletion event. 

1480 

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

1482 same class after their DELETE statements have been emitted at 

1483 once in a previous step. 

1484 

1485 .. warning:: 

1486 

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

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

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

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

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

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

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

1494 

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

1496 of this event. 

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

1498 emit DELETE statements for this instance. This 

1499 provides a handle into the current transaction on the 

1500 target database specific to this instance. 

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

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

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

1504 object associated with the instance. 

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

1506 

1507 .. seealso:: 

1508 

1509 :ref:`session_persistence_events` 

1510 

1511 """ 

1512 

1513 

1514class _MapperEventsHold(_EventsHold[_ET]): 

1515 all_holds = weakref.WeakKeyDictionary() 

1516 

1517 def resolve( 

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

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

1520 return _mapper_or_none(class_) 

1521 

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

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

1524 pass 

1525 

1526 dispatch = event.dispatcher(HoldMapperEvents) 

1527 

1528 

1529_sessionevents_lifecycle_event_names: Set[str] = set() 

1530 

1531 

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

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

1534 

1535 e.g.:: 

1536 

1537 from sqlalchemy import event 

1538 from sqlalchemy.orm import sessionmaker 

1539 

1540 

1541 def my_before_commit(session): 

1542 print("before commit!") 

1543 

1544 

1545 Session = sessionmaker() 

1546 

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

1548 

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

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

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

1552 

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

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

1555 globally. 

1556 

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

1558 to applicable event listener functions that work on individual 

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

1560 object, rather than the mapped instance itself. 

1561 

1562 :param restore_load_context=False: Applies to the 

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

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

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

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

1567 within this event if this flag is not set. 

1568 

1569 """ 

1570 

1571 _target_class_doc = "SomeSessionClassOrObject" 

1572 

1573 _dispatch_target = Session 

1574 

1575 def _lifecycle_event( # type: ignore [misc] 

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

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

1578 _sessionevents_lifecycle_event_names.add(fn.__name__) 

1579 return fn 

1580 

1581 @classmethod 

1582 def _accept_with( # type: ignore [return] 

1583 cls, target: Any, identifier: str 

1584 ) -> Union[Session, type]: 

1585 if isinstance(target, scoped_session): 

1586 target = target.session_factory 

1587 if not isinstance(target, sessionmaker) and ( 

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

1589 ): 

1590 raise exc.ArgumentError( 

1591 "Session event listen on a scoped_session " 

1592 "requires that its creation callable " 

1593 "is associated with the Session class." 

1594 ) 

1595 

1596 if isinstance(target, sessionmaker): 

1597 return target.class_ 

1598 elif isinstance(target, type): 

1599 if issubclass(target, scoped_session): 

1600 return Session 

1601 elif issubclass(target, Session): 

1602 return target 

1603 elif isinstance(target, Session): 

1604 return target 

1605 elif hasattr(target, "_no_async_engine_events"): 

1606 target._no_async_engine_events() 

1607 else: 

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

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

1610 

1611 @classmethod 

1612 def _listen( 

1613 cls, 

1614 event_key: Any, 

1615 *, 

1616 raw: bool = False, 

1617 restore_load_context: bool = False, 

1618 **kw: Any, 

1619 ) -> None: 

1620 is_instance_event = ( 

1621 event_key.identifier in _sessionevents_lifecycle_event_names 

1622 ) 

1623 

1624 if is_instance_event: 

1625 if not raw or restore_load_context: 

1626 fn = event_key._listen_fn 

1627 

1628 def wrap( 

1629 session: Session, 

1630 state: InstanceState[_O], 

1631 *arg: Any, 

1632 **kw: Any, 

1633 ) -> Optional[Any]: 

1634 if not raw: 

1635 target = state.obj() 

1636 if target is None: 

1637 # existing behavior is that if the object is 

1638 # garbage collected, no event is emitted 

1639 return None 

1640 else: 

1641 target = state # type: ignore [assignment] 

1642 if restore_load_context: 

1643 runid = state.runid 

1644 try: 

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

1646 finally: 

1647 if restore_load_context: 

1648 state.runid = runid 

1649 

1650 event_key = event_key.with_wrapper(wrap) 

1651 

1652 event_key.base_listen(**kw) 

1653 

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

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

1656 ORM :class:`.Session` object. 

1657 

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

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

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

1661 SQLAlchemy 1.4, all ORM queries that run through the 

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

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

1664 will participate in this event. 

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

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

1667 process described at :ref:`session_flushing`. 

1668 

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

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

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

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

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

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

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

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

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

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

1679 :meth:`.ConnectionEvents.before_execute` and 

1680 :meth:`.ConnectionEvents.before_cursor_execute`. 

1681 

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

1683 emitted internally within the ORM flush process, 

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

1685 intercept steps within the flush process, see the event 

1686 hooks described at :ref:`session_persistence_events` as 

1687 well as :ref:`session_persistence_mapper`. 

1688 

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

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

1691 performs. The intended use for this includes sharding and 

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

1693 across multiple database connections, returning a result that is 

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

1695 instead returning data from a cache. 

1696 

1697 The hook intends to replace the use of the 

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

1699 to SQLAlchemy 1.4. 

1700 

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

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

1703 as helper functions used to derive other commonly required 

1704 information. See that object for details. 

1705 

1706 .. seealso:: 

1707 

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

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

1710 

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

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

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

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

1715 and parameters as well as an option that allows programmatic 

1716 invocation of the statement at any point. 

1717 

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

1719 :meth:`_orm.SessionEvents.do_orm_execute` 

1720 

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

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

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

1724 

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

1726 extension relies upon the 

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

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

1729 

1730 

1731 .. versionadded:: 1.4 

1732 

1733 """ 

1734 

1735 def after_transaction_create( 

1736 self, session: Session, transaction: SessionTransaction 

1737 ) -> None: 

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

1739 

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

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

1742 overall, as opposed to when transactions are begun 

1743 on individual database connections. It is also invoked 

1744 for nested transactions and subtransactions, and is always 

1745 matched by a corresponding 

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

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

1748 

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

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

1751 

1752 To detect if this is the outermost 

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

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

1755 is ``None``:: 

1756 

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

1758 def after_transaction_create(session, transaction): 

1759 if transaction.parent is None: 

1760 ... # work with top-level transaction 

1761 

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

1763 :attr:`.SessionTransaction.nested` attribute:: 

1764 

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

1766 def after_transaction_create(session, transaction): 

1767 if transaction.nested: 

1768 ... # work with SAVEPOINT transaction 

1769 

1770 .. seealso:: 

1771 

1772 :class:`.SessionTransaction` 

1773 

1774 :meth:`~.SessionEvents.after_transaction_end` 

1775 

1776 """ 

1777 

1778 def after_transaction_end( 

1779 self, session: Session, transaction: SessionTransaction 

1780 ) -> None: 

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

1782 

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

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

1785 objects in use, including those for nested transactions 

1786 and subtransactions, and is always matched by a corresponding 

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

1788 

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

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

1791 

1792 To detect if this is the outermost 

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

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

1795 is ``None``:: 

1796 

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

1798 def after_transaction_end(session, transaction): 

1799 if transaction.parent is None: 

1800 ... # work with top-level transaction 

1801 

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

1803 :attr:`.SessionTransaction.nested` attribute:: 

1804 

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

1806 def after_transaction_end(session, transaction): 

1807 if transaction.nested: 

1808 ... # work with SAVEPOINT transaction 

1809 

1810 .. seealso:: 

1811 

1812 :class:`.SessionTransaction` 

1813 

1814 :meth:`~.SessionEvents.after_transaction_create` 

1815 

1816 """ 

1817 

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

1819 """Execute before commit is called. 

1820 

1821 .. note:: 

1822 

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

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

1825 many times within the scope of a transaction. 

1826 For interception of these events, use the 

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

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

1829 :meth:`~.SessionEvents.after_flush_postexec` 

1830 events. 

1831 

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

1833 

1834 .. seealso:: 

1835 

1836 :meth:`~.SessionEvents.after_commit` 

1837 

1838 :meth:`~.SessionEvents.after_begin` 

1839 

1840 :meth:`~.SessionEvents.after_transaction_create` 

1841 

1842 :meth:`~.SessionEvents.after_transaction_end` 

1843 

1844 """ 

1845 

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

1847 """Execute after a commit has occurred. 

1848 

1849 .. note:: 

1850 

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

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

1853 many times within the scope of a transaction. 

1854 For interception of these events, use the 

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

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

1857 :meth:`~.SessionEvents.after_flush_postexec` 

1858 events. 

1859 

1860 .. note:: 

1861 

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

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

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

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

1866 event. 

1867 

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

1869 

1870 .. seealso:: 

1871 

1872 :meth:`~.SessionEvents.before_commit` 

1873 

1874 :meth:`~.SessionEvents.after_begin` 

1875 

1876 :meth:`~.SessionEvents.after_transaction_create` 

1877 

1878 :meth:`~.SessionEvents.after_transaction_end` 

1879 

1880 """ 

1881 

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

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

1884 

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

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

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

1888 DBAPI transaction has already been rolled back. In many 

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

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

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

1892 which is active after the outermost rollback has proceeded, 

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

1894 :attr:`.Session.is_active` flag. 

1895 

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

1897 

1898 """ 

1899 

1900 def after_soft_rollback( 

1901 self, session: Session, previous_transaction: SessionTransaction 

1902 ) -> None: 

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

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

1905 

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

1907 the innermost rollback that calls the DBAPI's 

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

1909 calls that only pop themselves from the transaction stack. 

1910 

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

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

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

1914 

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

1916 def do_something(session, previous_transaction): 

1917 if session.is_active: 

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

1919 

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

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

1922 transactional marker object which was just closed. The current 

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

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

1925 

1926 """ 

1927 

1928 def before_flush( 

1929 self, 

1930 session: Session, 

1931 flush_context: UOWTransaction, 

1932 instances: Optional[Sequence[_O]], 

1933 ) -> None: 

1934 """Execute before flush process has started. 

1935 

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

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

1938 which handles the details of the flush. 

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

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

1941 (note this usage is deprecated). 

1942 

1943 .. seealso:: 

1944 

1945 :meth:`~.SessionEvents.after_flush` 

1946 

1947 :meth:`~.SessionEvents.after_flush_postexec` 

1948 

1949 :ref:`session_persistence_events` 

1950 

1951 """ 

1952 

1953 def after_flush( 

1954 self, session: Session, flush_context: UOWTransaction 

1955 ) -> None: 

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

1957 called. 

1958 

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

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

1961 as the history settings on instance attributes. 

1962 

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

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

1965 internal state to reflect those changes, including that newly 

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

1967 emitted within this event such as loads of related items 

1968 may produce new identity map entries that will immediately 

1969 be replaced, sometimes causing confusing results. SQLAlchemy will 

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

1971 

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

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

1974 which handles the details of the flush. 

1975 

1976 .. seealso:: 

1977 

1978 :meth:`~.SessionEvents.before_flush` 

1979 

1980 :meth:`~.SessionEvents.after_flush_postexec` 

1981 

1982 :ref:`session_persistence_events` 

1983 

1984 """ 

1985 

1986 def after_flush_postexec( 

1987 self, session: Session, flush_context: UOWTransaction 

1988 ) -> None: 

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

1990 state occurs. 

1991 

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

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

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

1995 transaction or participated in a larger transaction. 

1996 

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

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

1999 which handles the details of the flush. 

2000 

2001 

2002 .. seealso:: 

2003 

2004 :meth:`~.SessionEvents.before_flush` 

2005 

2006 :meth:`~.SessionEvents.after_flush` 

2007 

2008 :ref:`session_persistence_events` 

2009 

2010 """ 

2011 

2012 def after_begin( 

2013 self, 

2014 session: Session, 

2015 transaction: SessionTransaction, 

2016 connection: Connection, 

2017 ) -> None: 

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

2019 

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

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

2022 To invoke SQL operations within this hook, use the 

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

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

2025 directly. 

2026 

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

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

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

2030 which will be used for SQL statements. 

2031 

2032 .. seealso:: 

2033 

2034 :meth:`~.SessionEvents.before_commit` 

2035 

2036 :meth:`~.SessionEvents.after_commit` 

2037 

2038 :meth:`~.SessionEvents.after_transaction_create` 

2039 

2040 :meth:`~.SessionEvents.after_transaction_end` 

2041 

2042 """ 

2043 

2044 @_lifecycle_event 

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

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

2047 

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

2049 the object to be part of the session. 

2050 

2051 .. seealso:: 

2052 

2053 :meth:`~.SessionEvents.after_attach` 

2054 

2055 :ref:`session_lifecycle_events` 

2056 

2057 """ 

2058 

2059 @_lifecycle_event 

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

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

2062 

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

2064 

2065 .. note:: 

2066 

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

2068 has been fully associated with the session, which is 

2069 different than previous releases. For event 

2070 handlers that require the object not yet 

2071 be part of session state (such as handlers which 

2072 may autoflush while the target object is not 

2073 yet complete) consider the 

2074 new :meth:`.before_attach` event. 

2075 

2076 .. seealso:: 

2077 

2078 :meth:`~.SessionEvents.before_attach` 

2079 

2080 :ref:`session_lifecycle_events` 

2081 

2082 """ 

2083 

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

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

2086 has been called. 

2087 

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

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

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

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

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

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

2094 these calls. 

2095 

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

2097 details about the update, including these attributes: 

2098 

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

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

2101 object that this update operation 

2102 was called upon. 

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

2104 :meth:`_query.Query.update`. 

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

2106 returned as a result of the 

2107 bulk UPDATE operation. 

2108 

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

2110 ``QueryContext`` object associated with it. 

2111 

2112 .. seealso:: 

2113 

2114 :meth:`.QueryEvents.before_compile_update` 

2115 

2116 :meth:`.SessionEvents.after_bulk_delete` 

2117 

2118 """ 

2119 

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

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

2122 has been called. 

2123 

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

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

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

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

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

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

2130 these calls. 

2131 

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

2133 details about the update, including these attributes: 

2134 

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

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

2137 object that this update operation 

2138 was called upon. 

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

2140 returned as a result of the 

2141 bulk DELETE operation. 

2142 

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

2144 ``QueryContext`` object associated with it. 

2145 

2146 .. seealso:: 

2147 

2148 :meth:`.QueryEvents.before_compile_delete` 

2149 

2150 :meth:`.SessionEvents.after_bulk_update` 

2151 

2152 """ 

2153 

2154 @_lifecycle_event 

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

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

2157 object. 

2158 

2159 This event is a specialization of the 

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

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

2162 :meth:`.Session.add` call. 

2163 

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

2165 

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

2167 

2168 .. seealso:: 

2169 

2170 :ref:`session_lifecycle_events` 

2171 

2172 """ 

2173 

2174 @_lifecycle_event 

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

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

2177 object. 

2178 

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

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

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

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

2183 

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

2185 

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

2187 

2188 .. seealso:: 

2189 

2190 :ref:`session_lifecycle_events` 

2191 

2192 """ 

2193 

2194 @_lifecycle_event 

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

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

2197 object. 

2198 

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

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

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

2202 

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

2204 

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

2206 

2207 .. seealso:: 

2208 

2209 :ref:`session_lifecycle_events` 

2210 

2211 """ 

2212 

2213 @_lifecycle_event 

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

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

2216 object. 

2217 

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

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

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

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

2222 when the event is called. 

2223 

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

2225 

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

2227 

2228 .. seealso:: 

2229 

2230 :ref:`session_lifecycle_events` 

2231 

2232 """ 

2233 

2234 @_lifecycle_event 

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

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

2237 object. 

2238 

2239 This event is a specialization of the 

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

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

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

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

2244 associated with the 

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

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

2247 

2248 .. note:: 

2249 

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

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

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

2253 check the ``deleted`` flag sent to the 

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

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

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

2257 objects need to be intercepted before the flush. 

2258 

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

2260 

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

2262 

2263 .. seealso:: 

2264 

2265 :ref:`session_lifecycle_events` 

2266 

2267 """ 

2268 

2269 @_lifecycle_event 

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

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

2272 object. 

2273 

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

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

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

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

2278 with the other session lifecycle events smoothly. The object 

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

2280 this event is called. 

2281 

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

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

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

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

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

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

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

2289 works in the same manner as that of 

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

2291 resolve this scenario. 

2292 

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

2294 

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

2296 

2297 .. seealso:: 

2298 

2299 :ref:`session_lifecycle_events` 

2300 

2301 """ 

2302 

2303 @_lifecycle_event 

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

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

2306 object. 

2307 

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

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

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

2311 transaction completes. 

2312 

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

2314 to the persistent state, and the 

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

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

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

2318 event. 

2319 

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

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

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

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

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

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

2326 invoked at the end of a flush. 

2327 

2328 .. seealso:: 

2329 

2330 :ref:`session_lifecycle_events` 

2331 

2332 """ 

2333 

2334 @_lifecycle_event 

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

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

2337 object. 

2338 

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

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

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

2342 any other circumstances. 

2343 

2344 .. seealso:: 

2345 

2346 :ref:`session_lifecycle_events` 

2347 

2348 """ 

2349 

2350 @_lifecycle_event 

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

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

2353 object. 

2354 

2355 This event is invoked when a deleted object is evicted 

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

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

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

2359 state to the detached state. 

2360 

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

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

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

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

2365 

2366 .. seealso:: 

2367 

2368 :ref:`session_lifecycle_events` 

2369 

2370 """ 

2371 

2372 @_lifecycle_event 

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

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

2375 object. 

2376 

2377 This event is invoked when a persistent object is evicted 

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

2379 to happen, including: 

2380 

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

2382 or :meth:`.Session.close` 

2383 

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

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

2386 

2387 

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

2389 

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

2391 

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

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

2394 

2395 

2396 .. seealso:: 

2397 

2398 :ref:`session_lifecycle_events` 

2399 

2400 """ 

2401 

2402 

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

2404 r"""Define events for object attributes. 

2405 

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

2407 target class. 

2408 

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

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

2411 

2412 from sqlalchemy import event 

2413 

2414 

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

2416 def my_append_listener(target, value, initiator): 

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

2418 

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

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

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

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

2423 

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

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

2426 

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

2428 

2429 

2430 # setup listener on UserContact.phone attribute, instructing 

2431 # it to use the return value 

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

2433 

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

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

2436 

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

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

2439 as when using mapper inheritance patterns:: 

2440 

2441 

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

2443 def receive_set(target, value, initiator): 

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

2445 

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

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

2448 

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

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

2451 replaced unconditionally, even if this requires firing off 

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

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

2454 :func:`_orm.relationship`. 

2455 

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

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

2458 for attributes of the same name on all current subclasses 

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

2460 class, using an additional listener that listens for 

2461 instrumentation events. 

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

2463 event will be the :class:`.InstanceState` management 

2464 object, rather than the mapped instance itself. 

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

2466 listening must return the "value" argument from the 

2467 function. This gives the listening function the opportunity 

2468 to change the value that is ultimately used for a "set" 

2469 or "append" event. 

2470 

2471 """ 

2472 

2473 _target_class_doc = "SomeClass.some_attribute" 

2474 _dispatch_target = QueryableAttribute 

2475 

2476 @staticmethod 

2477 def _set_dispatch( 

2478 cls: Type[_HasEventsDispatch[Any]], dispatch_cls: Type[_Dispatch[Any]] 

2479 ) -> _Dispatch[Any]: 

2480 dispatch = event.Events._set_dispatch(cls, dispatch_cls) 

2481 dispatch_cls._active_history = False 

2482 return dispatch 

2483 

2484 @classmethod 

2485 def _accept_with( 

2486 cls, 

2487 target: Union[QueryableAttribute[Any], Type[QueryableAttribute[Any]]], 

2488 identifier: str, 

2489 ) -> Union[QueryableAttribute[Any], Type[QueryableAttribute[Any]]]: 

2490 # TODO: coverage 

2491 if isinstance(target, interfaces.MapperProperty): 

2492 return getattr(target.parent.class_, target.key) 

2493 else: 

2494 return target 

2495 

2496 @classmethod 

2497 def _listen( # type: ignore [override] 

2498 cls, 

2499 event_key: _EventKey[QueryableAttribute[Any]], 

2500 active_history: bool = False, 

2501 raw: bool = False, 

2502 retval: bool = False, 

2503 propagate: bool = False, 

2504 include_key: bool = False, 

2505 ) -> None: 

2506 target, fn = event_key.dispatch_target, event_key._listen_fn 

2507 

2508 if active_history: 

2509 target.dispatch._active_history = True 

2510 

2511 if not raw or not retval or not include_key: 

2512 

2513 def wrap(target: InstanceState[_O], *arg: Any, **kw: Any) -> Any: 

2514 if not raw: 

2515 target = target.obj() # type: ignore [assignment] 

2516 if not retval: 

2517 if arg: 

2518 value = arg[0] 

2519 else: 

2520 value = None 

2521 if include_key: 

2522 fn(target, *arg, **kw) 

2523 else: 

2524 fn(target, *arg) 

2525 return value 

2526 else: 

2527 if include_key: 

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

2529 else: 

2530 return fn(target, *arg) 

2531 

2532 event_key = event_key.with_wrapper(wrap) 

2533 

2534 event_key.base_listen(propagate=propagate) 

2535 

2536 if propagate: 

2537 manager = instrumentation.manager_of_class(target.class_) 

2538 

2539 for mgr in manager.subclass_managers(True): # type: ignore [no-untyped-call] # noqa: E501 

2540 event_key.with_dispatch_target(mgr[target.key]).base_listen( 

2541 propagate=True 

2542 ) 

2543 if active_history: 

2544 mgr[target.key].dispatch._active_history = True 

2545 

2546 def append( 

2547 self, 

2548 target: _O, 

2549 value: _T, 

2550 initiator: Event, 

2551 *, 

2552 key: EventConstants = NO_KEY, 

2553 ) -> Optional[_T]: 

2554 """Receive a collection append event. 

2555 

2556 The append event is invoked for each element as it is appended 

2557 to the collection. This occurs for single-item appends as well 

2558 as for a "bulk replace" operation. 

2559 

2560 :param target: the object instance receiving the event. 

2561 If the listener is registered with ``raw=True``, this will 

2562 be the :class:`.InstanceState` object. 

2563 :param value: the value being appended. If this listener 

2564 is registered with ``retval=True``, the listener 

2565 function must return this value, or a new value which 

2566 replaces it. 

2567 :param initiator: An instance of :class:`.attributes.Event` 

2568 representing the initiation of the event. May be modified 

2569 from its original value by backref handlers in order to control 

2570 chained event propagation, as well as be inspected for information 

2571 about the source of the event. 

2572 :param key: When the event is established using the 

2573 :paramref:`.AttributeEvents.include_key` parameter set to 

2574 True, this will be the key used in the operation, such as 

2575 ``collection[some_key_or_index] = value``. 

2576 The parameter is not passed 

2577 to the event at all if the the 

2578 :paramref:`.AttributeEvents.include_key` 

2579 was not used to set up the event; this is to allow backwards 

2580 compatibility with existing event handlers that don't include the 

2581 ``key`` parameter. 

2582 

2583 .. versionadded:: 2.0 

2584 

2585 :return: if the event was registered with ``retval=True``, 

2586 the given value, or a new effective value, should be returned. 

2587 

2588 .. seealso:: 

2589 

2590 :class:`.AttributeEvents` - background on listener options such 

2591 as propagation to subclasses. 

2592 

2593 :meth:`.AttributeEvents.bulk_replace` 

2594 

2595 """ 

2596 

2597 def append_wo_mutation( 

2598 self, 

2599 target: _O, 

2600 value: _T, 

2601 initiator: Event, 

2602 *, 

2603 key: EventConstants = NO_KEY, 

2604 ) -> None: 

2605 """Receive a collection append event where the collection was not 

2606 actually mutated. 

2607 

2608 This event differs from :meth:`_orm.AttributeEvents.append` in that 

2609 it is fired off for de-duplicating collections such as sets and 

2610 dictionaries, when the object already exists in the target collection. 

2611 The event does not have a return value and the identity of the 

2612 given object cannot be changed. 

2613 

2614 The event is used for cascading objects into a :class:`_orm.Session` 

2615 when the collection has already been mutated via a backref event. 

2616 

2617 :param target: the object instance receiving the event. 

2618 If the listener is registered with ``raw=True``, this will 

2619 be the :class:`.InstanceState` object. 

2620 :param value: the value that would be appended if the object did not 

2621 already exist in the collection. 

2622 :param initiator: An instance of :class:`.attributes.Event` 

2623 representing the initiation of the event. May be modified 

2624 from its original value by backref handlers in order to control 

2625 chained event propagation, as well as be inspected for information 

2626 about the source of the event. 

2627 :param key: When the event is established using the 

2628 :paramref:`.AttributeEvents.include_key` parameter set to 

2629 True, this will be the key used in the operation, such as 

2630 ``collection[some_key_or_index] = value``. 

2631 The parameter is not passed 

2632 to the event at all if the the 

2633 :paramref:`.AttributeEvents.include_key` 

2634 was not used to set up the event; this is to allow backwards 

2635 compatibility with existing event handlers that don't include the 

2636 ``key`` parameter. 

2637 

2638 .. versionadded:: 2.0 

2639 

2640 :return: No return value is defined for this event. 

2641 

2642 .. versionadded:: 1.4.15 

2643 

2644 """ 

2645 

2646 def bulk_replace( 

2647 self, 

2648 target: _O, 

2649 values: Iterable[_T], 

2650 initiator: Event, 

2651 *, 

2652 keys: Optional[Iterable[EventConstants]] = None, 

2653 ) -> None: 

2654 """Receive a collection 'bulk replace' event. 

2655 

2656 This event is invoked for a sequence of values as they are incoming 

2657 to a bulk collection set operation, which can be 

2658 modified in place before the values are treated as ORM objects. 

2659 This is an "early hook" that runs before the bulk replace routine 

2660 attempts to reconcile which objects are already present in the 

2661 collection and which are being removed by the net replace operation. 

2662 

2663 It is typical that this method be combined with use of the 

2664 :meth:`.AttributeEvents.append` event. When using both of these 

2665 events, note that a bulk replace operation will invoke 

2666 the :meth:`.AttributeEvents.append` event for all new items, 

2667 even after :meth:`.AttributeEvents.bulk_replace` has been invoked 

2668 for the collection as a whole. In order to determine if an 

2669 :meth:`.AttributeEvents.append` event is part of a bulk replace, 

2670 use the symbol :attr:`~.attributes.OP_BULK_REPLACE` to test the 

2671 incoming initiator:: 

2672 

2673 from sqlalchemy.orm.attributes import OP_BULK_REPLACE 

2674 

2675 

2676 @event.listens_for(SomeObject.collection, "bulk_replace") 

2677 def process_collection(target, values, initiator): 

2678 values[:] = [_make_value(value) for value in values] 

2679 

2680 

2681 @event.listens_for(SomeObject.collection, "append", retval=True) 

2682 def process_collection(target, value, initiator): 

2683 # make sure bulk_replace didn't already do it 

2684 if initiator is None or initiator.op is not OP_BULK_REPLACE: 

2685 return _make_value(value) 

2686 else: 

2687 return value 

2688 

2689 :param target: the object instance receiving the event. 

2690 If the listener is registered with ``raw=True``, this will 

2691 be the :class:`.InstanceState` object. 

2692 :param value: a sequence (e.g. a list) of the values being set. The 

2693 handler can modify this list in place. 

2694 :param initiator: An instance of :class:`.attributes.Event` 

2695 representing the initiation of the event. 

2696 :param keys: When the event is established using the 

2697 :paramref:`.AttributeEvents.include_key` parameter set to 

2698 True, this will be the sequence of keys used in the operation, 

2699 typically only for a dictionary update. The parameter is not passed 

2700 to the event at all if the the 

2701 :paramref:`.AttributeEvents.include_key` 

2702 was not used to set up the event; this is to allow backwards 

2703 compatibility with existing event handlers that don't include the 

2704 ``key`` parameter. 

2705 

2706 .. versionadded:: 2.0 

2707 

2708 .. seealso:: 

2709 

2710 :class:`.AttributeEvents` - background on listener options such 

2711 as propagation to subclasses. 

2712 

2713 

2714 """ 

2715 

2716 def remove( 

2717 self, 

2718 target: _O, 

2719 value: _T, 

2720 initiator: Event, 

2721 *, 

2722 key: EventConstants = NO_KEY, 

2723 ) -> None: 

2724 """Receive a collection remove event. 

2725 

2726 :param target: the object instance receiving the event. 

2727 If the listener is registered with ``raw=True``, this will 

2728 be the :class:`.InstanceState` object. 

2729 :param value: the value being removed. 

2730 :param initiator: An instance of :class:`.attributes.Event` 

2731 representing the initiation of the event. May be modified 

2732 from its original value by backref handlers in order to control 

2733 chained event propagation. 

2734 

2735 :param key: When the event is established using the 

2736 :paramref:`.AttributeEvents.include_key` parameter set to 

2737 True, this will be the key used in the operation, such as 

2738 ``del collection[some_key_or_index]``. The parameter is not passed 

2739 to the event at all if the the 

2740 :paramref:`.AttributeEvents.include_key` 

2741 was not used to set up the event; this is to allow backwards 

2742 compatibility with existing event handlers that don't include the 

2743 ``key`` parameter. 

2744 

2745 .. versionadded:: 2.0 

2746 

2747 :return: No return value is defined for this event. 

2748 

2749 

2750 .. seealso:: 

2751 

2752 :class:`.AttributeEvents` - background on listener options such 

2753 as propagation to subclasses. 

2754 

2755 """ 

2756 

2757 def set( 

2758 self, target: _O, value: _T, oldvalue: _T, initiator: Event 

2759 ) -> None: 

2760 """Receive a scalar set event. 

2761 

2762 :param target: the object instance receiving the event. 

2763 If the listener is registered with ``raw=True``, this will 

2764 be the :class:`.InstanceState` object. 

2765 :param value: the value being set. If this listener 

2766 is registered with ``retval=True``, the listener 

2767 function must return this value, or a new value which 

2768 replaces it. 

2769 :param oldvalue: the previous value being replaced. This 

2770 may also be the symbol ``NEVER_SET`` or ``NO_VALUE``. 

2771 If the listener is registered with ``active_history=True``, 

2772 the previous value of the attribute will be loaded from 

2773 the database if the existing value is currently unloaded 

2774 or expired. 

2775 :param initiator: An instance of :class:`.attributes.Event` 

2776 representing the initiation of the event. May be modified 

2777 from its original value by backref handlers in order to control 

2778 chained event propagation. 

2779 

2780 :return: if the event was registered with ``retval=True``, 

2781 the given value, or a new effective value, should be returned. 

2782 

2783 .. seealso:: 

2784 

2785 :class:`.AttributeEvents` - background on listener options such 

2786 as propagation to subclasses. 

2787 

2788 """ 

2789 

2790 def init_scalar( 

2791 self, target: _O, value: _T, dict_: Dict[Any, Any] 

2792 ) -> None: 

2793 r"""Receive a scalar "init" event. 

2794 

2795 This event is invoked when an uninitialized, unpersisted scalar 

2796 attribute is accessed, e.g. read:: 

2797 

2798 

2799 x = my_object.some_attribute 

2800 

2801 The ORM's default behavior when this occurs for an un-initialized 

2802 attribute is to return the value ``None``; note this differs from 

2803 Python's usual behavior of raising ``AttributeError``. The 

2804 event here can be used to customize what value is actually returned, 

2805 with the assumption that the event listener would be mirroring 

2806 a default generator that is configured on the Core 

2807 :class:`_schema.Column` 

2808 object as well. 

2809 

2810 Since a default generator on a :class:`_schema.Column` 

2811 might also produce 

2812 a changing value such as a timestamp, the 

2813 :meth:`.AttributeEvents.init_scalar` 

2814 event handler can also be used to **set** the newly returned value, so 

2815 that a Core-level default generation function effectively fires off 

2816 only once, but at the moment the attribute is accessed on the 

2817 non-persisted object. Normally, no change to the object's state 

2818 is made when an uninitialized attribute is accessed (much older 

2819 SQLAlchemy versions did in fact change the object's state). 

2820 

2821 If a default generator on a column returned a particular constant, 

2822 a handler might be used as follows:: 

2823 

2824 SOME_CONSTANT = 3.1415926 

2825 

2826 

2827 class MyClass(Base): 

2828 # ... 

2829 

2830 some_attribute = Column(Numeric, default=SOME_CONSTANT) 

2831 

2832 

2833 @event.listens_for( 

2834 MyClass.some_attribute, "init_scalar", retval=True, propagate=True 

2835 ) 

2836 def _init_some_attribute(target, dict_, value): 

2837 dict_["some_attribute"] = SOME_CONSTANT 

2838 return SOME_CONSTANT 

2839 

2840 Above, we initialize the attribute ``MyClass.some_attribute`` to the 

2841 value of ``SOME_CONSTANT``. The above code includes the following 

2842 features: 

2843 

2844 * By setting the value ``SOME_CONSTANT`` in the given ``dict_``, 

2845 we indicate that this value is to be persisted to the database. 

2846 This supersedes the use of ``SOME_CONSTANT`` in the default generator 

2847 for the :class:`_schema.Column`. The ``active_column_defaults.py`` 

2848 example given at :ref:`examples_instrumentation` illustrates using 

2849 the same approach for a changing default, e.g. a timestamp 

2850 generator. In this particular example, it is not strictly 

2851 necessary to do this since ``SOME_CONSTANT`` would be part of the 

2852 INSERT statement in either case. 

2853 

2854 * By establishing the ``retval=True`` flag, the value we return 

2855 from the function will be returned by the attribute getter. 

2856 Without this flag, the event is assumed to be a passive observer 

2857 and the return value of our function is ignored. 

2858 

2859 * The ``propagate=True`` flag is significant if the mapped class 

2860 includes inheriting subclasses, which would also make use of this 

2861 event listener. Without this flag, an inheriting subclass will 

2862 not use our event handler. 

2863 

2864 In the above example, the attribute set event 

2865 :meth:`.AttributeEvents.set` as well as the related validation feature 

2866 provided by :obj:`_orm.validates` is **not** invoked when we apply our 

2867 value to the given ``dict_``. To have these events to invoke in 

2868 response to our newly generated value, apply the value to the given 

2869 object as a normal attribute set operation:: 

2870 

2871 SOME_CONSTANT = 3.1415926 

2872 

2873 

2874 @event.listens_for( 

2875 MyClass.some_attribute, "init_scalar", retval=True, propagate=True 

2876 ) 

2877 def _init_some_attribute(target, dict_, value): 

2878 # will also fire off attribute set events 

2879 target.some_attribute = SOME_CONSTANT 

2880 return SOME_CONSTANT 

2881 

2882 When multiple listeners are set up, the generation of the value 

2883 is "chained" from one listener to the next by passing the value 

2884 returned by the previous listener that specifies ``retval=True`` 

2885 as the ``value`` argument of the next listener. 

2886 

2887 :param target: the object instance receiving the event. 

2888 If the listener is registered with ``raw=True``, this will 

2889 be the :class:`.InstanceState` object. 

2890 :param value: the value that is to be returned before this event 

2891 listener were invoked. This value begins as the value ``None``, 

2892 however will be the return value of the previous event handler 

2893 function if multiple listeners are present. 

2894 :param dict\_: the attribute dictionary of this mapped object. 

2895 This is normally the ``__dict__`` of the object, but in all cases 

2896 represents the destination that the attribute system uses to get 

2897 at the actual value of this attribute. Placing the value in this 

2898 dictionary has the effect that the value will be used in the 

2899 INSERT statement generated by the unit of work. 

2900 

2901 

2902 .. seealso:: 

2903 

2904 :meth:`.AttributeEvents.init_collection` - collection version 

2905 of this event 

2906 

2907 :class:`.AttributeEvents` - background on listener options such 

2908 as propagation to subclasses. 

2909 

2910 :ref:`examples_instrumentation` - see the 

2911 ``active_column_defaults.py`` example. 

2912 

2913 """ # noqa: E501 

2914 

2915 def init_collection( 

2916 self, 

2917 target: _O, 

2918 collection: Type[Collection[Any]], 

2919 collection_adapter: CollectionAdapter, 

2920 ) -> None: 

2921 """Receive a 'collection init' event. 

2922 

2923 This event is triggered for a collection-based attribute, when 

2924 the initial "empty collection" is first generated for a blank 

2925 attribute, as well as for when the collection is replaced with 

2926 a new one, such as via a set event. 

2927 

2928 E.g., given that ``User.addresses`` is a relationship-based 

2929 collection, the event is triggered here:: 

2930 

2931 u1 = User() 

2932 u1.addresses.append(a1) # <- new collection 

2933 

2934 and also during replace operations:: 

2935 

2936 u1.addresses = [a2, a3] # <- new collection 

2937 

2938 :param target: the object instance receiving the event. 

2939 If the listener is registered with ``raw=True``, this will 

2940 be the :class:`.InstanceState` object. 

2941 :param collection: the new collection. This will always be generated 

2942 from what was specified as 

2943 :paramref:`_orm.relationship.collection_class`, and will always 

2944 be empty. 

2945 :param collection_adapter: the :class:`.CollectionAdapter` that will 

2946 mediate internal access to the collection. 

2947 

2948 .. seealso:: 

2949 

2950 :class:`.AttributeEvents` - background on listener options such 

2951 as propagation to subclasses. 

2952 

2953 :meth:`.AttributeEvents.init_scalar` - "scalar" version of this 

2954 event. 

2955 

2956 """ 

2957 

2958 def dispose_collection( 

2959 self, 

2960 target: _O, 

2961 collection: Collection[Any], 

2962 collection_adapter: CollectionAdapter, 

2963 ) -> None: 

2964 """Receive a 'collection dispose' event. 

2965 

2966 This event is triggered for a collection-based attribute when 

2967 a collection is replaced, that is:: 

2968 

2969 u1.addresses.append(a1) 

2970 

2971 u1.addresses = [a2, a3] # <- old collection is disposed 

2972 

2973 The old collection received will contain its previous contents. 

2974 

2975 .. seealso:: 

2976 

2977 :class:`.AttributeEvents` - background on listener options such 

2978 as propagation to subclasses. 

2979 

2980 """ 

2981 

2982 def modified(self, target: _O, initiator: Event) -> None: 

2983 """Receive a 'modified' event. 

2984 

2985 This event is triggered when the :func:`.attributes.flag_modified` 

2986 function is used to trigger a modify event on an attribute without 

2987 any specific value being set. 

2988 

2989 :param target: the object instance receiving the event. 

2990 If the listener is registered with ``raw=True``, this will 

2991 be the :class:`.InstanceState` object. 

2992 

2993 :param initiator: An instance of :class:`.attributes.Event` 

2994 representing the initiation of the event. 

2995 

2996 .. seealso:: 

2997 

2998 :class:`.AttributeEvents` - background on listener options such 

2999 as propagation to subclasses. 

3000 

3001 """ 

3002 

3003 

3004class QueryEvents(event.Events[Query[Any]]): 

3005 """Represent events within the construction of a :class:`_query.Query` 

3006 object. 

3007 

3008 .. legacy:: The :class:`_orm.QueryEvents` event methods are legacy 

3009 as of SQLAlchemy 2.0, and only apply to direct use of the 

3010 :class:`_orm.Query` object. They are not used for :term:`2.0 style` 

3011 statements. For events to intercept and modify 2.0 style ORM use, 

3012 use the :meth:`_orm.SessionEvents.do_orm_execute` hook. 

3013 

3014 

3015 The :class:`_orm.QueryEvents` hooks are now superseded by the 

3016 :meth:`_orm.SessionEvents.do_orm_execute` event hook. 

3017 

3018 """ 

3019 

3020 _target_class_doc = "SomeQuery" 

3021 _dispatch_target = Query 

3022 

3023 def before_compile(self, query: Query[Any]) -> None: 

3024 """Receive the :class:`_query.Query` 

3025 object before it is composed into a 

3026 core :class:`_expression.Select` object. 

3027 

3028 .. deprecated:: 1.4 The :meth:`_orm.QueryEvents.before_compile` event 

3029 is superseded by the much more capable 

3030 :meth:`_orm.SessionEvents.do_orm_execute` hook. In version 1.4, 

3031 the :meth:`_orm.QueryEvents.before_compile` event is **no longer 

3032 used** for ORM-level attribute loads, such as loads of deferred 

3033 or expired attributes as well as relationship loaders. See the 

3034 new examples in :ref:`examples_session_orm_events` which 

3035 illustrate new ways of intercepting and modifying ORM queries 

3036 for the most common purpose of adding arbitrary filter criteria. 

3037 

3038 

3039 This event is intended to allow changes to the query given:: 

3040 

3041 @event.listens_for(Query, "before_compile", retval=True) 

3042 def no_deleted(query): 

3043 for desc in query.column_descriptions: 

3044 if desc["type"] is User: 

3045 entity = desc["entity"] 

3046 query = query.filter(entity.deleted == False) 

3047 return query 

3048 

3049 The event should normally be listened with the ``retval=True`` 

3050 parameter set, so that the modified query may be returned. 

3051 

3052 The :meth:`.QueryEvents.before_compile` event by default 

3053 will disallow "baked" queries from caching a query, if the event 

3054 hook returns a new :class:`_query.Query` object. 

3055 This affects both direct 

3056 use of the baked query extension as well as its operation within 

3057 lazy loaders and eager loaders for relationships. In order to 

3058 re-establish the query being cached, apply the event adding the 

3059 ``bake_ok`` flag:: 

3060 

3061 @event.listens_for(Query, "before_compile", retval=True, bake_ok=True) 

3062 def my_event(query): 

3063 for desc in query.column_descriptions: 

3064 if desc["type"] is User: 

3065 entity = desc["entity"] 

3066 query = query.filter(entity.deleted == False) 

3067 return query 

3068 

3069 When ``bake_ok`` is set to True, the event hook will only be invoked 

3070 once, and not called for subsequent invocations of a particular query 

3071 that is being cached. 

3072 

3073 .. seealso:: 

3074 

3075 :meth:`.QueryEvents.before_compile_update` 

3076 

3077 :meth:`.QueryEvents.before_compile_delete` 

3078 

3079 :ref:`baked_with_before_compile` 

3080 

3081 """ # noqa: E501 

3082 

3083 def before_compile_update( 

3084 self, query: Query[Any], update_context: BulkUpdate 

3085 ) -> None: 

3086 """Allow modifications to the :class:`_query.Query` object within 

3087 :meth:`_query.Query.update`. 

3088 

3089 .. deprecated:: 1.4 The :meth:`_orm.QueryEvents.before_compile_update` 

3090 event is superseded by the much more capable 

3091 :meth:`_orm.SessionEvents.do_orm_execute` hook. 

3092 

3093 Like the :meth:`.QueryEvents.before_compile` event, if the event 

3094 is to be used to alter the :class:`_query.Query` object, it should 

3095 be configured with ``retval=True``, and the modified 

3096 :class:`_query.Query` object returned, as in :: 

3097 

3098 @event.listens_for(Query, "before_compile_update", retval=True) 

3099 def no_deleted(query, update_context): 

3100 for desc in query.column_descriptions: 

3101 if desc["type"] is User: 

3102 entity = desc["entity"] 

3103 query = query.filter(entity.deleted == False) 

3104 

3105 update_context.values["timestamp"] = datetime.datetime.now( 

3106 datetime.UTC 

3107 ) 

3108 return query 

3109 

3110 The ``.values`` dictionary of the "update context" object can also 

3111 be modified in place as illustrated above. 

3112 

3113 :param query: a :class:`_query.Query` instance; this is also 

3114 the ``.query`` attribute of the given "update context" 

3115 object. 

3116 

3117 :param update_context: an "update context" object which is 

3118 the same kind of object as described in 

3119 :paramref:`.QueryEvents.after_bulk_update.update_context`. 

3120 The object has a ``.values`` attribute in an UPDATE context which is 

3121 the dictionary of parameters passed to :meth:`_query.Query.update`. 

3122 This 

3123 dictionary can be modified to alter the VALUES clause of the 

3124 resulting UPDATE statement. 

3125 

3126 .. seealso:: 

3127 

3128 :meth:`.QueryEvents.before_compile` 

3129 

3130 :meth:`.QueryEvents.before_compile_delete` 

3131 

3132 

3133 """ # noqa: E501 

3134 

3135 def before_compile_delete( 

3136 self, query: Query[Any], delete_context: BulkDelete 

3137 ) -> None: 

3138 """Allow modifications to the :class:`_query.Query` object within 

3139 :meth:`_query.Query.delete`. 

3140 

3141 .. deprecated:: 1.4 The :meth:`_orm.QueryEvents.before_compile_delete` 

3142 event is superseded by the much more capable 

3143 :meth:`_orm.SessionEvents.do_orm_execute` hook. 

3144 

3145 Like the :meth:`.QueryEvents.before_compile` event, this event 

3146 should be configured with ``retval=True``, and the modified 

3147 :class:`_query.Query` object returned, as in :: 

3148 

3149 @event.listens_for(Query, "before_compile_delete", retval=True) 

3150 def no_deleted(query, delete_context): 

3151 for desc in query.column_descriptions: 

3152 if desc["type"] is User: 

3153 entity = desc["entity"] 

3154 query = query.filter(entity.deleted == False) 

3155 return query 

3156 

3157 :param query: a :class:`_query.Query` instance; this is also 

3158 the ``.query`` attribute of the given "delete context" 

3159 object. 

3160 

3161 :param delete_context: a "delete context" object which is 

3162 the same kind of object as described in 

3163 :paramref:`.QueryEvents.after_bulk_delete.delete_context`. 

3164 

3165 .. seealso:: 

3166 

3167 :meth:`.QueryEvents.before_compile` 

3168 

3169 :meth:`.QueryEvents.before_compile_update` 

3170 

3171 

3172 """ 

3173 

3174 @classmethod 

3175 def _listen( 

3176 cls, 

3177 event_key: _EventKey[_ET], 

3178 retval: bool = False, 

3179 bake_ok: bool = False, 

3180 **kw: Any, 

3181 ) -> None: 

3182 fn = event_key._listen_fn 

3183 

3184 if not retval: 

3185 

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

3187 if not retval: 

3188 query = arg[0] 

3189 fn(*arg, **kw) 

3190 return query 

3191 else: 

3192 return fn(*arg, **kw) 

3193 

3194 event_key = event_key.with_wrapper(wrap) 

3195 else: 

3196 # don't assume we can apply an attribute to the callable 

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

3198 return fn(*arg, **kw) 

3199 

3200 event_key = event_key.with_wrapper(wrap) 

3201 

3202 wrap._bake_ok = bake_ok # type: ignore [attr-defined] 

3203 

3204 event_key.base_listen(**kw) 

3205 

3206 

3207class RegistryEvents(event.Events["registry"]): 

3208 """Define events specific to :class:`_orm.registry` lifecycle. 

3209 

3210 The :class:`_orm.RegistryEvents` class defines events that are specific 

3211 to the lifecycle and operation of the :class:`_orm.registry` object. 

3212 

3213 e.g.:: 

3214 

3215 from typing import Any 

3216 

3217 from sqlalchemy import event 

3218 from sqlalchemy.orm import registry 

3219 from sqlalchemy.orm import TypeResolve 

3220 from sqlalchemy.types import TypeEngine 

3221 

3222 reg = registry() 

3223 

3224 

3225 @event.listens_for(reg, "resolve_type_annotation") 

3226 def resolve_custom_type( 

3227 resolve_type: TypeResolve, 

3228 ) -> TypeEngine[Any] | None: 

3229 if python_type is MyCustomType: 

3230 return MyCustomSQLType() 

3231 return None 

3232 

3233 The events defined by :class:`_orm.RegistryEvents` include 

3234 :meth:`_orm.RegistryEvents.resolve_type_annotation`, 

3235 :meth:`_orm.RegistryEvents.before_configured`, and 

3236 :meth:`_orm.RegistryEvents.after_configured`.`. These events may be 

3237 applied to a :class:`_orm.registry` object as shown in the preceding 

3238 example, as well as to a declarative base class directly, which will 

3239 automatically locate the registry for the event to be applied:: 

3240 

3241 from typing import Any 

3242 

3243 from sqlalchemy import event 

3244 from sqlalchemy.orm import DeclarativeBase 

3245 from sqlalchemy.orm import registry as RegistryType 

3246 from sqlalchemy.orm import TypeResolve 

3247 from sqlalchemy.types import TypeEngine 

3248 

3249 

3250 class Base(DeclarativeBase): 

3251 pass 

3252 

3253 

3254 @event.listens_for(Base, "resolve_type_annotation") 

3255 def resolve_custom_type( 

3256 resolve_type: TypeResolve, 

3257 ) -> TypeEngine[Any] | None: 

3258 if resolve_type.resolved_type is MyCustomType: 

3259 return MyCustomSQLType() 

3260 else: 

3261 return None 

3262 

3263 

3264 @event.listens_for(Base, "after_configured") 

3265 def after_base_configured(registry: RegistryType) -> None: 

3266 print(f"Registry {registry} fully configured") 

3267 

3268 .. versionadded:: 2.1 

3269 

3270 

3271 """ 

3272 

3273 _target_class_doc = "SomeRegistry" 

3274 _dispatch_target = decl_api.registry 

3275 

3276 @classmethod 

3277 def _accept_with( 

3278 cls, 

3279 target: Any, 

3280 identifier: str, 

3281 ) -> Any: 

3282 if isinstance(target, decl_api.registry): 

3283 return target 

3284 elif ( 

3285 isinstance(target, type) 

3286 and "_sa_registry" in target.__dict__ 

3287 and isinstance(target.__dict__["_sa_registry"], decl_api.registry) 

3288 ): 

3289 return target._sa_registry # type: ignore[attr-defined] 

3290 else: 

3291 return None 

3292 

3293 @classmethod 

3294 def _listen( 

3295 cls, 

3296 event_key: _EventKey["registry"], 

3297 **kw: Any, 

3298 ) -> None: 

3299 identifier = event_key.identifier 

3300 

3301 # Only resolve_type_annotation needs retval=True 

3302 if identifier == "resolve_type_annotation": 

3303 kw["retval"] = True 

3304 

3305 event_key.base_listen(**kw) 

3306 

3307 def resolve_type_annotation( 

3308 self, resolve_type: decl_api.TypeResolve 

3309 ) -> Optional[Any]: 

3310 """Intercept and customize type annotation resolution. 

3311 

3312 This event is fired when the :class:`_orm.registry` attempts to 

3313 resolve a Python type annotation to a SQLAlchemy type. This is 

3314 particularly useful for handling advanced typing scenarios such as 

3315 nested :pep:`695` type aliases. 

3316 

3317 The :meth:`.RegistryEvents.resolve_type_annotation` event automatically 

3318 sets up ``retval=True`` when the event is set up, so that implementing 

3319 functions may return a resolved type, or ``None`` to indicate no type 

3320 was resolved, and the default resolution for the type should proceed. 

3321 

3322 :param resolve_type: A :class:`_orm.TypeResolve` object which contains 

3323 all the relevant information about the type, including a link to the 

3324 registry and its resolver function. 

3325 

3326 :return: A SQLAlchemy type to use for the given Python type. If 

3327 ``None`` is returned, the default resolution behavior will proceed 

3328 from there. 

3329 

3330 .. versionadded:: 2.1 

3331 

3332 .. seealso:: 

3333 

3334 :ref:`orm_declarative_resolve_type_event` 

3335 

3336 """ 

3337 

3338 def before_configured(self, registry: "registry") -> None: 

3339 """Called before a series of mappers in this registry are configured. 

3340 

3341 This event is invoked each time the :func:`_orm.configure_mappers` 

3342 function is invoked and this registry has mappers that are part of 

3343 the configuration process. 

3344 

3345 Compared to the :meth:`.MapperEvents.before_configured` event hook, 

3346 this event is local to the mappers within a specific 

3347 :class:`_orm.registry` and not for all :class:`.Mapper` objects 

3348 globally. 

3349 

3350 :param registry: The :class:`_orm.registry` instance. 

3351 

3352 .. versionadded:: 2.1 

3353 

3354 .. seealso:: 

3355 

3356 :meth:`.RegistryEvents.after_configured` 

3357 

3358 :meth:`.MapperEvents.before_configured` 

3359 

3360 :meth:`.MapperEvents.after_configured` 

3361 

3362 """ 

3363 

3364 def after_configured(self, registry: "registry") -> None: 

3365 """Called after a series of mappers in this registry are configured. 

3366 

3367 This event is invoked each time the :func:`_orm.configure_mappers` 

3368 function completes and this registry had mappers that were part of 

3369 the configuration process. 

3370 

3371 Compared to the :meth:`.MapperEvents.after_configured` event hook, this 

3372 event is local to the mappers within a specific :class:`_orm.registry` 

3373 and not for all :class:`.Mapper` objects globally. 

3374 

3375 :param registry: The :class:`_orm.registry` instance. 

3376 

3377 .. versionadded:: 2.1 

3378 

3379 .. seealso:: 

3380 

3381 :meth:`.RegistryEvents.before_configured` 

3382 

3383 :meth:`.MapperEvents.before_configured` 

3384 

3385 :meth:`.MapperEvents.after_configured` 

3386 

3387 """