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

421 statements  

1# orm/events.py 

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

3# <see AUTHORS file> 

4# 

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

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

7 

8"""ORM event interfaces.""" 

9from __future__ import annotations 

10 

11from typing import Any 

12from typing import Callable 

13from typing import Collection 

14from typing import Dict 

15from typing import Generic 

16from typing import Iterable 

17from typing import Optional 

18from typing import Sequence 

19from typing import Set 

20from typing import Type 

21from typing import TYPE_CHECKING 

22from typing import TypeVar 

23from typing import Union 

24import weakref 

25 

26from . import instrumentation 

27from . import interfaces 

28from . import mapperlib 

29from .attributes import QueryableAttribute 

30from .base import _mapper_or_none 

31from .base import NO_KEY 

32from .instrumentation import ClassManager 

33from .instrumentation import InstrumentationFactory 

34from .query import BulkDelete 

35from .query import BulkUpdate 

36from .query import Query 

37from .scoping import scoped_session 

38from .session import Session 

39from .session import sessionmaker 

40from .. import event 

41from .. import exc 

42from .. import util 

43from ..event import EventTarget 

44from ..event.registry import _ET 

45from ..util.compat import inspect_getfullargspec 

46 

47if TYPE_CHECKING: 

48 from weakref import ReferenceType 

49 

50 from ._typing import _InstanceDict 

51 from ._typing import _InternalEntityType 

52 from ._typing import _O 

53 from ._typing import _T 

54 from .attributes import Event 

55 from .base import EventConstants 

56 from .session import ORMExecuteState 

57 from .session import SessionTransaction 

58 from .unitofwork import UOWTransaction 

59 from ..engine import Connection 

60 from ..event.base import _Dispatch 

61 from ..event.base import _HasEventsDispatch 

62 from ..event.registry import _EventKey 

63 from ..orm.collections import CollectionAdapter 

64 from ..orm.context import QueryContext 

65 from ..orm.decl_api import DeclarativeAttributeIntercept 

66 from ..orm.decl_api import DeclarativeMeta 

67 from ..orm.mapper import Mapper 

68 from ..orm.state import InstanceState 

69 

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

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

72 

73 

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

75 """Events related to class instrumentation events. 

76 

77 The listeners here support being established against 

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

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

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

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

82 of that class as well. 

83 

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

85 which when used has the effect of events being emitted 

86 for all classes. 

87 

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

89 unlike the other class level events where it defaults 

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

91 be the subject of these events, when a listener 

92 is established on a superclass. 

93 

94 """ 

95 

96 _target_class_doc = "SomeBaseClass" 

97 _dispatch_target = InstrumentationFactory 

98 

99 @classmethod 

100 def _accept_with( 

101 cls, 

102 target: Union[ 

103 InstrumentationFactory, 

104 Type[InstrumentationFactory], 

105 ], 

106 identifier: str, 

107 ) -> Optional[ 

108 Union[ 

109 InstrumentationFactory, 

110 Type[InstrumentationFactory], 

111 ] 

112 ]: 

113 if isinstance(target, type): 

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

115 else: 

116 return None 

117 

118 @classmethod 

119 def _listen( 

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

121 ) -> None: 

122 target, identifier, fn = ( 

123 event_key.dispatch_target, 

124 event_key.identifier, 

125 event_key._listen_fn, 

126 ) 

127 

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

129 listen_cls = target() 

130 

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

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

133 # between mapper/registry/instrumentation_manager, however this 

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

135 if listen_cls is None: 

136 return None 

137 

138 if propagate and issubclass(target_cls, listen_cls): 

139 return fn(target_cls, *arg) 

140 elif not propagate and target_cls is listen_cls: 

141 return fn(target_cls, *arg) 

142 else: 

143 return None 

144 

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

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

147 None, 

148 identifier, 

149 listen, 

150 instrumentation._instrumentation_factory, 

151 ) 

152 getattr( 

153 instrumentation._instrumentation_factory.dispatch, identifier 

154 ).remove(key) 

155 

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

157 

158 event_key.with_dispatch_target( 

159 instrumentation._instrumentation_factory 

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

161 

162 @classmethod 

163 def _clear(cls) -> None: 

164 super()._clear() 

165 instrumentation._instrumentation_factory.dispatch._clear() 

166 

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

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

169 

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

171 :func:`.manager_of_class`. 

172 

173 """ 

174 

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

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

177 

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

179 :func:`.manager_of_class`. 

180 

181 """ 

182 

183 def attribute_instrument( 

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

185 ) -> None: 

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

187 

188 

189class _InstrumentationEventsHold: 

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

191 _listen() on the InstrumentationEvents class. 

192 

193 """ 

194 

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

196 self.class_ = class_ 

197 

198 dispatch = event.dispatcher(InstrumentationEvents) 

199 

200 

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

202 """Define events specific to object lifecycle. 

203 

204 e.g.:: 

205 

206 from sqlalchemy import event 

207 

208 

209 def my_load_listener(target, context): 

210 print("on load!") 

211 

212 

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

214 

215 Available targets include: 

216 

217 * mapped classes 

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

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

220 * :class:`_orm.Mapper` objects 

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

222 mappers. 

223 

224 Instance events are closely related to mapper events, but 

225 are more specific to the instance and its instrumentation, 

226 rather than its system of persistence. 

227 

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

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

230 

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

232 be applied to all inheriting classes as well as the 

233 class which is the target of this listener. 

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

235 to applicable event listener functions will be the 

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

237 object, rather than the mapped instance itself. 

238 :param restore_load_context=False: Applies to the 

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

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

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

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

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

244 events if this flag is not set. 

245 

246 """ 

247 

248 _target_class_doc = "SomeClass" 

249 

250 _dispatch_target = ClassManager 

251 

252 @classmethod 

253 def _new_classmanager_instance( 

254 cls, 

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

256 classmanager: ClassManager[_O], 

257 ) -> None: 

258 _InstanceEventsHold.populate(class_, classmanager) 

259 

260 @classmethod 

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

262 def _accept_with( 

263 cls, 

264 target: Union[ 

265 ClassManager[Any], 

266 Type[ClassManager[Any]], 

267 ], 

268 identifier: str, 

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

270 orm = util.preloaded.orm 

271 

272 if isinstance(target, ClassManager): 

273 return target 

274 elif isinstance(target, mapperlib.Mapper): 

275 return target.class_manager 

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

277 util.warn_deprecated( 

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

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

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

281 "2.0", 

282 ) 

283 return ClassManager 

284 elif isinstance(target, type): 

285 if issubclass(target, mapperlib.Mapper): 

286 return ClassManager 

287 else: 

288 manager = instrumentation.opt_manager_of_class(target) 

289 if manager: 

290 return manager 

291 else: 

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

293 return None 

294 

295 @classmethod 

296 def _listen( 

297 cls, 

298 event_key: _EventKey[ClassManager[Any]], 

299 raw: bool = False, 

300 propagate: bool = False, 

301 restore_load_context: bool = False, 

302 **kw: Any, 

303 ) -> None: 

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

305 

306 if not raw or restore_load_context: 

307 

308 def wrap( 

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

310 ) -> Optional[Any]: 

311 if not raw: 

312 target: Any = state.obj() 

313 else: 

314 target = state 

315 if restore_load_context: 

316 runid = state.runid 

317 try: 

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

319 finally: 

320 if restore_load_context: 

321 state.runid = runid 

322 

323 event_key = event_key.with_wrapper(wrap) 

324 

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

326 

327 if propagate: 

328 for mgr in target.subclass_managers(True): 

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

330 

331 @classmethod 

332 def _clear(cls) -> None: 

333 super()._clear() 

334 _InstanceEventsHold._clear() 

335 

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

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

338 

339 This method is only called during a userland construction of 

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

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

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

343 event in order to intercept a database load. 

344 

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

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

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

348 ``__init__``. 

349 

350 :param target: the mapped instance. If 

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

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

353 object associated with the instance. 

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

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

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

357 This structure *can* be altered in place. 

358 

359 .. seealso:: 

360 

361 :meth:`.InstanceEvents.init_failure` 

362 

363 :meth:`.InstanceEvents.load` 

364 

365 """ 

366 

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

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

369 and raised an exception. 

370 

371 This method is only called during a userland construction of 

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

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

374 from the database. 

375 

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

377 method is caught. After the event 

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

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

380 actual exception and stack trace raised should be present in 

381 ``sys.exc_info()``. 

382 

383 :param target: the mapped instance. If 

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

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

386 object associated with the instance. 

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

388 method. 

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

390 method. 

391 

392 .. seealso:: 

393 

394 :meth:`.InstanceEvents.init` 

395 

396 :meth:`.InstanceEvents.load` 

397 

398 """ 

399 

400 def _sa_event_merge_wo_load( 

401 self, target: _O, context: QueryContext 

402 ) -> None: 

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

404 call, when load=False was passed. 

405 

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

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

408 overwrite operation does not use attribute events, instead just 

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

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

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

412 

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

414 

415 .. versionadded:: 1.4.41 

416 

417 """ 

418 

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

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

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

422 occurred. 

423 

424 This typically occurs when the instance is created based on 

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

426 instance's lifetime. 

427 

428 .. warning:: 

429 

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

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

432 eager loading with collection-oriented attributes, the additional 

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

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

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

436 if an operation occurs within this event handler that emits 

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

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

439 existing eager loaders still in progress. 

440 

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

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

443 

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

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

446 

447 * accessing attributes on a joined-inheritance subclass that 

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

449 

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

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

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

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

454 event is called:: 

455 

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

457 def on_load(instance, context): 

458 instance.some_unloaded_attribute 

459 

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

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

462 

463 :param target: the mapped instance. If 

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

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

466 object associated with the instance. 

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

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

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

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

471 

472 .. seealso:: 

473 

474 :ref:`mapped_class_load_events` 

475 

476 :meth:`.InstanceEvents.init` 

477 

478 :meth:`.InstanceEvents.refresh` 

479 

480 :meth:`.SessionEvents.loaded_as_persistent` 

481 

482 """ # noqa: E501 

483 

484 def refresh( 

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

486 ) -> None: 

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

488 been refreshed from a query. 

489 

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

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

492 

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

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

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

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

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

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

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

500 order to resolve this scenario. 

501 

502 :param target: the mapped instance. If 

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

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

505 object associated with the instance. 

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

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

508 :param attrs: sequence of attribute names which 

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

510 attributes were populated. 

511 

512 .. seealso:: 

513 

514 :ref:`mapped_class_load_events` 

515 

516 :meth:`.InstanceEvents.load` 

517 

518 """ 

519 

520 def refresh_flush( 

521 self, 

522 target: _O, 

523 flush_context: UOWTransaction, 

524 attrs: Optional[Iterable[str]], 

525 ) -> None: 

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

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

528 during persistence of the object's state. 

529 

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

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

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

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

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

535 

536 .. note:: 

537 

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

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

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

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

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

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

544 intercept the newly INSERTed state of an object, the 

545 :meth:`.SessionEvents.pending_to_persistent` and 

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

547 

548 :param target: the mapped instance. If 

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

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

551 object associated with the instance. 

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

553 which handles the details of the flush. 

554 :param attrs: sequence of attribute names which 

555 were populated. 

556 

557 .. seealso:: 

558 

559 :ref:`mapped_class_load_events` 

560 

561 :ref:`orm_server_defaults` 

562 

563 :ref:`metadata_defaults_toplevel` 

564 

565 """ 

566 

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

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

569 have been expired. 

570 

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

572 state was expired. 

573 

574 :param target: the mapped instance. If 

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

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

577 object associated with the instance. 

578 :param attrs: sequence of attribute 

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

580 expired. 

581 

582 """ 

583 

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

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

586 being pickled. 

587 

588 :param target: the mapped instance. If 

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

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

591 object associated with the instance. 

592 :param state_dict: the dictionary returned by 

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

594 to be pickled. 

595 

596 """ 

597 

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

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

600 been unpickled. 

601 

602 :param target: the mapped instance. If 

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

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

605 object associated with the instance. 

606 :param state_dict: the dictionary sent to 

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

608 dictionary which was pickled. 

609 

610 """ 

611 

612 

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

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

615 

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

617 those objects are created for that class. 

618 

619 """ 

620 

621 all_holds: weakref.WeakKeyDictionary[Any, Any] 

622 

623 def __init__( 

624 self, 

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

626 ) -> None: 

627 self.class_ = class_ 

628 

629 @classmethod 

630 def _clear(cls) -> None: 

631 cls.all_holds.clear() 

632 

633 class HoldEvents(Generic[_ET2]): 

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

635 

636 @classmethod 

637 def _listen( 

638 cls, 

639 event_key: _EventKey[_ET2], 

640 raw: bool = False, 

641 propagate: bool = False, 

642 retval: bool = False, 

643 **kw: Any, 

644 ) -> None: 

645 target = event_key.dispatch_target 

646 

647 if target.class_ in target.all_holds: 

648 collection = target.all_holds[target.class_] 

649 else: 

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

651 

652 event.registry._stored_in_collection(event_key, target) 

653 collection[event_key._key] = ( 

654 event_key, 

655 raw, 

656 propagate, 

657 retval, 

658 kw, 

659 ) 

660 

661 if propagate: 

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

663 while stack: 

664 subclass = stack.pop(0) 

665 stack.extend(subclass.__subclasses__()) 

666 subject = target.resolve(subclass) 

667 if subject is not None: 

668 # we are already going through __subclasses__() 

669 # so leave generic propagate flag False 

670 event_key.with_dispatch_target(subject).listen( 

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

672 ) 

673 

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

675 target = event_key.dispatch_target 

676 

677 if isinstance(target, _EventsHold): 

678 collection = target.all_holds[target.class_] 

679 del collection[event_key._key] 

680 

681 @classmethod 

682 def populate( 

683 cls, 

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

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

686 ) -> None: 

687 for subclass in class_.__mro__: 

688 if subclass in cls.all_holds: 

689 collection = cls.all_holds[subclass] 

690 for ( 

691 event_key, 

692 raw, 

693 propagate, 

694 retval, 

695 kw, 

696 ) in collection.values(): 

697 if propagate or subclass is class_: 

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

699 # classes in a hierarchy are triggered with 

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

701 # assignment, instead of using the generic propagate 

702 # flag. 

703 event_key.with_dispatch_target(subject).listen( 

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

705 ) 

706 

707 

708class _InstanceEventsHold(_EventsHold[_ET]): 

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

710 weakref.WeakKeyDictionary() 

711 ) 

712 

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

714 return instrumentation.opt_manager_of_class(class_) 

715 

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

717 pass 

718 

719 dispatch = event.dispatcher(HoldInstanceEvents) 

720 

721 

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

723 """Define events specific to mappings. 

724 

725 e.g.:: 

726 

727 from sqlalchemy import event 

728 

729 

730 def my_before_insert_listener(mapper, connection, target): 

731 # execute a stored procedure upon INSERT, 

732 # apply the value to the row to be inserted 

733 target.calculated_value = connection.execute( 

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

735 ).scalar() 

736 

737 

738 # associate the listener function with SomeClass, 

739 # to execute during the "before_insert" hook 

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

741 

742 Available targets include: 

743 

744 * mapped classes 

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

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

747 * :class:`_orm.Mapper` objects 

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

749 mappers. 

750 

751 Mapper events provide hooks into critical sections of the 

752 mapper, including those related to object instrumentation, 

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

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

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

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

757 methods operate with several significant restrictions. The 

758 user is encouraged to evaluate the 

759 :meth:`.SessionEvents.before_flush` and 

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

761 flexible and user-friendly hooks in which to apply 

762 additional database state during a flush. 

763 

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

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

766 

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

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

769 inheriting classes, as well as any 

770 mapper which is the target of this listener. 

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

772 to applicable event listener functions will be the 

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

774 object, rather than the mapped instance itself. 

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

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

777 control subsequent event propagation, or to otherwise alter 

778 the operation in progress by the mapper. Possible return 

779 values are: 

780 

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

782 processing normally. 

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

784 event handlers in the chain. 

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

786 

787 """ 

788 

789 _target_class_doc = "SomeClass" 

790 _dispatch_target = mapperlib.Mapper 

791 

792 @classmethod 

793 def _new_mapper_instance( 

794 cls, 

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

796 mapper: Mapper[_O], 

797 ) -> None: 

798 _MapperEventsHold.populate(class_, mapper) 

799 

800 @classmethod 

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

802 def _accept_with( 

803 cls, 

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

805 identifier: str, 

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

807 orm = util.preloaded.orm 

808 

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

810 util.warn_deprecated( 

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

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

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

814 "2.0", 

815 ) 

816 return mapperlib.Mapper 

817 elif isinstance(target, type): 

818 if issubclass(target, mapperlib.Mapper): 

819 return target 

820 else: 

821 mapper = _mapper_or_none(target) 

822 if mapper is not None: 

823 return mapper 

824 else: 

825 return _MapperEventsHold(target) 

826 else: 

827 return target 

828 

829 @classmethod 

830 def _listen( 

831 cls, 

832 event_key: _EventKey[_ET], 

833 raw: bool = False, 

834 retval: bool = False, 

835 propagate: bool = False, 

836 **kw: Any, 

837 ) -> None: 

838 target, identifier, fn = ( 

839 event_key.dispatch_target, 

840 event_key.identifier, 

841 event_key._listen_fn, 

842 ) 

843 

844 if ( 

845 identifier in ("before_configured", "after_configured") 

846 and target is not mapperlib.Mapper 

847 ): 

848 util.warn( 

849 "'before_configured' and 'after_configured' ORM events " 

850 "only invoke with the Mapper class " 

851 "as the target." 

852 ) 

853 

854 if not raw or not retval: 

855 if not raw: 

856 meth = getattr(cls, identifier) 

857 try: 

858 target_index = ( 

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

860 ) 

861 except ValueError: 

862 target_index = None 

863 

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

865 if not raw and target_index is not None: 

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

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

868 if not retval: 

869 fn(*arg, **kw) 

870 return interfaces.EXT_CONTINUE 

871 else: 

872 return fn(*arg, **kw) 

873 

874 event_key = event_key.with_wrapper(wrap) 

875 

876 if propagate: 

877 for mapper in target.self_and_descendants: 

878 event_key.with_dispatch_target(mapper).base_listen( 

879 propagate=True, **kw 

880 ) 

881 else: 

882 event_key.base_listen(**kw) 

883 

884 @classmethod 

885 def _clear(cls) -> None: 

886 super()._clear() 

887 _MapperEventsHold._clear() 

888 

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

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

891 before instrumentation is applied to the mapped class. 

892 

893 This event is the earliest phase of mapper construction. 

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

895 receive an event within initial mapper construction where basic 

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

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

898 be a better choice. 

899 

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

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

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

903 

904 Base = declarative_base() 

905 

906 

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

908 def on_new_class(mapper, cls_): 

909 "..." 

910 

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

912 of this event. 

913 :param class\_: the mapped class. 

914 

915 .. seealso:: 

916 

917 :meth:`_orm.MapperEvents.after_mapper_constructed` 

918 

919 """ 

920 

921 def after_mapper_constructed( 

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

923 ) -> None: 

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

925 fully constructed. 

926 

927 This event is called after the initial constructor for 

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

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

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

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

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

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

934 

935 This event differs from the 

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

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

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

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

940 wish to create additional mapped classes in response to the 

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

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

943 

944 .. versionadded:: 2.0.2 

945 

946 .. seealso:: 

947 

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

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

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

951 objects. 

952 

953 """ 

954 

955 def before_mapper_configured( 

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

957 ) -> None: 

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

959 

960 This event is intended to allow a specific mapper to be skipped during 

961 the configure step, by returning the :attr:`.orm.interfaces.EXT_SKIP` 

962 symbol which indicates to the :func:`.configure_mappers` call that this 

963 particular mapper (or hierarchy of mappers, if ``propagate=True`` is 

964 used) should be skipped in the current configuration run. When one or 

965 more mappers are skipped, the "new mappers" flag will remain set, 

966 meaning the :func:`.configure_mappers` function will continue to be 

967 called when mappers are used, to continue to try to configure all 

968 available mappers. 

969 

970 In comparison to the other configure-level events, 

971 :meth:`.MapperEvents.before_configured`, 

972 :meth:`.MapperEvents.after_configured`, and 

973 :meth:`.MapperEvents.mapper_configured`, the 

974 :meth:`.MapperEvents.before_mapper_configured` event provides for a 

975 meaningful return value when it is registered with the ``retval=True`` 

976 parameter. 

977 

978 e.g.:: 

979 

980 from sqlalchemy.orm import EXT_SKIP 

981 

982 Base = declarative_base() 

983 

984 DontConfigureBase = declarative_base() 

985 

986 

987 @event.listens_for( 

988 DontConfigureBase, 

989 "before_mapper_configured", 

990 retval=True, 

991 propagate=True, 

992 ) 

993 def dont_configure(mapper, cls): 

994 return EXT_SKIP 

995 

996 .. seealso:: 

997 

998 :meth:`.MapperEvents.before_configured` 

999 

1000 :meth:`.MapperEvents.after_configured` 

1001 

1002 :meth:`.MapperEvents.mapper_configured` 

1003 

1004 """ 

1005 

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

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

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

1009 

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

1011 for each mapper that is encountered when the 

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

1013 list of not-yet-configured mappers. 

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

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

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

1017 detected. 

1018 

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

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

1021 other mappers; they might still be pending within the 

1022 configuration operation. Bidirectional relationships that 

1023 are instead configured via the 

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

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

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

1027 exist. 

1028 

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

1030 to go including backrefs that are defined only on other 

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

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

1033 fully configured. 

1034 

1035 The :meth:`.MapperEvents.mapper_configured` event, unlike 

1036 :meth:`.MapperEvents.before_configured` or 

1037 :meth:`.MapperEvents.after_configured`, 

1038 is called for each mapper/class individually, and the mapper is 

1039 passed to the event itself. It also is called exactly once for 

1040 a particular mapper. The event is therefore useful for 

1041 configurational steps that benefit from being invoked just once 

1042 on a specific mapper basis, which don't require that "backref" 

1043 configurations are necessarily ready yet. 

1044 

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

1046 of this event. 

1047 :param class\_: the mapped class. 

1048 

1049 .. seealso:: 

1050 

1051 :meth:`.MapperEvents.before_configured` 

1052 

1053 :meth:`.MapperEvents.after_configured` 

1054 

1055 :meth:`.MapperEvents.before_mapper_configured` 

1056 

1057 """ 

1058 # TODO: need coverage for this event 

1059 

1060 def before_configured(self) -> None: 

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

1062 

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

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

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

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

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

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

1069 detected. 

1070 

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

1072 and not to individual mappings or mapped classes. It is only invoked 

1073 for all mappings as a whole:: 

1074 

1075 from sqlalchemy.orm import Mapper 

1076 

1077 

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

1079 def go(): ... 

1080 

1081 Contrast this event to :meth:`.MapperEvents.after_configured`, 

1082 which is invoked after the series of mappers has been configured, 

1083 as well as :meth:`.MapperEvents.before_mapper_configured` 

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

1085 on a per-mapper basis. 

1086 

1087 Theoretically this event is called once per 

1088 application, but is actually called any time new mappers 

1089 are to be affected by a :func:`_orm.configure_mappers` 

1090 call. If new mappings are constructed after existing ones have 

1091 already been used, this event will likely be called again. To ensure 

1092 that a particular event is only called once and no further, the 

1093 ``once=True`` argument (new in 0.9.4) can be applied:: 

1094 

1095 from sqlalchemy.orm import mapper 

1096 

1097 

1098 @event.listens_for(mapper, "before_configured", once=True) 

1099 def go(): ... 

1100 

1101 .. seealso:: 

1102 

1103 :meth:`.MapperEvents.before_mapper_configured` 

1104 

1105 :meth:`.MapperEvents.mapper_configured` 

1106 

1107 :meth:`.MapperEvents.after_configured` 

1108 

1109 """ 

1110 

1111 def after_configured(self) -> None: 

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

1113 

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

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

1116 invoked, after the function has completed its work. 

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

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

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

1120 detected. 

1121 

1122 Contrast this event to the :meth:`.MapperEvents.mapper_configured` 

1123 event, which is called on a per-mapper basis while the configuration 

1124 operation proceeds; unlike that event, when this event is invoked, 

1125 all cross-configurations (e.g. backrefs) will also have been made 

1126 available for any mappers that were pending. 

1127 Also contrast to :meth:`.MapperEvents.before_configured`, 

1128 which is invoked before the series of mappers has been configured. 

1129 

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

1131 and not to individual mappings or 

1132 mapped classes. It is only invoked for all mappings as a whole:: 

1133 

1134 from sqlalchemy.orm import Mapper 

1135 

1136 

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

1138 def go(): ... 

1139 

1140 Theoretically this event is called once per 

1141 application, but is actually called any time new mappers 

1142 have been affected by a :func:`_orm.configure_mappers` 

1143 call. If new mappings are constructed after existing ones have 

1144 already been used, this event will likely be called again. To ensure 

1145 that a particular event is only called once and no further, the 

1146 ``once=True`` argument (new in 0.9.4) can be applied:: 

1147 

1148 from sqlalchemy.orm import mapper 

1149 

1150 

1151 @event.listens_for(mapper, "after_configured", once=True) 

1152 def go(): ... 

1153 

1154 .. seealso:: 

1155 

1156 :meth:`.MapperEvents.before_mapper_configured` 

1157 

1158 :meth:`.MapperEvents.mapper_configured` 

1159 

1160 :meth:`.MapperEvents.before_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 class HoldMapperEvents(_EventsHold.HoldEvents[_ET], MapperEvents): # type: ignore [misc] # noqa: E501 

1523 pass 

1524 

1525 dispatch = event.dispatcher(HoldMapperEvents) 

1526 

1527 

1528_sessionevents_lifecycle_event_names: Set[str] = set() 

1529 

1530 

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

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

1533 

1534 e.g.:: 

1535 

1536 from sqlalchemy import event 

1537 from sqlalchemy.orm import sessionmaker 

1538 

1539 

1540 def my_before_commit(session): 

1541 print("before commit!") 

1542 

1543 

1544 Session = sessionmaker() 

1545 

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

1547 

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

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

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

1551 

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

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

1554 globally. 

1555 

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

1557 to applicable event listener functions that work on individual 

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

1559 object, rather than the mapped instance itself. 

1560 

1561 :param restore_load_context=False: Applies to the 

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

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

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

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

1566 within this event if this flag is not set. 

1567 

1568 """ 

1569 

1570 _target_class_doc = "SomeSessionClassOrObject" 

1571 

1572 _dispatch_target = Session 

1573 

1574 def _lifecycle_event( # type: ignore [misc] 

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

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

1577 _sessionevents_lifecycle_event_names.add(fn.__name__) 

1578 return fn 

1579 

1580 @classmethod 

1581 def _accept_with( # type: ignore [return] 

1582 cls, target: Any, identifier: str 

1583 ) -> Union[Session, type]: 

1584 if isinstance(target, scoped_session): 

1585 target = target.session_factory 

1586 if not isinstance(target, sessionmaker) and ( 

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

1588 ): 

1589 raise exc.ArgumentError( 

1590 "Session event listen on a scoped_session " 

1591 "requires that its creation callable " 

1592 "is associated with the Session class." 

1593 ) 

1594 

1595 if isinstance(target, sessionmaker): 

1596 return target.class_ 

1597 elif isinstance(target, type): 

1598 if issubclass(target, scoped_session): 

1599 return Session 

1600 elif issubclass(target, Session): 

1601 return target 

1602 elif isinstance(target, Session): 

1603 return target 

1604 elif hasattr(target, "_no_async_engine_events"): 

1605 target._no_async_engine_events() 

1606 else: 

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

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

1609 

1610 @classmethod 

1611 def _listen( 

1612 cls, 

1613 event_key: Any, 

1614 *, 

1615 raw: bool = False, 

1616 restore_load_context: bool = False, 

1617 **kw: Any, 

1618 ) -> None: 

1619 is_instance_event = ( 

1620 event_key.identifier in _sessionevents_lifecycle_event_names 

1621 ) 

1622 

1623 if is_instance_event: 

1624 if not raw or restore_load_context: 

1625 fn = event_key._listen_fn 

1626 

1627 def wrap( 

1628 session: Session, 

1629 state: InstanceState[_O], 

1630 *arg: Any, 

1631 **kw: Any, 

1632 ) -> Optional[Any]: 

1633 if not raw: 

1634 target = state.obj() 

1635 if target is None: 

1636 # existing behavior is that if the object is 

1637 # garbage collected, no event is emitted 

1638 return None 

1639 else: 

1640 target = state # type: ignore [assignment] 

1641 if restore_load_context: 

1642 runid = state.runid 

1643 try: 

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

1645 finally: 

1646 if restore_load_context: 

1647 state.runid = runid 

1648 

1649 event_key = event_key.with_wrapper(wrap) 

1650 

1651 event_key.base_listen(**kw) 

1652 

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

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

1655 ORM :class:`.Session` object. 

1656 

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

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

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

1660 SQLAlchemy 1.4, all ORM queries that run through the 

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

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

1663 will participate in this event. 

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

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

1666 process described at :ref:`session_flushing`. 

1667 

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

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

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

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

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

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

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

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

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

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

1678 :meth:`.ConnectionEvents.before_execute` and 

1679 :meth:`.ConnectionEvents.before_cursor_execute`. 

1680 

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

1682 emitted internally within the ORM flush process, 

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

1684 intercept steps within the flush process, see the event 

1685 hooks described at :ref:`session_persistence_events` as 

1686 well as :ref:`session_persistence_mapper`. 

1687 

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

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

1690 performs. The intended use for this includes sharding and 

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

1692 across multiple database connections, returning a result that is 

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

1694 instead returning data from a cache. 

1695 

1696 The hook intends to replace the use of the 

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

1698 to SQLAlchemy 1.4. 

1699 

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

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

1702 as helper functions used to derive other commonly required 

1703 information. See that object for details. 

1704 

1705 .. seealso:: 

1706 

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

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

1709 

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

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

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

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

1714 and parameters as well as an option that allows programmatic 

1715 invocation of the statement at any point. 

1716 

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

1718 :meth:`_orm.SessionEvents.do_orm_execute` 

1719 

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

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

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

1723 

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

1725 extension relies upon the 

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

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

1728 

1729 

1730 .. versionadded:: 1.4 

1731 

1732 """ 

1733 

1734 def after_transaction_create( 

1735 self, session: Session, transaction: SessionTransaction 

1736 ) -> None: 

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

1738 

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

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

1741 overall, as opposed to when transactions are begun 

1742 on individual database connections. It is also invoked 

1743 for nested transactions and subtransactions, and is always 

1744 matched by a corresponding 

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

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

1747 

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

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

1750 

1751 To detect if this is the outermost 

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

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

1754 is ``None``:: 

1755 

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

1757 def after_transaction_create(session, transaction): 

1758 if transaction.parent is None: 

1759 ... # work with top-level transaction 

1760 

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

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

1763 

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

1765 def after_transaction_create(session, transaction): 

1766 if transaction.nested: 

1767 ... # work with SAVEPOINT transaction 

1768 

1769 .. seealso:: 

1770 

1771 :class:`.SessionTransaction` 

1772 

1773 :meth:`~.SessionEvents.after_transaction_end` 

1774 

1775 """ 

1776 

1777 def after_transaction_end( 

1778 self, session: Session, transaction: SessionTransaction 

1779 ) -> None: 

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

1781 

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

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

1784 objects in use, including those for nested transactions 

1785 and subtransactions, and is always matched by a corresponding 

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

1787 

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

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

1790 

1791 To detect if this is the outermost 

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

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

1794 is ``None``:: 

1795 

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

1797 def after_transaction_end(session, transaction): 

1798 if transaction.parent is None: 

1799 ... # work with top-level transaction 

1800 

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

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

1803 

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

1805 def after_transaction_end(session, transaction): 

1806 if transaction.nested: 

1807 ... # work with SAVEPOINT transaction 

1808 

1809 .. seealso:: 

1810 

1811 :class:`.SessionTransaction` 

1812 

1813 :meth:`~.SessionEvents.after_transaction_create` 

1814 

1815 """ 

1816 

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

1818 """Execute before commit is called. 

1819 

1820 .. note:: 

1821 

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

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

1824 many times within the scope of a transaction. 

1825 For interception of these events, use the 

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

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

1828 :meth:`~.SessionEvents.after_flush_postexec` 

1829 events. 

1830 

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

1832 

1833 .. seealso:: 

1834 

1835 :meth:`~.SessionEvents.after_commit` 

1836 

1837 :meth:`~.SessionEvents.after_begin` 

1838 

1839 :meth:`~.SessionEvents.after_transaction_create` 

1840 

1841 :meth:`~.SessionEvents.after_transaction_end` 

1842 

1843 """ 

1844 

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

1846 """Execute after a commit has occurred. 

1847 

1848 .. note:: 

1849 

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

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

1852 many times within the scope of a transaction. 

1853 For interception of these events, use the 

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

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

1856 :meth:`~.SessionEvents.after_flush_postexec` 

1857 events. 

1858 

1859 .. note:: 

1860 

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

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

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

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

1865 event. 

1866 

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

1868 

1869 .. seealso:: 

1870 

1871 :meth:`~.SessionEvents.before_commit` 

1872 

1873 :meth:`~.SessionEvents.after_begin` 

1874 

1875 :meth:`~.SessionEvents.after_transaction_create` 

1876 

1877 :meth:`~.SessionEvents.after_transaction_end` 

1878 

1879 """ 

1880 

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

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

1883 

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

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

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

1887 DBAPI transaction has already been rolled back. In many 

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

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

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

1891 which is active after the outermost rollback has proceeded, 

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

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

1894 

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

1896 

1897 """ 

1898 

1899 def after_soft_rollback( 

1900 self, session: Session, previous_transaction: SessionTransaction 

1901 ) -> None: 

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

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

1904 

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

1906 the innermost rollback that calls the DBAPI's 

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

1908 calls that only pop themselves from the transaction stack. 

1909 

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

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

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

1913 

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

1915 def do_something(session, previous_transaction): 

1916 if session.is_active: 

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

1918 

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

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

1921 transactional marker object which was just closed. The current 

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

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

1924 

1925 """ 

1926 

1927 def before_flush( 

1928 self, 

1929 session: Session, 

1930 flush_context: UOWTransaction, 

1931 instances: Optional[Sequence[_O]], 

1932 ) -> None: 

1933 """Execute before flush process has started. 

1934 

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

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

1937 which handles the details of the flush. 

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

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

1940 (note this usage is deprecated). 

1941 

1942 .. seealso:: 

1943 

1944 :meth:`~.SessionEvents.after_flush` 

1945 

1946 :meth:`~.SessionEvents.after_flush_postexec` 

1947 

1948 :ref:`session_persistence_events` 

1949 

1950 """ 

1951 

1952 def after_flush( 

1953 self, session: Session, flush_context: UOWTransaction 

1954 ) -> None: 

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

1956 called. 

1957 

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

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

1960 as the history settings on instance attributes. 

1961 

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

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

1964 internal state to reflect those changes, including that newly 

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

1966 emitted within this event such as loads of related items 

1967 may produce new identity map entries that will immediately 

1968 be replaced, sometimes causing confusing results. SQLAlchemy will 

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

1970 

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

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

1973 which handles the details of the flush. 

1974 

1975 .. seealso:: 

1976 

1977 :meth:`~.SessionEvents.before_flush` 

1978 

1979 :meth:`~.SessionEvents.after_flush_postexec` 

1980 

1981 :ref:`session_persistence_events` 

1982 

1983 """ 

1984 

1985 def after_flush_postexec( 

1986 self, session: Session, flush_context: UOWTransaction 

1987 ) -> None: 

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

1989 state occurs. 

1990 

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

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

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

1994 transaction or participated in a larger transaction. 

1995 

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

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

1998 which handles the details of the flush. 

1999 

2000 

2001 .. seealso:: 

2002 

2003 :meth:`~.SessionEvents.before_flush` 

2004 

2005 :meth:`~.SessionEvents.after_flush` 

2006 

2007 :ref:`session_persistence_events` 

2008 

2009 """ 

2010 

2011 def after_begin( 

2012 self, 

2013 session: Session, 

2014 transaction: SessionTransaction, 

2015 connection: Connection, 

2016 ) -> None: 

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

2018 

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

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

2021 To invoke SQL operations within this hook, use the 

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

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

2024 directly. 

2025 

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

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

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

2029 which will be used for SQL statements. 

2030 

2031 .. seealso:: 

2032 

2033 :meth:`~.SessionEvents.before_commit` 

2034 

2035 :meth:`~.SessionEvents.after_commit` 

2036 

2037 :meth:`~.SessionEvents.after_transaction_create` 

2038 

2039 :meth:`~.SessionEvents.after_transaction_end` 

2040 

2041 """ 

2042 

2043 @_lifecycle_event 

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

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

2046 

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

2048 the object to be part of the session. 

2049 

2050 .. seealso:: 

2051 

2052 :meth:`~.SessionEvents.after_attach` 

2053 

2054 :ref:`session_lifecycle_events` 

2055 

2056 """ 

2057 

2058 @_lifecycle_event 

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

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

2061 

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

2063 

2064 .. note:: 

2065 

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

2067 has been fully associated with the session, which is 

2068 different than previous releases. For event 

2069 handlers that require the object not yet 

2070 be part of session state (such as handlers which 

2071 may autoflush while the target object is not 

2072 yet complete) consider the 

2073 new :meth:`.before_attach` event. 

2074 

2075 .. seealso:: 

2076 

2077 :meth:`~.SessionEvents.before_attach` 

2078 

2079 :ref:`session_lifecycle_events` 

2080 

2081 """ 

2082 

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

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

2085 has been called. 

2086 

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

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

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

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

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

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

2093 these calls. 

2094 

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

2096 details about the update, including these attributes: 

2097 

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

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

2100 object that this update operation 

2101 was called upon. 

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

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

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

2105 returned as a result of the 

2106 bulk UPDATE operation. 

2107 

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

2109 ``QueryContext`` object associated with it. 

2110 

2111 .. seealso:: 

2112 

2113 :meth:`.QueryEvents.before_compile_update` 

2114 

2115 :meth:`.SessionEvents.after_bulk_delete` 

2116 

2117 """ 

2118 

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

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

2121 has been called. 

2122 

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

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

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

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

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

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

2129 these calls. 

2130 

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

2132 details about the update, including these attributes: 

2133 

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

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

2136 object that this update operation 

2137 was called upon. 

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

2139 returned as a result of the 

2140 bulk DELETE operation. 

2141 

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

2143 ``QueryContext`` object associated with it. 

2144 

2145 .. seealso:: 

2146 

2147 :meth:`.QueryEvents.before_compile_delete` 

2148 

2149 :meth:`.SessionEvents.after_bulk_update` 

2150 

2151 """ 

2152 

2153 @_lifecycle_event 

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

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

2156 object. 

2157 

2158 This event is a specialization of the 

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

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

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

2162 

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

2164 

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

2166 

2167 .. seealso:: 

2168 

2169 :ref:`session_lifecycle_events` 

2170 

2171 """ 

2172 

2173 @_lifecycle_event 

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

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

2176 object. 

2177 

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

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

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

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

2182 

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

2184 

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

2186 

2187 .. seealso:: 

2188 

2189 :ref:`session_lifecycle_events` 

2190 

2191 """ 

2192 

2193 @_lifecycle_event 

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

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

2196 object. 

2197 

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

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

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

2201 

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

2203 

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

2205 

2206 .. seealso:: 

2207 

2208 :ref:`session_lifecycle_events` 

2209 

2210 """ 

2211 

2212 @_lifecycle_event 

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

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

2215 object. 

2216 

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

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

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

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

2221 when the event is called. 

2222 

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

2224 

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

2226 

2227 .. seealso:: 

2228 

2229 :ref:`session_lifecycle_events` 

2230 

2231 """ 

2232 

2233 @_lifecycle_event 

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

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

2236 object. 

2237 

2238 This event is a specialization of the 

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

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

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

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

2243 associated with the 

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

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

2246 

2247 .. note:: 

2248 

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

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

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

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

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

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

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

2256 objects need to be intercepted before the flush. 

2257 

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

2259 

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

2261 

2262 .. seealso:: 

2263 

2264 :ref:`session_lifecycle_events` 

2265 

2266 """ 

2267 

2268 @_lifecycle_event 

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

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

2271 object. 

2272 

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

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

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

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

2277 with the other session lifecycle events smoothly. The object 

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

2279 this event is called. 

2280 

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

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

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

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

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

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

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

2288 works in the same manner as that of 

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

2290 resolve this scenario. 

2291 

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

2293 

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

2295 

2296 .. seealso:: 

2297 

2298 :ref:`session_lifecycle_events` 

2299 

2300 """ 

2301 

2302 @_lifecycle_event 

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

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

2305 object. 

2306 

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

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

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

2310 transaction completes. 

2311 

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

2313 to the persistent state, and the 

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

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

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

2317 event. 

2318 

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

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

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

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

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

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

2325 invoked at the end of a flush. 

2326 

2327 .. seealso:: 

2328 

2329 :ref:`session_lifecycle_events` 

2330 

2331 """ 

2332 

2333 @_lifecycle_event 

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

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

2336 object. 

2337 

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

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

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

2341 any other circumstances. 

2342 

2343 .. seealso:: 

2344 

2345 :ref:`session_lifecycle_events` 

2346 

2347 """ 

2348 

2349 @_lifecycle_event 

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

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

2352 object. 

2353 

2354 This event is invoked when a deleted object is evicted 

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

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

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

2358 state to the detached state. 

2359 

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

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

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

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

2364 

2365 .. seealso:: 

2366 

2367 :ref:`session_lifecycle_events` 

2368 

2369 """ 

2370 

2371 @_lifecycle_event 

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

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

2374 object. 

2375 

2376 This event is invoked when a persistent object is evicted 

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

2378 to happen, including: 

2379 

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

2381 or :meth:`.Session.close` 

2382 

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

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

2385 

2386 

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

2388 

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

2390 

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

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

2393 

2394 

2395 .. seealso:: 

2396 

2397 :ref:`session_lifecycle_events` 

2398 

2399 """ 

2400 

2401 

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

2403 r"""Define events for object attributes. 

2404 

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

2406 target class. 

2407 

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

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

2410 

2411 from sqlalchemy import event 

2412 

2413 

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

2415 def my_append_listener(target, value, initiator): 

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

2417 

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

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

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

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

2422 

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

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

2425 

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

2427 

2428 

2429 # setup listener on UserContact.phone attribute, instructing 

2430 # it to use the return value 

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

2432 

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

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

2435 

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

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

2438 as when using mapper inheritance patterns:: 

2439 

2440 

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

2442 def receive_set(target, value, initiator): 

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

2444 

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

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

2447 

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

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

2450 replaced unconditionally, even if this requires firing off 

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

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

2453 :func:`_orm.relationship`. 

2454 

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

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

2457 for attributes of the same name on all current subclasses 

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

2459 class, using an additional listener that listens for 

2460 instrumentation events. 

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

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

2463 object, rather than the mapped instance itself. 

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

2465 listening must return the "value" argument from the 

2466 function. This gives the listening function the opportunity 

2467 to change the value that is ultimately used for a "set" 

2468 or "append" event. 

2469 

2470 """ 

2471 

2472 _target_class_doc = "SomeClass.some_attribute" 

2473 _dispatch_target = QueryableAttribute 

2474 

2475 @staticmethod 

2476 def _set_dispatch( 

2477 cls: Type[_HasEventsDispatch[Any]], dispatch_cls: Type[_Dispatch[Any]] 

2478 ) -> _Dispatch[Any]: 

2479 dispatch = event.Events._set_dispatch(cls, dispatch_cls) 

2480 dispatch_cls._active_history = False 

2481 return dispatch 

2482 

2483 @classmethod 

2484 def _accept_with( 

2485 cls, 

2486 target: Union[QueryableAttribute[Any], Type[QueryableAttribute[Any]]], 

2487 identifier: str, 

2488 ) -> Union[QueryableAttribute[Any], Type[QueryableAttribute[Any]]]: 

2489 # TODO: coverage 

2490 if isinstance(target, interfaces.MapperProperty): 

2491 return getattr(target.parent.class_, target.key) 

2492 else: 

2493 return target 

2494 

2495 @classmethod 

2496 def _listen( # type: ignore [override] 

2497 cls, 

2498 event_key: _EventKey[QueryableAttribute[Any]], 

2499 active_history: bool = False, 

2500 raw: bool = False, 

2501 retval: bool = False, 

2502 propagate: bool = False, 

2503 include_key: bool = False, 

2504 ) -> None: 

2505 target, fn = event_key.dispatch_target, event_key._listen_fn 

2506 

2507 if active_history: 

2508 target.dispatch._active_history = True 

2509 

2510 if not raw or not retval or not include_key: 

2511 

2512 def wrap(target: InstanceState[_O], *arg: Any, **kw: Any) -> Any: 

2513 if not raw: 

2514 target = target.obj() # type: ignore [assignment] 

2515 if not retval: 

2516 if arg: 

2517 value = arg[0] 

2518 else: 

2519 value = None 

2520 if include_key: 

2521 fn(target, *arg, **kw) 

2522 else: 

2523 fn(target, *arg) 

2524 return value 

2525 else: 

2526 if include_key: 

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

2528 else: 

2529 return fn(target, *arg) 

2530 

2531 event_key = event_key.with_wrapper(wrap) 

2532 

2533 event_key.base_listen(propagate=propagate) 

2534 

2535 if propagate: 

2536 manager = instrumentation.manager_of_class(target.class_) 

2537 

2538 for mgr in manager.subclass_managers(True): # type: ignore [no-untyped-call] # noqa: E501 

2539 event_key.with_dispatch_target(mgr[target.key]).base_listen( 

2540 propagate=True 

2541 ) 

2542 if active_history: 

2543 mgr[target.key].dispatch._active_history = True 

2544 

2545 def append( 

2546 self, 

2547 target: _O, 

2548 value: _T, 

2549 initiator: Event, 

2550 *, 

2551 key: EventConstants = NO_KEY, 

2552 ) -> Optional[_T]: 

2553 """Receive a collection append event. 

2554 

2555 The append event is invoked for each element as it is appended 

2556 to the collection. This occurs for single-item appends as well 

2557 as for a "bulk replace" operation. 

2558 

2559 :param target: the object instance receiving the event. 

2560 If the listener is registered with ``raw=True``, this will 

2561 be the :class:`.InstanceState` object. 

2562 :param value: the value being appended. If this listener 

2563 is registered with ``retval=True``, the listener 

2564 function must return this value, or a new value which 

2565 replaces it. 

2566 :param initiator: An instance of :class:`.attributes.Event` 

2567 representing the initiation of the event. May be modified 

2568 from its original value by backref handlers in order to control 

2569 chained event propagation, as well as be inspected for information 

2570 about the source of the event. 

2571 :param key: When the event is established using the 

2572 :paramref:`.AttributeEvents.include_key` parameter set to 

2573 True, this will be the key used in the operation, such as 

2574 ``collection[some_key_or_index] = value``. 

2575 The parameter is not passed 

2576 to the event at all if the the 

2577 :paramref:`.AttributeEvents.include_key` 

2578 was not used to set up the event; this is to allow backwards 

2579 compatibility with existing event handlers that don't include the 

2580 ``key`` parameter. 

2581 

2582 .. versionadded:: 2.0 

2583 

2584 :return: if the event was registered with ``retval=True``, 

2585 the given value, or a new effective value, should be returned. 

2586 

2587 .. seealso:: 

2588 

2589 :class:`.AttributeEvents` - background on listener options such 

2590 as propagation to subclasses. 

2591 

2592 :meth:`.AttributeEvents.bulk_replace` 

2593 

2594 """ 

2595 

2596 def append_wo_mutation( 

2597 self, 

2598 target: _O, 

2599 value: _T, 

2600 initiator: Event, 

2601 *, 

2602 key: EventConstants = NO_KEY, 

2603 ) -> None: 

2604 """Receive a collection append event where the collection was not 

2605 actually mutated. 

2606 

2607 This event differs from :meth:`_orm.AttributeEvents.append` in that 

2608 it is fired off for de-duplicating collections such as sets and 

2609 dictionaries, when the object already exists in the target collection. 

2610 The event does not have a return value and the identity of the 

2611 given object cannot be changed. 

2612 

2613 The event is used for cascading objects into a :class:`_orm.Session` 

2614 when the collection has already been mutated via a backref event. 

2615 

2616 :param target: the object instance receiving the event. 

2617 If the listener is registered with ``raw=True``, this will 

2618 be the :class:`.InstanceState` object. 

2619 :param value: the value that would be appended if the object did not 

2620 already exist in the collection. 

2621 :param initiator: An instance of :class:`.attributes.Event` 

2622 representing the initiation of the event. May be modified 

2623 from its original value by backref handlers in order to control 

2624 chained event propagation, as well as be inspected for information 

2625 about the source of the event. 

2626 :param key: When the event is established using the 

2627 :paramref:`.AttributeEvents.include_key` parameter set to 

2628 True, this will be the key used in the operation, such as 

2629 ``collection[some_key_or_index] = value``. 

2630 The parameter is not passed 

2631 to the event at all if the the 

2632 :paramref:`.AttributeEvents.include_key` 

2633 was not used to set up the event; this is to allow backwards 

2634 compatibility with existing event handlers that don't include the 

2635 ``key`` parameter. 

2636 

2637 .. versionadded:: 2.0 

2638 

2639 :return: No return value is defined for this event. 

2640 

2641 .. versionadded:: 1.4.15 

2642 

2643 """ 

2644 

2645 def bulk_replace( 

2646 self, 

2647 target: _O, 

2648 values: Iterable[_T], 

2649 initiator: Event, 

2650 *, 

2651 keys: Optional[Iterable[EventConstants]] = None, 

2652 ) -> None: 

2653 """Receive a collection 'bulk replace' event. 

2654 

2655 This event is invoked for a sequence of values as they are incoming 

2656 to a bulk collection set operation, which can be 

2657 modified in place before the values are treated as ORM objects. 

2658 This is an "early hook" that runs before the bulk replace routine 

2659 attempts to reconcile which objects are already present in the 

2660 collection and which are being removed by the net replace operation. 

2661 

2662 It is typical that this method be combined with use of the 

2663 :meth:`.AttributeEvents.append` event. When using both of these 

2664 events, note that a bulk replace operation will invoke 

2665 the :meth:`.AttributeEvents.append` event for all new items, 

2666 even after :meth:`.AttributeEvents.bulk_replace` has been invoked 

2667 for the collection as a whole. In order to determine if an 

2668 :meth:`.AttributeEvents.append` event is part of a bulk replace, 

2669 use the symbol :attr:`~.attributes.OP_BULK_REPLACE` to test the 

2670 incoming initiator:: 

2671 

2672 from sqlalchemy.orm.attributes import OP_BULK_REPLACE 

2673 

2674 

2675 @event.listens_for(SomeObject.collection, "bulk_replace") 

2676 def process_collection(target, values, initiator): 

2677 values[:] = [_make_value(value) for value in values] 

2678 

2679 

2680 @event.listens_for(SomeObject.collection, "append", retval=True) 

2681 def process_collection(target, value, initiator): 

2682 # make sure bulk_replace didn't already do it 

2683 if initiator is None or initiator.op is not OP_BULK_REPLACE: 

2684 return _make_value(value) 

2685 else: 

2686 return value 

2687 

2688 :param target: the object instance receiving the event. 

2689 If the listener is registered with ``raw=True``, this will 

2690 be the :class:`.InstanceState` object. 

2691 :param value: a sequence (e.g. a list) of the values being set. The 

2692 handler can modify this list in place. 

2693 :param initiator: An instance of :class:`.attributes.Event` 

2694 representing the initiation of the event. 

2695 :param keys: When the event is established using the 

2696 :paramref:`.AttributeEvents.include_key` parameter set to 

2697 True, this will be the sequence of keys used in the operation, 

2698 typically only for a dictionary update. The parameter is not passed 

2699 to the event at all if the the 

2700 :paramref:`.AttributeEvents.include_key` 

2701 was not used to set up the event; this is to allow backwards 

2702 compatibility with existing event handlers that don't include the 

2703 ``key`` parameter. 

2704 

2705 .. versionadded:: 2.0 

2706 

2707 .. seealso:: 

2708 

2709 :class:`.AttributeEvents` - background on listener options such 

2710 as propagation to subclasses. 

2711 

2712 

2713 """ 

2714 

2715 def remove( 

2716 self, 

2717 target: _O, 

2718 value: _T, 

2719 initiator: Event, 

2720 *, 

2721 key: EventConstants = NO_KEY, 

2722 ) -> None: 

2723 """Receive a collection remove event. 

2724 

2725 :param target: the object instance receiving the event. 

2726 If the listener is registered with ``raw=True``, this will 

2727 be the :class:`.InstanceState` object. 

2728 :param value: the value being removed. 

2729 :param initiator: An instance of :class:`.attributes.Event` 

2730 representing the initiation of the event. May be modified 

2731 from its original value by backref handlers in order to control 

2732 chained event propagation. 

2733 

2734 :param key: When the event is established using the 

2735 :paramref:`.AttributeEvents.include_key` parameter set to 

2736 True, this will be the key used in the operation, such as 

2737 ``del collection[some_key_or_index]``. The parameter is not passed 

2738 to the event at all if the the 

2739 :paramref:`.AttributeEvents.include_key` 

2740 was not used to set up the event; this is to allow backwards 

2741 compatibility with existing event handlers that don't include the 

2742 ``key`` parameter. 

2743 

2744 .. versionadded:: 2.0 

2745 

2746 :return: No return value is defined for this event. 

2747 

2748 

2749 .. seealso:: 

2750 

2751 :class:`.AttributeEvents` - background on listener options such 

2752 as propagation to subclasses. 

2753 

2754 """ 

2755 

2756 def set( 

2757 self, target: _O, value: _T, oldvalue: _T, initiator: Event 

2758 ) -> None: 

2759 """Receive a scalar set event. 

2760 

2761 :param target: the object instance receiving the event. 

2762 If the listener is registered with ``raw=True``, this will 

2763 be the :class:`.InstanceState` object. 

2764 :param value: the value being set. If this listener 

2765 is registered with ``retval=True``, the listener 

2766 function must return this value, or a new value which 

2767 replaces it. 

2768 :param oldvalue: the previous value being replaced. This 

2769 may also be the symbol ``NEVER_SET`` or ``NO_VALUE``. 

2770 If the listener is registered with ``active_history=True``, 

2771 the previous value of the attribute will be loaded from 

2772 the database if the existing value is currently unloaded 

2773 or expired. 

2774 :param initiator: An instance of :class:`.attributes.Event` 

2775 representing the initiation of the event. May be modified 

2776 from its original value by backref handlers in order to control 

2777 chained event propagation. 

2778 

2779 :return: if the event was registered with ``retval=True``, 

2780 the given value, or a new effective value, should be returned. 

2781 

2782 .. seealso:: 

2783 

2784 :class:`.AttributeEvents` - background on listener options such 

2785 as propagation to subclasses. 

2786 

2787 """ 

2788 

2789 def init_scalar( 

2790 self, target: _O, value: _T, dict_: Dict[Any, Any] 

2791 ) -> None: 

2792 r"""Receive a scalar "init" event. 

2793 

2794 This event is invoked when an uninitialized, unpersisted scalar 

2795 attribute is accessed, e.g. read:: 

2796 

2797 

2798 x = my_object.some_attribute 

2799 

2800 The ORM's default behavior when this occurs for an un-initialized 

2801 attribute is to return the value ``None``; note this differs from 

2802 Python's usual behavior of raising ``AttributeError``. The 

2803 event here can be used to customize what value is actually returned, 

2804 with the assumption that the event listener would be mirroring 

2805 a default generator that is configured on the Core 

2806 :class:`_schema.Column` 

2807 object as well. 

2808 

2809 Since a default generator on a :class:`_schema.Column` 

2810 might also produce 

2811 a changing value such as a timestamp, the 

2812 :meth:`.AttributeEvents.init_scalar` 

2813 event handler can also be used to **set** the newly returned value, so 

2814 that a Core-level default generation function effectively fires off 

2815 only once, but at the moment the attribute is accessed on the 

2816 non-persisted object. Normally, no change to the object's state 

2817 is made when an uninitialized attribute is accessed (much older 

2818 SQLAlchemy versions did in fact change the object's state). 

2819 

2820 If a default generator on a column returned a particular constant, 

2821 a handler might be used as follows:: 

2822 

2823 SOME_CONSTANT = 3.1415926 

2824 

2825 

2826 class MyClass(Base): 

2827 # ... 

2828 

2829 some_attribute = Column(Numeric, default=SOME_CONSTANT) 

2830 

2831 

2832 @event.listens_for( 

2833 MyClass.some_attribute, "init_scalar", retval=True, propagate=True 

2834 ) 

2835 def _init_some_attribute(target, dict_, value): 

2836 dict_["some_attribute"] = SOME_CONSTANT 

2837 return SOME_CONSTANT 

2838 

2839 Above, we initialize the attribute ``MyClass.some_attribute`` to the 

2840 value of ``SOME_CONSTANT``. The above code includes the following 

2841 features: 

2842 

2843 * By setting the value ``SOME_CONSTANT`` in the given ``dict_``, 

2844 we indicate that this value is to be persisted to the database. 

2845 This supersedes the use of ``SOME_CONSTANT`` in the default generator 

2846 for the :class:`_schema.Column`. The ``active_column_defaults.py`` 

2847 example given at :ref:`examples_instrumentation` illustrates using 

2848 the same approach for a changing default, e.g. a timestamp 

2849 generator. In this particular example, it is not strictly 

2850 necessary to do this since ``SOME_CONSTANT`` would be part of the 

2851 INSERT statement in either case. 

2852 

2853 * By establishing the ``retval=True`` flag, the value we return 

2854 from the function will be returned by the attribute getter. 

2855 Without this flag, the event is assumed to be a passive observer 

2856 and the return value of our function is ignored. 

2857 

2858 * The ``propagate=True`` flag is significant if the mapped class 

2859 includes inheriting subclasses, which would also make use of this 

2860 event listener. Without this flag, an inheriting subclass will 

2861 not use our event handler. 

2862 

2863 In the above example, the attribute set event 

2864 :meth:`.AttributeEvents.set` as well as the related validation feature 

2865 provided by :obj:`_orm.validates` is **not** invoked when we apply our 

2866 value to the given ``dict_``. To have these events to invoke in 

2867 response to our newly generated value, apply the value to the given 

2868 object as a normal attribute set operation:: 

2869 

2870 SOME_CONSTANT = 3.1415926 

2871 

2872 

2873 @event.listens_for( 

2874 MyClass.some_attribute, "init_scalar", retval=True, propagate=True 

2875 ) 

2876 def _init_some_attribute(target, dict_, value): 

2877 # will also fire off attribute set events 

2878 target.some_attribute = SOME_CONSTANT 

2879 return SOME_CONSTANT 

2880 

2881 When multiple listeners are set up, the generation of the value 

2882 is "chained" from one listener to the next by passing the value 

2883 returned by the previous listener that specifies ``retval=True`` 

2884 as the ``value`` argument of the next listener. 

2885 

2886 :param target: the object instance receiving the event. 

2887 If the listener is registered with ``raw=True``, this will 

2888 be the :class:`.InstanceState` object. 

2889 :param value: the value that is to be returned before this event 

2890 listener were invoked. This value begins as the value ``None``, 

2891 however will be the return value of the previous event handler 

2892 function if multiple listeners are present. 

2893 :param dict\_: the attribute dictionary of this mapped object. 

2894 This is normally the ``__dict__`` of the object, but in all cases 

2895 represents the destination that the attribute system uses to get 

2896 at the actual value of this attribute. Placing the value in this 

2897 dictionary has the effect that the value will be used in the 

2898 INSERT statement generated by the unit of work. 

2899 

2900 

2901 .. seealso:: 

2902 

2903 :meth:`.AttributeEvents.init_collection` - collection version 

2904 of this event 

2905 

2906 :class:`.AttributeEvents` - background on listener options such 

2907 as propagation to subclasses. 

2908 

2909 :ref:`examples_instrumentation` - see the 

2910 ``active_column_defaults.py`` example. 

2911 

2912 """ # noqa: E501 

2913 

2914 def init_collection( 

2915 self, 

2916 target: _O, 

2917 collection: Type[Collection[Any]], 

2918 collection_adapter: CollectionAdapter, 

2919 ) -> None: 

2920 """Receive a 'collection init' event. 

2921 

2922 This event is triggered for a collection-based attribute, when 

2923 the initial "empty collection" is first generated for a blank 

2924 attribute, as well as for when the collection is replaced with 

2925 a new one, such as via a set event. 

2926 

2927 E.g., given that ``User.addresses`` is a relationship-based 

2928 collection, the event is triggered here:: 

2929 

2930 u1 = User() 

2931 u1.addresses.append(a1) # <- new collection 

2932 

2933 and also during replace operations:: 

2934 

2935 u1.addresses = [a2, a3] # <- new collection 

2936 

2937 :param target: the object instance receiving the event. 

2938 If the listener is registered with ``raw=True``, this will 

2939 be the :class:`.InstanceState` object. 

2940 :param collection: the new collection. This will always be generated 

2941 from what was specified as 

2942 :paramref:`_orm.relationship.collection_class`, and will always 

2943 be empty. 

2944 :param collection_adapter: the :class:`.CollectionAdapter` that will 

2945 mediate internal access to the collection. 

2946 

2947 .. seealso:: 

2948 

2949 :class:`.AttributeEvents` - background on listener options such 

2950 as propagation to subclasses. 

2951 

2952 :meth:`.AttributeEvents.init_scalar` - "scalar" version of this 

2953 event. 

2954 

2955 """ 

2956 

2957 def dispose_collection( 

2958 self, 

2959 target: _O, 

2960 collection: Collection[Any], 

2961 collection_adapter: CollectionAdapter, 

2962 ) -> None: 

2963 """Receive a 'collection dispose' event. 

2964 

2965 This event is triggered for a collection-based attribute when 

2966 a collection is replaced, that is:: 

2967 

2968 u1.addresses.append(a1) 

2969 

2970 u1.addresses = [a2, a3] # <- old collection is disposed 

2971 

2972 The old collection received will contain its previous contents. 

2973 

2974 .. seealso:: 

2975 

2976 :class:`.AttributeEvents` - background on listener options such 

2977 as propagation to subclasses. 

2978 

2979 """ 

2980 

2981 def modified(self, target: _O, initiator: Event) -> None: 

2982 """Receive a 'modified' event. 

2983 

2984 This event is triggered when the :func:`.attributes.flag_modified` 

2985 function is used to trigger a modify event on an attribute without 

2986 any specific value being set. 

2987 

2988 :param target: the object instance receiving the event. 

2989 If the listener is registered with ``raw=True``, this will 

2990 be the :class:`.InstanceState` object. 

2991 

2992 :param initiator: An instance of :class:`.attributes.Event` 

2993 representing the initiation of the event. 

2994 

2995 .. seealso:: 

2996 

2997 :class:`.AttributeEvents` - background on listener options such 

2998 as propagation to subclasses. 

2999 

3000 """ 

3001 

3002 

3003class QueryEvents(event.Events[Query[Any]]): 

3004 """Represent events within the construction of a :class:`_query.Query` 

3005 object. 

3006 

3007 .. legacy:: The :class:`_orm.QueryEvents` event methods are legacy 

3008 as of SQLAlchemy 2.0, and only apply to direct use of the 

3009 :class:`_orm.Query` object. They are not used for :term:`2.0 style` 

3010 statements. For events to intercept and modify 2.0 style ORM use, 

3011 use the :meth:`_orm.SessionEvents.do_orm_execute` hook. 

3012 

3013 

3014 The :class:`_orm.QueryEvents` hooks are now superseded by the 

3015 :meth:`_orm.SessionEvents.do_orm_execute` event hook. 

3016 

3017 """ 

3018 

3019 _target_class_doc = "SomeQuery" 

3020 _dispatch_target = Query 

3021 

3022 def before_compile(self, query: Query[Any]) -> None: 

3023 """Receive the :class:`_query.Query` 

3024 object before it is composed into a 

3025 core :class:`_expression.Select` object. 

3026 

3027 .. deprecated:: 1.4 The :meth:`_orm.QueryEvents.before_compile` event 

3028 is superseded by the much more capable 

3029 :meth:`_orm.SessionEvents.do_orm_execute` hook. In version 1.4, 

3030 the :meth:`_orm.QueryEvents.before_compile` event is **no longer 

3031 used** for ORM-level attribute loads, such as loads of deferred 

3032 or expired attributes as well as relationship loaders. See the 

3033 new examples in :ref:`examples_session_orm_events` which 

3034 illustrate new ways of intercepting and modifying ORM queries 

3035 for the most common purpose of adding arbitrary filter criteria. 

3036 

3037 

3038 This event is intended to allow changes to the query given:: 

3039 

3040 @event.listens_for(Query, "before_compile", retval=True) 

3041 def no_deleted(query): 

3042 for desc in query.column_descriptions: 

3043 if desc["type"] is User: 

3044 entity = desc["entity"] 

3045 query = query.filter(entity.deleted == False) 

3046 return query 

3047 

3048 The event should normally be listened with the ``retval=True`` 

3049 parameter set, so that the modified query may be returned. 

3050 

3051 The :meth:`.QueryEvents.before_compile` event by default 

3052 will disallow "baked" queries from caching a query, if the event 

3053 hook returns a new :class:`_query.Query` object. 

3054 This affects both direct 

3055 use of the baked query extension as well as its operation within 

3056 lazy loaders and eager loaders for relationships. In order to 

3057 re-establish the query being cached, apply the event adding the 

3058 ``bake_ok`` flag:: 

3059 

3060 @event.listens_for(Query, "before_compile", retval=True, bake_ok=True) 

3061 def my_event(query): 

3062 for desc in query.column_descriptions: 

3063 if desc["type"] is User: 

3064 entity = desc["entity"] 

3065 query = query.filter(entity.deleted == False) 

3066 return query 

3067 

3068 When ``bake_ok`` is set to True, the event hook will only be invoked 

3069 once, and not called for subsequent invocations of a particular query 

3070 that is being cached. 

3071 

3072 .. seealso:: 

3073 

3074 :meth:`.QueryEvents.before_compile_update` 

3075 

3076 :meth:`.QueryEvents.before_compile_delete` 

3077 

3078 :ref:`baked_with_before_compile` 

3079 

3080 """ # noqa: E501 

3081 

3082 def before_compile_update( 

3083 self, query: Query[Any], update_context: BulkUpdate 

3084 ) -> None: 

3085 """Allow modifications to the :class:`_query.Query` object within 

3086 :meth:`_query.Query.update`. 

3087 

3088 .. deprecated:: 1.4 The :meth:`_orm.QueryEvents.before_compile_update` 

3089 event is superseded by the much more capable 

3090 :meth:`_orm.SessionEvents.do_orm_execute` hook. 

3091 

3092 Like the :meth:`.QueryEvents.before_compile` event, if the event 

3093 is to be used to alter the :class:`_query.Query` object, it should 

3094 be configured with ``retval=True``, and the modified 

3095 :class:`_query.Query` object returned, as in :: 

3096 

3097 @event.listens_for(Query, "before_compile_update", retval=True) 

3098 def no_deleted(query, update_context): 

3099 for desc in query.column_descriptions: 

3100 if desc["type"] is User: 

3101 entity = desc["entity"] 

3102 query = query.filter(entity.deleted == False) 

3103 

3104 update_context.values["timestamp"] = datetime.datetime.now( 

3105 datetime.UTC 

3106 ) 

3107 return query 

3108 

3109 The ``.values`` dictionary of the "update context" object can also 

3110 be modified in place as illustrated above. 

3111 

3112 :param query: a :class:`_query.Query` instance; this is also 

3113 the ``.query`` attribute of the given "update context" 

3114 object. 

3115 

3116 :param update_context: an "update context" object which is 

3117 the same kind of object as described in 

3118 :paramref:`.QueryEvents.after_bulk_update.update_context`. 

3119 The object has a ``.values`` attribute in an UPDATE context which is 

3120 the dictionary of parameters passed to :meth:`_query.Query.update`. 

3121 This 

3122 dictionary can be modified to alter the VALUES clause of the 

3123 resulting UPDATE statement. 

3124 

3125 .. seealso:: 

3126 

3127 :meth:`.QueryEvents.before_compile` 

3128 

3129 :meth:`.QueryEvents.before_compile_delete` 

3130 

3131 

3132 """ # noqa: E501 

3133 

3134 def before_compile_delete( 

3135 self, query: Query[Any], delete_context: BulkDelete 

3136 ) -> None: 

3137 """Allow modifications to the :class:`_query.Query` object within 

3138 :meth:`_query.Query.delete`. 

3139 

3140 .. deprecated:: 1.4 The :meth:`_orm.QueryEvents.before_compile_delete` 

3141 event is superseded by the much more capable 

3142 :meth:`_orm.SessionEvents.do_orm_execute` hook. 

3143 

3144 Like the :meth:`.QueryEvents.before_compile` event, this event 

3145 should be configured with ``retval=True``, and the modified 

3146 :class:`_query.Query` object returned, as in :: 

3147 

3148 @event.listens_for(Query, "before_compile_delete", retval=True) 

3149 def no_deleted(query, delete_context): 

3150 for desc in query.column_descriptions: 

3151 if desc["type"] is User: 

3152 entity = desc["entity"] 

3153 query = query.filter(entity.deleted == False) 

3154 return query 

3155 

3156 :param query: a :class:`_query.Query` instance; this is also 

3157 the ``.query`` attribute of the given "delete context" 

3158 object. 

3159 

3160 :param delete_context: a "delete context" object which is 

3161 the same kind of object as described in 

3162 :paramref:`.QueryEvents.after_bulk_delete.delete_context`. 

3163 

3164 .. seealso:: 

3165 

3166 :meth:`.QueryEvents.before_compile` 

3167 

3168 :meth:`.QueryEvents.before_compile_update` 

3169 

3170 

3171 """ 

3172 

3173 @classmethod 

3174 def _listen( 

3175 cls, 

3176 event_key: _EventKey[_ET], 

3177 retval: bool = False, 

3178 bake_ok: bool = False, 

3179 **kw: Any, 

3180 ) -> None: 

3181 fn = event_key._listen_fn 

3182 

3183 if not retval: 

3184 

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

3186 if not retval: 

3187 query = arg[0] 

3188 fn(*arg, **kw) 

3189 return query 

3190 else: 

3191 return fn(*arg, **kw) 

3192 

3193 event_key = event_key.with_wrapper(wrap) 

3194 else: 

3195 # don't assume we can apply an attribute to the callable 

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

3197 return fn(*arg, **kw) 

3198 

3199 event_key = event_key.with_wrapper(wrap) 

3200 

3201 wrap._bake_ok = bake_ok # type: ignore [attr-defined] 

3202 

3203 event_key.base_listen(**kw)