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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

424 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 .. versionadded:: 1.3.14 

247 

248 

249 """ 

250 

251 _target_class_doc = "SomeClass" 

252 

253 _dispatch_target = ClassManager 

254 

255 @classmethod 

256 def _new_classmanager_instance( 

257 cls, 

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

259 classmanager: ClassManager[_O], 

260 ) -> None: 

261 _InstanceEventsHold.populate(class_, classmanager) 

262 

263 @classmethod 

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

265 def _accept_with( 

266 cls, 

267 target: Union[ 

268 ClassManager[Any], 

269 Type[ClassManager[Any]], 

270 ], 

271 identifier: str, 

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

273 orm = util.preloaded.orm 

274 

275 if isinstance(target, ClassManager): 

276 return target 

277 elif isinstance(target, mapperlib.Mapper): 

278 return target.class_manager 

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

280 util.warn_deprecated( 

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

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

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

284 "2.0", 

285 ) 

286 return ClassManager 

287 elif isinstance(target, type): 

288 if issubclass(target, mapperlib.Mapper): 

289 return ClassManager 

290 else: 

291 manager = instrumentation.opt_manager_of_class(target) 

292 if manager: 

293 return manager 

294 else: 

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

296 return None 

297 

298 @classmethod 

299 def _listen( 

300 cls, 

301 event_key: _EventKey[ClassManager[Any]], 

302 raw: bool = False, 

303 propagate: bool = False, 

304 restore_load_context: bool = False, 

305 **kw: Any, 

306 ) -> None: 

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

308 

309 if not raw or restore_load_context: 

310 

311 def wrap( 

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

313 ) -> Optional[Any]: 

314 if not raw: 

315 target: Any = state.obj() 

316 else: 

317 target = state 

318 if restore_load_context: 

319 runid = state.runid 

320 try: 

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

322 finally: 

323 if restore_load_context: 

324 state.runid = runid 

325 

326 event_key = event_key.with_wrapper(wrap) 

327 

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

329 

330 if propagate: 

331 for mgr in target.subclass_managers(True): 

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

333 

334 @classmethod 

335 def _clear(cls) -> None: 

336 super()._clear() 

337 _InstanceEventsHold._clear() 

338 

339 def first_init(self, manager: ClassManager[_O], cls: Type[_O]) -> None: 

340 """Called when the first instance of a particular mapping is called. 

341 

342 This event is called when the ``__init__`` method of a class 

343 is called the first time for that particular class. The event 

344 invokes before ``__init__`` actually proceeds as well as before 

345 the :meth:`.InstanceEvents.init` event is invoked. 

346 

347 """ 

348 

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

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

351 

352 This method is only called during a userland construction of 

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

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

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

356 event in order to intercept a database load. 

357 

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

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

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

361 ``__init__``. 

362 

363 :param target: the mapped instance. If 

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

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

366 object associated with the instance. 

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

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

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

370 This structure *can* be altered in place. 

371 

372 .. seealso:: 

373 

374 :meth:`.InstanceEvents.init_failure` 

375 

376 :meth:`.InstanceEvents.load` 

377 

378 """ 

379 

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

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

382 and raised an exception. 

383 

384 This method is only called during a userland construction of 

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

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

387 from the database. 

388 

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

390 method is caught. After the event 

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

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

393 actual exception and stack trace raised should be present in 

394 ``sys.exc_info()``. 

395 

396 :param target: the mapped instance. If 

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

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

399 object associated with the instance. 

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

401 method. 

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

403 method. 

404 

405 .. seealso:: 

406 

407 :meth:`.InstanceEvents.init` 

408 

409 :meth:`.InstanceEvents.load` 

410 

411 """ 

412 

413 def _sa_event_merge_wo_load( 

414 self, target: _O, context: QueryContext 

415 ) -> None: 

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

417 call, when load=False was passed. 

418 

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

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

421 overwrite operation does not use attribute events, instead just 

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

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

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

425 

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

427 

428 .. versionadded:: 1.4.41 

429 

430 """ 

431 

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

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

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

435 occurred. 

436 

437 This typically occurs when the instance is created based on 

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

439 instance's lifetime. 

440 

441 .. warning:: 

442 

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

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

445 eager loading with collection-oriented attributes, the additional 

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

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

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

449 if an operation occurs within this event handler that emits 

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

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

452 existing eager loaders still in progress. 

453 

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

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

456 

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

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

459 

460 * accessing attributes on a joined-inheritance subclass that 

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

462 

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

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

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

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

467 event is called:: 

468 

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

470 def on_load(instance, context): 

471 instance.some_unloaded_attribute 

472 

473 .. versionchanged:: 1.3.14 Added 

474 :paramref:`.InstanceEvents.restore_load_context` 

475 and :paramref:`.SessionEvents.restore_load_context` flags which 

476 apply to "on load" events, which will ensure that the loading 

477 context for an object is restored when the event hook is 

478 complete; a warning is emitted if the load context of the object 

479 changes without this flag being set. 

480 

481 

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

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

484 

485 :param target: the mapped instance. If 

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

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

488 object associated with the instance. 

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

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

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

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

493 

494 .. seealso:: 

495 

496 :ref:`mapped_class_load_events` 

497 

498 :meth:`.InstanceEvents.init` 

499 

500 :meth:`.InstanceEvents.refresh` 

501 

502 :meth:`.SessionEvents.loaded_as_persistent` 

503 

504 """ # noqa: E501 

505 

506 def refresh( 

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

508 ) -> None: 

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

510 been refreshed from a query. 

511 

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

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

514 

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

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

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

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

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

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

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

522 order to resolve this scenario. 

523 

524 :param target: the mapped instance. If 

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

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

527 object associated with the instance. 

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

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

530 :param attrs: sequence of attribute names which 

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

532 attributes were populated. 

533 

534 .. seealso:: 

535 

536 :ref:`mapped_class_load_events` 

537 

538 :meth:`.InstanceEvents.load` 

539 

540 """ 

541 

542 def refresh_flush( 

543 self, 

544 target: _O, 

545 flush_context: UOWTransaction, 

546 attrs: Optional[Iterable[str]], 

547 ) -> None: 

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

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

550 during persistence of the object's state. 

551 

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

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

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

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

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

557 

558 .. note:: 

559 

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

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

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

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

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

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

566 intercept the newly INSERTed state of an object, the 

567 :meth:`.SessionEvents.pending_to_persistent` and 

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

569 

570 :param target: the mapped instance. If 

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

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

573 object associated with the instance. 

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

575 which handles the details of the flush. 

576 :param attrs: sequence of attribute names which 

577 were populated. 

578 

579 .. seealso:: 

580 

581 :ref:`mapped_class_load_events` 

582 

583 :ref:`orm_server_defaults` 

584 

585 :ref:`metadata_defaults_toplevel` 

586 

587 """ 

588 

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

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

591 have been expired. 

592 

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

594 state was expired. 

595 

596 :param target: the mapped instance. If 

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

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

599 object associated with the instance. 

600 :param attrs: sequence of attribute 

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

602 expired. 

603 

604 """ 

605 

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

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

608 being pickled. 

609 

610 :param target: the mapped instance. If 

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

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

613 object associated with the instance. 

614 :param state_dict: the dictionary returned by 

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

616 to be pickled. 

617 

618 """ 

619 

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

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

622 been unpickled. 

623 

624 :param target: the mapped instance. If 

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

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

627 object associated with the instance. 

628 :param state_dict: the dictionary sent to 

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

630 dictionary which was pickled. 

631 

632 """ 

633 

634 

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

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

637 

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

639 those objects are created for that class. 

640 

641 """ 

642 

643 all_holds: weakref.WeakKeyDictionary[Any, Any] 

644 

645 def __init__( 

646 self, 

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

648 ) -> None: 

649 self.class_ = class_ 

650 

651 @classmethod 

652 def _clear(cls) -> None: 

653 cls.all_holds.clear() 

654 

655 class HoldEvents(Generic[_ET2]): 

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

657 

658 @classmethod 

659 def _listen( 

660 cls, 

661 event_key: _EventKey[_ET2], 

662 raw: bool = False, 

663 propagate: bool = False, 

664 retval: bool = False, 

665 **kw: Any, 

666 ) -> None: 

667 target = event_key.dispatch_target 

668 

669 if target.class_ in target.all_holds: 

670 collection = target.all_holds[target.class_] 

671 else: 

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

673 

674 event.registry._stored_in_collection(event_key, target) 

675 collection[event_key._key] = ( 

676 event_key, 

677 raw, 

678 propagate, 

679 retval, 

680 kw, 

681 ) 

682 

683 if propagate: 

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

685 while stack: 

686 subclass = stack.pop(0) 

687 stack.extend(subclass.__subclasses__()) 

688 subject = target.resolve(subclass) 

689 if subject is not None: 

690 # we are already going through __subclasses__() 

691 # so leave generic propagate flag False 

692 event_key.with_dispatch_target(subject).listen( 

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

694 ) 

695 

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

697 target = event_key.dispatch_target 

698 

699 if isinstance(target, _EventsHold): 

700 collection = target.all_holds[target.class_] 

701 del collection[event_key._key] 

702 

703 @classmethod 

704 def populate( 

705 cls, 

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

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

708 ) -> None: 

709 for subclass in class_.__mro__: 

710 if subclass in cls.all_holds: 

711 collection = cls.all_holds[subclass] 

712 for ( 

713 event_key, 

714 raw, 

715 propagate, 

716 retval, 

717 kw, 

718 ) in collection.values(): 

719 if propagate or subclass is class_: 

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

721 # classes in a hierarchy are triggered with 

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

723 # assignment, instead of using the generic propagate 

724 # flag. 

725 event_key.with_dispatch_target(subject).listen( 

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

727 ) 

728 

729 

730class _InstanceEventsHold(_EventsHold[_ET]): 

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

732 weakref.WeakKeyDictionary() 

733 ) 

734 

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

736 return instrumentation.opt_manager_of_class(class_) 

737 

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

739 pass 

740 

741 dispatch = event.dispatcher(HoldInstanceEvents) 

742 

743 

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

745 """Define events specific to mappings. 

746 

747 e.g.:: 

748 

749 from sqlalchemy import event 

750 

751 

752 def my_before_insert_listener(mapper, connection, target): 

753 # execute a stored procedure upon INSERT, 

754 # apply the value to the row to be inserted 

755 target.calculated_value = connection.execute( 

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

757 ).scalar() 

758 

759 

760 # associate the listener function with SomeClass, 

761 # to execute during the "before_insert" hook 

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

763 

764 Available targets include: 

765 

766 * mapped classes 

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

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

769 * :class:`_orm.Mapper` objects 

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

771 mappers. 

772 

773 Mapper events provide hooks into critical sections of the 

774 mapper, including those related to object instrumentation, 

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

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

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

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

779 methods operate with several significant restrictions. The 

780 user is encouraged to evaluate the 

781 :meth:`.SessionEvents.before_flush` and 

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

783 flexible and user-friendly hooks in which to apply 

784 additional database state during a flush. 

785 

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

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

788 

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

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

791 inheriting classes, as well as any 

792 mapper which is the target of this listener. 

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

794 to applicable event listener functions will be the 

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

796 object, rather than the mapped instance itself. 

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

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

799 control subsequent event propagation, or to otherwise alter 

800 the operation in progress by the mapper. Possible return 

801 values are: 

802 

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

804 processing normally. 

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

806 event handlers in the chain. 

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

808 

809 """ 

810 

811 _target_class_doc = "SomeClass" 

812 _dispatch_target = mapperlib.Mapper 

813 

814 @classmethod 

815 def _new_mapper_instance( 

816 cls, 

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

818 mapper: Mapper[_O], 

819 ) -> None: 

820 _MapperEventsHold.populate(class_, mapper) 

821 

822 @classmethod 

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

824 def _accept_with( 

825 cls, 

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

827 identifier: str, 

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

829 orm = util.preloaded.orm 

830 

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

832 util.warn_deprecated( 

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

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

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

836 "2.0", 

837 ) 

838 return mapperlib.Mapper 

839 elif isinstance(target, type): 

840 if issubclass(target, mapperlib.Mapper): 

841 return target 

842 else: 

843 mapper = _mapper_or_none(target) 

844 if mapper is not None: 

845 return mapper 

846 else: 

847 return _MapperEventsHold(target) 

848 else: 

849 return target 

850 

851 @classmethod 

852 def _listen( 

853 cls, 

854 event_key: _EventKey[_ET], 

855 raw: bool = False, 

856 retval: bool = False, 

857 propagate: bool = False, 

858 **kw: Any, 

859 ) -> None: 

860 target, identifier, fn = ( 

861 event_key.dispatch_target, 

862 event_key.identifier, 

863 event_key._listen_fn, 

864 ) 

865 

866 if ( 

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

868 and target is not mapperlib.Mapper 

869 ): 

870 util.warn( 

871 "'before_configured' and 'after_configured' ORM events " 

872 "only invoke with the Mapper class " 

873 "as the target." 

874 ) 

875 

876 if not raw or not retval: 

877 if not raw: 

878 meth = getattr(cls, identifier) 

879 try: 

880 target_index = ( 

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

882 ) 

883 except ValueError: 

884 target_index = None 

885 

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

887 if not raw and target_index is not None: 

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

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

890 if not retval: 

891 fn(*arg, **kw) 

892 return interfaces.EXT_CONTINUE 

893 else: 

894 return fn(*arg, **kw) 

895 

896 event_key = event_key.with_wrapper(wrap) 

897 

898 if propagate: 

899 for mapper in target.self_and_descendants: 

900 event_key.with_dispatch_target(mapper).base_listen( 

901 propagate=True, **kw 

902 ) 

903 else: 

904 event_key.base_listen(**kw) 

905 

906 @classmethod 

907 def _clear(cls) -> None: 

908 super()._clear() 

909 _MapperEventsHold._clear() 

910 

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

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

913 before instrumentation is applied to the mapped class. 

914 

915 This event is the earliest phase of mapper construction. 

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

917 receive an event within initial mapper construction where basic 

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

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

920 be a better choice. 

921 

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

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

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

925 

926 Base = declarative_base() 

927 

928 

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

930 def on_new_class(mapper, cls_): 

931 "..." 

932 

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

934 of this event. 

935 :param class\_: the mapped class. 

936 

937 .. seealso:: 

938 

939 :meth:`_orm.MapperEvents.after_mapper_constructed` 

940 

941 """ 

942 

943 def after_mapper_constructed( 

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

945 ) -> None: 

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

947 fully constructed. 

948 

949 This event is called after the initial constructor for 

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

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

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

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

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

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

956 

957 This event differs from the 

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

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

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

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

962 wish to create additional mapped classes in response to the 

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

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

965 

966 .. versionadded:: 2.0.2 

967 

968 .. seealso:: 

969 

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

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

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

973 objects. 

974 

975 """ 

976 

977 def before_mapper_configured( 

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

979 ) -> None: 

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

981 

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

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

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

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

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

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

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

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

990 available mappers. 

991 

992 In comparison to the other configure-level events, 

993 :meth:`.MapperEvents.before_configured`, 

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

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

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

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

998 parameter. 

999 

1000 .. versionadded:: 1.3 

1001 

1002 e.g.:: 

1003 

1004 from sqlalchemy.orm import EXT_SKIP 

1005 

1006 Base = declarative_base() 

1007 

1008 DontConfigureBase = declarative_base() 

1009 

1010 

1011 @event.listens_for( 

1012 DontConfigureBase, 

1013 "before_mapper_configured", 

1014 retval=True, 

1015 propagate=True, 

1016 ) 

1017 def dont_configure(mapper, cls): 

1018 return EXT_SKIP 

1019 

1020 .. seealso:: 

1021 

1022 :meth:`.MapperEvents.before_configured` 

1023 

1024 :meth:`.MapperEvents.after_configured` 

1025 

1026 :meth:`.MapperEvents.mapper_configured` 

1027 

1028 """ 

1029 

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

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

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

1033 

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

1035 for each mapper that is encountered when the 

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

1037 list of not-yet-configured mappers. 

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

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

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

1041 detected. 

1042 

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

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

1045 other mappers; they might still be pending within the 

1046 configuration operation. Bidirectional relationships that 

1047 are instead configured via the 

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

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

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

1051 exist. 

1052 

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

1054 to go including backrefs that are defined only on other 

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

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

1057 fully configured. 

1058 

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

1060 :meth:`.MapperEvents.before_configured` or 

1061 :meth:`.MapperEvents.after_configured`, 

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

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

1064 a particular mapper. The event is therefore useful for 

1065 configurational steps that benefit from being invoked just once 

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

1067 configurations are necessarily ready yet. 

1068 

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

1070 of this event. 

1071 :param class\_: the mapped class. 

1072 

1073 .. seealso:: 

1074 

1075 :meth:`.MapperEvents.before_configured` 

1076 

1077 :meth:`.MapperEvents.after_configured` 

1078 

1079 :meth:`.MapperEvents.before_mapper_configured` 

1080 

1081 """ 

1082 # TODO: need coverage for this event 

1083 

1084 def before_configured(self) -> None: 

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

1086 

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

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

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

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

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

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

1093 detected. 

1094 

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

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

1097 for all mappings as a whole:: 

1098 

1099 from sqlalchemy.orm import Mapper 

1100 

1101 

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

1103 def go(): ... 

1104 

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

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

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

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

1109 on a per-mapper basis. 

1110 

1111 Theoretically this event is called once per 

1112 application, but is actually called any time new mappers 

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

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

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

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

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

1118 

1119 from sqlalchemy.orm import mapper 

1120 

1121 

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

1123 def go(): ... 

1124 

1125 .. seealso:: 

1126 

1127 :meth:`.MapperEvents.before_mapper_configured` 

1128 

1129 :meth:`.MapperEvents.mapper_configured` 

1130 

1131 :meth:`.MapperEvents.after_configured` 

1132 

1133 """ 

1134 

1135 def after_configured(self) -> None: 

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

1137 

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

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

1140 invoked, after the function has completed its work. 

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

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

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

1144 detected. 

1145 

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

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

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

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

1150 available for any mappers that were pending. 

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

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

1153 

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

1155 and not to individual mappings or 

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

1157 

1158 from sqlalchemy.orm import Mapper 

1159 

1160 

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

1162 def go(): ... 

1163 

1164 Theoretically this event is called once per 

1165 application, but is actually called any time new mappers 

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

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

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

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

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

1171 

1172 from sqlalchemy.orm import mapper 

1173 

1174 

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

1176 def go(): ... 

1177 

1178 .. seealso:: 

1179 

1180 :meth:`.MapperEvents.before_mapper_configured` 

1181 

1182 :meth:`.MapperEvents.mapper_configured` 

1183 

1184 :meth:`.MapperEvents.before_configured` 

1185 

1186 """ 

1187 

1188 def before_insert( 

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

1190 ) -> None: 

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

1192 is emitted corresponding to that instance. 

1193 

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

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

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

1197 :ref:`orm_expression_update_delete`. To intercept ORM 

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

1199 

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

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

1202 as to emit additional SQL statements on the given 

1203 connection. 

1204 

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

1206 same class before their INSERT statements are emitted at 

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

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

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

1210 batches of instances to be broken up into individual 

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

1212 steps. 

1213 

1214 .. warning:: 

1215 

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

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

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

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

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

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

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

1223 

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

1225 of this event. 

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

1227 emit INSERT statements for this instance. This 

1228 provides a handle into the current transaction on the 

1229 target database specific to this instance. 

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

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

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

1233 object associated with the instance. 

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

1235 

1236 .. seealso:: 

1237 

1238 :ref:`session_persistence_events` 

1239 

1240 """ 

1241 

1242 def after_insert( 

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

1244 ) -> None: 

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

1246 is emitted corresponding to that instance. 

1247 

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

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

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

1251 :ref:`orm_expression_update_delete`. To intercept ORM 

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

1253 

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

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

1256 as to emit additional SQL statements on the given 

1257 connection. 

1258 

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

1260 same class after their INSERT statements have been 

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

1262 rare case that this is not desirable, the 

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

1264 which will cause batches of instances to be broken up 

1265 into individual (and more poorly performing) 

1266 event->persist->event steps. 

1267 

1268 .. warning:: 

1269 

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

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

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

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

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

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

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

1277 

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

1279 of this event. 

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

1281 emit INSERT statements for this instance. This 

1282 provides a handle into the current transaction on the 

1283 target database specific to this instance. 

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

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

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

1287 object associated with the instance. 

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

1289 

1290 .. seealso:: 

1291 

1292 :ref:`session_persistence_events` 

1293 

1294 """ 

1295 

1296 def before_update( 

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

1298 ) -> None: 

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

1300 is emitted corresponding to that instance. 

1301 

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

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

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

1305 :ref:`orm_expression_update_delete`. To intercept ORM 

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

1307 

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

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

1310 as to emit additional SQL statements on the given 

1311 connection. 

1312 

1313 This method is called for all instances that are 

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

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

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

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

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

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

1320 statement will be issued. This means that an instance 

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

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

1323 issued, although you can affect the outcome here by 

1324 modifying attributes so that a net change in value does 

1325 exist. 

1326 

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

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

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

1330 include_collections=False)``. 

1331 

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

1333 same class before their UPDATE statements are emitted at 

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

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

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

1337 batches of instances to be broken up into individual 

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

1339 steps. 

1340 

1341 .. warning:: 

1342 

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

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

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

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

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

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

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

1350 

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

1352 of this event. 

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

1354 emit UPDATE statements for this instance. This 

1355 provides a handle into the current transaction on the 

1356 target database specific to this instance. 

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

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

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

1360 object associated with the instance. 

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

1362 

1363 .. seealso:: 

1364 

1365 :ref:`session_persistence_events` 

1366 

1367 """ 

1368 

1369 def after_update( 

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

1371 ) -> None: 

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

1373 is emitted corresponding to that instance. 

1374 

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

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

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

1378 :ref:`orm_expression_update_delete`. To intercept ORM 

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

1380 

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

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

1383 as to emit additional SQL statements on the given 

1384 connection. 

1385 

1386 This method is called for all instances that are 

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

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

1389 no UPDATE statement has proceeded. An object is marked 

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

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

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

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

1394 statement will be issued. This means that an instance 

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

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

1397 issued. 

1398 

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

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

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

1402 include_collections=False)``. 

1403 

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

1405 same class after their UPDATE statements have been emitted at 

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

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

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

1409 batches of instances to be broken up into individual 

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

1411 steps. 

1412 

1413 .. warning:: 

1414 

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

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

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

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

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

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

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

1422 

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

1424 of this event. 

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

1426 emit UPDATE statements for this instance. This 

1427 provides a handle into the current transaction on the 

1428 target database specific to this instance. 

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

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

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

1432 object associated with the instance. 

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

1434 

1435 .. seealso:: 

1436 

1437 :ref:`session_persistence_events` 

1438 

1439 """ 

1440 

1441 def before_delete( 

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

1443 ) -> None: 

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

1445 is emitted corresponding to that instance. 

1446 

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

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

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

1450 :ref:`orm_expression_update_delete`. To intercept ORM 

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

1452 

1453 This event is used to emit additional SQL statements on 

1454 the given connection as well as to perform application 

1455 specific bookkeeping related to a deletion event. 

1456 

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

1458 same class before their DELETE statements are emitted at 

1459 once in a later step. 

1460 

1461 .. warning:: 

1462 

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

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

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

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

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

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

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

1470 

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

1472 of this event. 

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

1474 emit DELETE statements for this instance. This 

1475 provides a handle into the current transaction on the 

1476 target database specific to this instance. 

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

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

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

1480 object associated with the instance. 

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

1482 

1483 .. seealso:: 

1484 

1485 :ref:`session_persistence_events` 

1486 

1487 """ 

1488 

1489 def after_delete( 

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

1491 ) -> None: 

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

1493 has been emitted corresponding to that instance. 

1494 

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

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

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

1498 :ref:`orm_expression_update_delete`. To intercept ORM 

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

1500 

1501 This event is used to emit additional SQL statements on 

1502 the given connection as well as to perform application 

1503 specific bookkeeping related to a deletion event. 

1504 

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

1506 same class after their DELETE statements have been emitted at 

1507 once in a previous step. 

1508 

1509 .. warning:: 

1510 

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

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

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

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

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

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

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

1518 

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

1520 of this event. 

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

1522 emit DELETE statements for this instance. This 

1523 provides a handle into the current transaction on the 

1524 target database specific to this instance. 

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

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

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

1528 object associated with the instance. 

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

1530 

1531 .. seealso:: 

1532 

1533 :ref:`session_persistence_events` 

1534 

1535 """ 

1536 

1537 

1538class _MapperEventsHold(_EventsHold[_ET]): 

1539 all_holds = weakref.WeakKeyDictionary() 

1540 

1541 def resolve( 

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

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

1544 return _mapper_or_none(class_) 

1545 

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

1547 pass 

1548 

1549 dispatch = event.dispatcher(HoldMapperEvents) 

1550 

1551 

1552_sessionevents_lifecycle_event_names: Set[str] = set() 

1553 

1554 

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

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

1557 

1558 e.g.:: 

1559 

1560 from sqlalchemy import event 

1561 from sqlalchemy.orm import sessionmaker 

1562 

1563 

1564 def my_before_commit(session): 

1565 print("before commit!") 

1566 

1567 

1568 Session = sessionmaker() 

1569 

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

1571 

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

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

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

1575 

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

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

1578 globally. 

1579 

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

1581 to applicable event listener functions that work on individual 

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

1583 object, rather than the mapped instance itself. 

1584 

1585 .. versionadded:: 1.3.14 

1586 

1587 :param restore_load_context=False: Applies to the 

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

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

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

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

1592 within this event if this flag is not set. 

1593 

1594 .. versionadded:: 1.3.14 

1595 

1596 """ 

1597 

1598 _target_class_doc = "SomeSessionClassOrObject" 

1599 

1600 _dispatch_target = Session 

1601 

1602 def _lifecycle_event( # type: ignore [misc] 

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

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

1605 _sessionevents_lifecycle_event_names.add(fn.__name__) 

1606 return fn 

1607 

1608 @classmethod 

1609 def _accept_with( # type: ignore [return] 

1610 cls, target: Any, identifier: str 

1611 ) -> Union[Session, type]: 

1612 if isinstance(target, scoped_session): 

1613 target = target.session_factory 

1614 if not isinstance(target, sessionmaker) and ( 

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

1616 ): 

1617 raise exc.ArgumentError( 

1618 "Session event listen on a scoped_session " 

1619 "requires that its creation callable " 

1620 "is associated with the Session class." 

1621 ) 

1622 

1623 if isinstance(target, sessionmaker): 

1624 return target.class_ 

1625 elif isinstance(target, type): 

1626 if issubclass(target, scoped_session): 

1627 return Session 

1628 elif issubclass(target, Session): 

1629 return target 

1630 elif isinstance(target, Session): 

1631 return target 

1632 elif hasattr(target, "_no_async_engine_events"): 

1633 target._no_async_engine_events() 

1634 else: 

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

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

1637 

1638 @classmethod 

1639 def _listen( 

1640 cls, 

1641 event_key: Any, 

1642 *, 

1643 raw: bool = False, 

1644 restore_load_context: bool = False, 

1645 **kw: Any, 

1646 ) -> None: 

1647 is_instance_event = ( 

1648 event_key.identifier in _sessionevents_lifecycle_event_names 

1649 ) 

1650 

1651 if is_instance_event: 

1652 if not raw or restore_load_context: 

1653 fn = event_key._listen_fn 

1654 

1655 def wrap( 

1656 session: Session, 

1657 state: InstanceState[_O], 

1658 *arg: Any, 

1659 **kw: Any, 

1660 ) -> Optional[Any]: 

1661 if not raw: 

1662 target = state.obj() 

1663 if target is None: 

1664 # existing behavior is that if the object is 

1665 # garbage collected, no event is emitted 

1666 return None 

1667 else: 

1668 target = state # type: ignore [assignment] 

1669 if restore_load_context: 

1670 runid = state.runid 

1671 try: 

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

1673 finally: 

1674 if restore_load_context: 

1675 state.runid = runid 

1676 

1677 event_key = event_key.with_wrapper(wrap) 

1678 

1679 event_key.base_listen(**kw) 

1680 

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

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

1683 ORM :class:`.Session` object. 

1684 

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

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

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

1688 SQLAlchemy 1.4, all ORM queries that run through the 

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

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

1691 will participate in this event. 

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

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

1694 process described at :ref:`session_flushing`. 

1695 

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

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

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

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

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

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

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

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

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

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

1706 :meth:`.ConnectionEvents.before_execute` and 

1707 :meth:`.ConnectionEvents.before_cursor_execute`. 

1708 

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

1710 emitted internally within the ORM flush process, 

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

1712 intercept steps within the flush process, see the event 

1713 hooks described at :ref:`session_persistence_events` as 

1714 well as :ref:`session_persistence_mapper`. 

1715 

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

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

1718 performs. The intended use for this includes sharding and 

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

1720 across multiple database connections, returning a result that is 

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

1722 instead returning data from a cache. 

1723 

1724 The hook intends to replace the use of the 

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

1726 to SQLAlchemy 1.4. 

1727 

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

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

1730 as helper functions used to derive other commonly required 

1731 information. See that object for details. 

1732 

1733 .. seealso:: 

1734 

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

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

1737 

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

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

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

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

1742 and parameters as well as an option that allows programmatic 

1743 invocation of the statement at any point. 

1744 

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

1746 :meth:`_orm.SessionEvents.do_orm_execute` 

1747 

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

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

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

1751 

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

1753 extension relies upon the 

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

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

1756 

1757 

1758 .. versionadded:: 1.4 

1759 

1760 """ 

1761 

1762 def after_transaction_create( 

1763 self, session: Session, transaction: SessionTransaction 

1764 ) -> None: 

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

1766 

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

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

1769 overall, as opposed to when transactions are begun 

1770 on individual database connections. It is also invoked 

1771 for nested transactions and subtransactions, and is always 

1772 matched by a corresponding 

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

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

1775 

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

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

1778 

1779 To detect if this is the outermost 

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

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

1782 is ``None``:: 

1783 

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

1785 def after_transaction_create(session, transaction): 

1786 if transaction.parent is None: 

1787 ... # work with top-level transaction 

1788 

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

1790 :attr:`.SessionTransaction.nested` attribute:: 

1791 

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

1793 def after_transaction_create(session, transaction): 

1794 if transaction.nested: 

1795 ... # work with SAVEPOINT transaction 

1796 

1797 .. seealso:: 

1798 

1799 :class:`.SessionTransaction` 

1800 

1801 :meth:`~.SessionEvents.after_transaction_end` 

1802 

1803 """ 

1804 

1805 def after_transaction_end( 

1806 self, session: Session, transaction: SessionTransaction 

1807 ) -> None: 

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

1809 

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

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

1812 objects in use, including those for nested transactions 

1813 and subtransactions, and is always matched by a corresponding 

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

1815 

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

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

1818 

1819 To detect if this is the outermost 

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

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

1822 is ``None``:: 

1823 

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

1825 def after_transaction_end(session, transaction): 

1826 if transaction.parent is None: 

1827 ... # work with top-level transaction 

1828 

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

1830 :attr:`.SessionTransaction.nested` attribute:: 

1831 

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

1833 def after_transaction_end(session, transaction): 

1834 if transaction.nested: 

1835 ... # work with SAVEPOINT transaction 

1836 

1837 .. seealso:: 

1838 

1839 :class:`.SessionTransaction` 

1840 

1841 :meth:`~.SessionEvents.after_transaction_create` 

1842 

1843 """ 

1844 

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

1846 """Execute before commit is called. 

1847 

1848 .. note:: 

1849 

1850 The :meth:`~.SessionEvents.before_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 :param session: The target :class:`.Session`. 

1860 

1861 .. seealso:: 

1862 

1863 :meth:`~.SessionEvents.after_commit` 

1864 

1865 :meth:`~.SessionEvents.after_begin` 

1866 

1867 :meth:`~.SessionEvents.after_transaction_create` 

1868 

1869 :meth:`~.SessionEvents.after_transaction_end` 

1870 

1871 """ 

1872 

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

1874 """Execute after a commit has occurred. 

1875 

1876 .. note:: 

1877 

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

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

1880 many times within the scope of a transaction. 

1881 For interception of these events, use the 

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

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

1884 :meth:`~.SessionEvents.after_flush_postexec` 

1885 events. 

1886 

1887 .. note:: 

1888 

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

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

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

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

1893 event. 

1894 

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

1896 

1897 .. seealso:: 

1898 

1899 :meth:`~.SessionEvents.before_commit` 

1900 

1901 :meth:`~.SessionEvents.after_begin` 

1902 

1903 :meth:`~.SessionEvents.after_transaction_create` 

1904 

1905 :meth:`~.SessionEvents.after_transaction_end` 

1906 

1907 """ 

1908 

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

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

1911 

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

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

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

1915 DBAPI transaction has already been rolled back. In many 

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

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

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

1919 which is active after the outermost rollback has proceeded, 

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

1921 :attr:`.Session.is_active` flag. 

1922 

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

1924 

1925 """ 

1926 

1927 def after_soft_rollback( 

1928 self, session: Session, previous_transaction: SessionTransaction 

1929 ) -> None: 

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

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

1932 

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

1934 the innermost rollback that calls the DBAPI's 

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

1936 calls that only pop themselves from the transaction stack. 

1937 

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

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

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

1941 

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

1943 def do_something(session, previous_transaction): 

1944 if session.is_active: 

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

1946 

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

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

1949 transactional marker object which was just closed. The current 

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

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

1952 

1953 """ 

1954 

1955 def before_flush( 

1956 self, 

1957 session: Session, 

1958 flush_context: UOWTransaction, 

1959 instances: Optional[Sequence[_O]], 

1960 ) -> None: 

1961 """Execute before flush process has started. 

1962 

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

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

1965 which handles the details of the flush. 

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

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

1968 (note this usage is deprecated). 

1969 

1970 .. seealso:: 

1971 

1972 :meth:`~.SessionEvents.after_flush` 

1973 

1974 :meth:`~.SessionEvents.after_flush_postexec` 

1975 

1976 :ref:`session_persistence_events` 

1977 

1978 """ 

1979 

1980 def after_flush( 

1981 self, session: Session, flush_context: UOWTransaction 

1982 ) -> None: 

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

1984 called. 

1985 

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

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

1988 as the history settings on instance attributes. 

1989 

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

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

1992 internal state to reflect those changes, including that newly 

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

1994 emitted within this event such as loads of related items 

1995 may produce new identity map entries that will immediately 

1996 be replaced, sometimes causing confusing results. SQLAlchemy will 

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

1998 

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

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

2001 which handles the details of the flush. 

2002 

2003 .. seealso:: 

2004 

2005 :meth:`~.SessionEvents.before_flush` 

2006 

2007 :meth:`~.SessionEvents.after_flush_postexec` 

2008 

2009 :ref:`session_persistence_events` 

2010 

2011 """ 

2012 

2013 def after_flush_postexec( 

2014 self, session: Session, flush_context: UOWTransaction 

2015 ) -> None: 

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

2017 state occurs. 

2018 

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

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

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

2022 transaction or participated in a larger transaction. 

2023 

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

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

2026 which handles the details of the flush. 

2027 

2028 

2029 .. seealso:: 

2030 

2031 :meth:`~.SessionEvents.before_flush` 

2032 

2033 :meth:`~.SessionEvents.after_flush` 

2034 

2035 :ref:`session_persistence_events` 

2036 

2037 """ 

2038 

2039 def after_begin( 

2040 self, 

2041 session: Session, 

2042 transaction: SessionTransaction, 

2043 connection: Connection, 

2044 ) -> None: 

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

2046 

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

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

2049 To invoke SQL operations within this hook, use the 

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

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

2052 directly. 

2053 

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

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

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

2057 which will be used for SQL statements. 

2058 

2059 .. seealso:: 

2060 

2061 :meth:`~.SessionEvents.before_commit` 

2062 

2063 :meth:`~.SessionEvents.after_commit` 

2064 

2065 :meth:`~.SessionEvents.after_transaction_create` 

2066 

2067 :meth:`~.SessionEvents.after_transaction_end` 

2068 

2069 """ 

2070 

2071 @_lifecycle_event 

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

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

2074 

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

2076 the object to be part of the session. 

2077 

2078 .. seealso:: 

2079 

2080 :meth:`~.SessionEvents.after_attach` 

2081 

2082 :ref:`session_lifecycle_events` 

2083 

2084 """ 

2085 

2086 @_lifecycle_event 

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

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

2089 

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

2091 

2092 .. note:: 

2093 

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

2095 has been fully associated with the session, which is 

2096 different than previous releases. For event 

2097 handlers that require the object not yet 

2098 be part of session state (such as handlers which 

2099 may autoflush while the target object is not 

2100 yet complete) consider the 

2101 new :meth:`.before_attach` event. 

2102 

2103 .. seealso:: 

2104 

2105 :meth:`~.SessionEvents.before_attach` 

2106 

2107 :ref:`session_lifecycle_events` 

2108 

2109 """ 

2110 

2111 @event._legacy_signature( 

2112 "0.9", 

2113 ["session", "query", "query_context", "result"], 

2114 lambda update_context: ( 

2115 update_context.session, 

2116 update_context.query, 

2117 None, 

2118 update_context.result, 

2119 ), 

2120 ) 

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

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

2123 has been called. 

2124 

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

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

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

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

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

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

2131 these calls. 

2132 

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

2134 details about the update, including these attributes: 

2135 

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

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

2138 object that this update operation 

2139 was called upon. 

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

2141 :meth:`_query.Query.update`. 

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

2143 returned as a result of the 

2144 bulk UPDATE operation. 

2145 

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

2147 ``QueryContext`` object associated with it. 

2148 

2149 .. seealso:: 

2150 

2151 :meth:`.QueryEvents.before_compile_update` 

2152 

2153 :meth:`.SessionEvents.after_bulk_delete` 

2154 

2155 """ 

2156 

2157 @event._legacy_signature( 

2158 "0.9", 

2159 ["session", "query", "query_context", "result"], 

2160 lambda delete_context: ( 

2161 delete_context.session, 

2162 delete_context.query, 

2163 None, 

2164 delete_context.result, 

2165 ), 

2166 ) 

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

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

2169 has been called. 

2170 

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

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

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

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

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

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

2177 these calls. 

2178 

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

2180 details about the update, including these attributes: 

2181 

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

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

2184 object that this update operation 

2185 was called upon. 

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

2187 returned as a result of the 

2188 bulk DELETE operation. 

2189 

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

2191 ``QueryContext`` object associated with it. 

2192 

2193 .. seealso:: 

2194 

2195 :meth:`.QueryEvents.before_compile_delete` 

2196 

2197 :meth:`.SessionEvents.after_bulk_update` 

2198 

2199 """ 

2200 

2201 @_lifecycle_event 

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

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

2204 object. 

2205 

2206 This event is a specialization of the 

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

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

2209 :meth:`.Session.add` call. 

2210 

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

2212 

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

2214 

2215 .. seealso:: 

2216 

2217 :ref:`session_lifecycle_events` 

2218 

2219 """ 

2220 

2221 @_lifecycle_event 

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

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

2224 object. 

2225 

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

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

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

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

2230 

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

2232 

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

2234 

2235 .. seealso:: 

2236 

2237 :ref:`session_lifecycle_events` 

2238 

2239 """ 

2240 

2241 @_lifecycle_event 

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

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

2244 object. 

2245 

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

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

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

2249 

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

2251 

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

2253 

2254 .. seealso:: 

2255 

2256 :ref:`session_lifecycle_events` 

2257 

2258 """ 

2259 

2260 @_lifecycle_event 

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

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

2263 object. 

2264 

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

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

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

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

2269 when the event is called. 

2270 

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

2272 

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

2274 

2275 .. seealso:: 

2276 

2277 :ref:`session_lifecycle_events` 

2278 

2279 """ 

2280 

2281 @_lifecycle_event 

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

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

2284 object. 

2285 

2286 This event is a specialization of the 

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

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

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

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

2291 associated with the 

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

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

2294 

2295 .. note:: 

2296 

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

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

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

2300 check the ``deleted`` flag sent to the 

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

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

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

2304 objects need to be intercepted before the flush. 

2305 

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

2307 

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

2309 

2310 .. seealso:: 

2311 

2312 :ref:`session_lifecycle_events` 

2313 

2314 """ 

2315 

2316 @_lifecycle_event 

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

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

2319 object. 

2320 

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

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

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

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

2325 with the other session lifecycle events smoothly. The object 

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

2327 this event is called. 

2328 

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

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

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

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

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

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

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

2336 works in the same manner as that of 

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

2338 resolve this scenario. 

2339 

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

2341 

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

2343 

2344 .. seealso:: 

2345 

2346 :ref:`session_lifecycle_events` 

2347 

2348 """ 

2349 

2350 @_lifecycle_event 

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

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

2353 object. 

2354 

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

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

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

2358 transaction completes. 

2359 

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

2361 to the persistent state, and the 

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

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

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

2365 event. 

2366 

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

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

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

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

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

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

2373 invoked at the end of a flush. 

2374 

2375 .. seealso:: 

2376 

2377 :ref:`session_lifecycle_events` 

2378 

2379 """ 

2380 

2381 @_lifecycle_event 

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

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

2384 object. 

2385 

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

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

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

2389 any other circumstances. 

2390 

2391 .. seealso:: 

2392 

2393 :ref:`session_lifecycle_events` 

2394 

2395 """ 

2396 

2397 @_lifecycle_event 

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

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

2400 object. 

2401 

2402 This event is invoked when a deleted object is evicted 

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

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

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

2406 state to the detached state. 

2407 

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

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

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

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

2412 

2413 .. seealso:: 

2414 

2415 :ref:`session_lifecycle_events` 

2416 

2417 """ 

2418 

2419 @_lifecycle_event 

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

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

2422 object. 

2423 

2424 This event is invoked when a persistent object is evicted 

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

2426 to happen, including: 

2427 

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

2429 or :meth:`.Session.close` 

2430 

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

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

2433 

2434 

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

2436 

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

2438 

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

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

2441 

2442 

2443 .. seealso:: 

2444 

2445 :ref:`session_lifecycle_events` 

2446 

2447 """ 

2448 

2449 

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

2451 r"""Define events for object attributes. 

2452 

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

2454 target class. 

2455 

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

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

2458 

2459 from sqlalchemy import event 

2460 

2461 

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

2463 def my_append_listener(target, value, initiator): 

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

2465 

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

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

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

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

2470 

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

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

2473 

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

2475 

2476 

2477 # setup listener on UserContact.phone attribute, instructing 

2478 # it to use the return value 

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

2480 

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

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

2483 

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

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

2486 as when using mapper inheritance patterns:: 

2487 

2488 

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

2490 def receive_set(target, value, initiator): 

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

2492 

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

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

2495 

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

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

2498 replaced unconditionally, even if this requires firing off 

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

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

2501 :func:`_orm.relationship`. 

2502 

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

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

2505 for attributes of the same name on all current subclasses 

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

2507 class, using an additional listener that listens for 

2508 instrumentation events. 

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

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

2511 object, rather than the mapped instance itself. 

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

2513 listening must return the "value" argument from the 

2514 function. This gives the listening function the opportunity 

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

2516 or "append" event. 

2517 

2518 """ 

2519 

2520 _target_class_doc = "SomeClass.some_attribute" 

2521 _dispatch_target = QueryableAttribute 

2522 

2523 @staticmethod 

2524 def _set_dispatch( 

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

2526 ) -> _Dispatch[Any]: 

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

2528 dispatch_cls._active_history = False 

2529 return dispatch 

2530 

2531 @classmethod 

2532 def _accept_with( 

2533 cls, 

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

2535 identifier: str, 

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

2537 # TODO: coverage 

2538 if isinstance(target, interfaces.MapperProperty): 

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

2540 else: 

2541 return target 

2542 

2543 @classmethod 

2544 def _listen( # type: ignore [override] 

2545 cls, 

2546 event_key: _EventKey[QueryableAttribute[Any]], 

2547 active_history: bool = False, 

2548 raw: bool = False, 

2549 retval: bool = False, 

2550 propagate: bool = False, 

2551 include_key: bool = False, 

2552 ) -> None: 

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

2554 

2555 if active_history: 

2556 target.dispatch._active_history = True 

2557 

2558 if not raw or not retval or not include_key: 

2559 

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

2561 if not raw: 

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

2563 if not retval: 

2564 if arg: 

2565 value = arg[0] 

2566 else: 

2567 value = None 

2568 if include_key: 

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

2570 else: 

2571 fn(target, *arg) 

2572 return value 

2573 else: 

2574 if include_key: 

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

2576 else: 

2577 return fn(target, *arg) 

2578 

2579 event_key = event_key.with_wrapper(wrap) 

2580 

2581 event_key.base_listen(propagate=propagate) 

2582 

2583 if propagate: 

2584 manager = instrumentation.manager_of_class(target.class_) 

2585 

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

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

2588 propagate=True 

2589 ) 

2590 if active_history: 

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

2592 

2593 def append( 

2594 self, 

2595 target: _O, 

2596 value: _T, 

2597 initiator: Event, 

2598 *, 

2599 key: EventConstants = NO_KEY, 

2600 ) -> Optional[_T]: 

2601 """Receive a collection append event. 

2602 

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

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

2605 as for a "bulk replace" operation. 

2606 

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

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

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

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

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

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

2613 replaces it. 

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

2615 representing the initiation of the event. May be modified 

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

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

2618 about the source of the event. 

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

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

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

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

2623 The parameter is not passed 

2624 to the event at all if the the 

2625 :paramref:`.AttributeEvents.include_key` 

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

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

2628 ``key`` parameter. 

2629 

2630 .. versionadded:: 2.0 

2631 

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

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

2634 

2635 .. seealso:: 

2636 

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

2638 as propagation to subclasses. 

2639 

2640 :meth:`.AttributeEvents.bulk_replace` 

2641 

2642 """ 

2643 

2644 def append_wo_mutation( 

2645 self, 

2646 target: _O, 

2647 value: _T, 

2648 initiator: Event, 

2649 *, 

2650 key: EventConstants = NO_KEY, 

2651 ) -> None: 

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

2653 actually mutated. 

2654 

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

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

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

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

2659 given object cannot be changed. 

2660 

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

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

2663 

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

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

2666 be the :class:`.InstanceState` object. 

2667 :param value: the value that would be appended if the object did not 

2668 already exist in the collection. 

2669 :param initiator: An instance of :class:`.attributes.Event` 

2670 representing the initiation of the event. May be modified 

2671 from its original value by backref handlers in order to control 

2672 chained event propagation, as well as be inspected for information 

2673 about the source of the event. 

2674 :param key: When the event is established using the 

2675 :paramref:`.AttributeEvents.include_key` parameter set to 

2676 True, this will be the key used in the operation, such as 

2677 ``collection[some_key_or_index] = value``. 

2678 The parameter is not passed 

2679 to the event at all if the the 

2680 :paramref:`.AttributeEvents.include_key` 

2681 was not used to set up the event; this is to allow backwards 

2682 compatibility with existing event handlers that don't include the 

2683 ``key`` parameter. 

2684 

2685 .. versionadded:: 2.0 

2686 

2687 :return: No return value is defined for this event. 

2688 

2689 .. versionadded:: 1.4.15 

2690 

2691 """ 

2692 

2693 def bulk_replace( 

2694 self, 

2695 target: _O, 

2696 values: Iterable[_T], 

2697 initiator: Event, 

2698 *, 

2699 keys: Optional[Iterable[EventConstants]] = None, 

2700 ) -> None: 

2701 """Receive a collection 'bulk replace' event. 

2702 

2703 This event is invoked for a sequence of values as they are incoming 

2704 to a bulk collection set operation, which can be 

2705 modified in place before the values are treated as ORM objects. 

2706 This is an "early hook" that runs before the bulk replace routine 

2707 attempts to reconcile which objects are already present in the 

2708 collection and which are being removed by the net replace operation. 

2709 

2710 It is typical that this method be combined with use of the 

2711 :meth:`.AttributeEvents.append` event. When using both of these 

2712 events, note that a bulk replace operation will invoke 

2713 the :meth:`.AttributeEvents.append` event for all new items, 

2714 even after :meth:`.AttributeEvents.bulk_replace` has been invoked 

2715 for the collection as a whole. In order to determine if an 

2716 :meth:`.AttributeEvents.append` event is part of a bulk replace, 

2717 use the symbol :attr:`~.attributes.OP_BULK_REPLACE` to test the 

2718 incoming initiator:: 

2719 

2720 from sqlalchemy.orm.attributes import OP_BULK_REPLACE 

2721 

2722 

2723 @event.listens_for(SomeObject.collection, "bulk_replace") 

2724 def process_collection(target, values, initiator): 

2725 values[:] = [_make_value(value) for value in values] 

2726 

2727 

2728 @event.listens_for(SomeObject.collection, "append", retval=True) 

2729 def process_collection(target, value, initiator): 

2730 # make sure bulk_replace didn't already do it 

2731 if initiator is None or initiator.op is not OP_BULK_REPLACE: 

2732 return _make_value(value) 

2733 else: 

2734 return value 

2735 

2736 .. versionadded:: 1.2 

2737 

2738 :param target: the object instance receiving the event. 

2739 If the listener is registered with ``raw=True``, this will 

2740 be the :class:`.InstanceState` object. 

2741 :param value: a sequence (e.g. a list) of the values being set. The 

2742 handler can modify this list in place. 

2743 :param initiator: An instance of :class:`.attributes.Event` 

2744 representing the initiation of the event. 

2745 :param keys: When the event is established using the 

2746 :paramref:`.AttributeEvents.include_key` parameter set to 

2747 True, this will be the sequence of keys used in the operation, 

2748 typically only for a dictionary update. The parameter is not passed 

2749 to the event at all if the the 

2750 :paramref:`.AttributeEvents.include_key` 

2751 was not used to set up the event; this is to allow backwards 

2752 compatibility with existing event handlers that don't include the 

2753 ``key`` parameter. 

2754 

2755 .. versionadded:: 2.0 

2756 

2757 .. seealso:: 

2758 

2759 :class:`.AttributeEvents` - background on listener options such 

2760 as propagation to subclasses. 

2761 

2762 

2763 """ 

2764 

2765 def remove( 

2766 self, 

2767 target: _O, 

2768 value: _T, 

2769 initiator: Event, 

2770 *, 

2771 key: EventConstants = NO_KEY, 

2772 ) -> None: 

2773 """Receive a collection remove event. 

2774 

2775 :param target: the object instance receiving the event. 

2776 If the listener is registered with ``raw=True``, this will 

2777 be the :class:`.InstanceState` object. 

2778 :param value: the value being removed. 

2779 :param initiator: An instance of :class:`.attributes.Event` 

2780 representing the initiation of the event. May be modified 

2781 from its original value by backref handlers in order to control 

2782 chained event propagation. 

2783 

2784 :param key: When the event is established using the 

2785 :paramref:`.AttributeEvents.include_key` parameter set to 

2786 True, this will be the key used in the operation, such as 

2787 ``del collection[some_key_or_index]``. The parameter is not passed 

2788 to the event at all if the the 

2789 :paramref:`.AttributeEvents.include_key` 

2790 was not used to set up the event; this is to allow backwards 

2791 compatibility with existing event handlers that don't include the 

2792 ``key`` parameter. 

2793 

2794 .. versionadded:: 2.0 

2795 

2796 :return: No return value is defined for this event. 

2797 

2798 

2799 .. seealso:: 

2800 

2801 :class:`.AttributeEvents` - background on listener options such 

2802 as propagation to subclasses. 

2803 

2804 """ 

2805 

2806 def set( 

2807 self, target: _O, value: _T, oldvalue: _T, initiator: Event 

2808 ) -> None: 

2809 """Receive a scalar set event. 

2810 

2811 :param target: the object instance receiving the event. 

2812 If the listener is registered with ``raw=True``, this will 

2813 be the :class:`.InstanceState` object. 

2814 :param value: the value being set. If this listener 

2815 is registered with ``retval=True``, the listener 

2816 function must return this value, or a new value which 

2817 replaces it. 

2818 :param oldvalue: the previous value being replaced. This 

2819 may also be the symbol ``NEVER_SET`` or ``NO_VALUE``. 

2820 If the listener is registered with ``active_history=True``, 

2821 the previous value of the attribute will be loaded from 

2822 the database if the existing value is currently unloaded 

2823 or expired. 

2824 :param initiator: An instance of :class:`.attributes.Event` 

2825 representing the initiation of the event. May be modified 

2826 from its original value by backref handlers in order to control 

2827 chained event propagation. 

2828 

2829 :return: if the event was registered with ``retval=True``, 

2830 the given value, or a new effective value, should be returned. 

2831 

2832 .. seealso:: 

2833 

2834 :class:`.AttributeEvents` - background on listener options such 

2835 as propagation to subclasses. 

2836 

2837 """ 

2838 

2839 def init_scalar( 

2840 self, target: _O, value: _T, dict_: Dict[Any, Any] 

2841 ) -> None: 

2842 r"""Receive a scalar "init" event. 

2843 

2844 This event is invoked when an uninitialized, unpersisted scalar 

2845 attribute is accessed, e.g. read:: 

2846 

2847 

2848 x = my_object.some_attribute 

2849 

2850 The ORM's default behavior when this occurs for an un-initialized 

2851 attribute is to return the value ``None``; note this differs from 

2852 Python's usual behavior of raising ``AttributeError``. The 

2853 event here can be used to customize what value is actually returned, 

2854 with the assumption that the event listener would be mirroring 

2855 a default generator that is configured on the Core 

2856 :class:`_schema.Column` 

2857 object as well. 

2858 

2859 Since a default generator on a :class:`_schema.Column` 

2860 might also produce 

2861 a changing value such as a timestamp, the 

2862 :meth:`.AttributeEvents.init_scalar` 

2863 event handler can also be used to **set** the newly returned value, so 

2864 that a Core-level default generation function effectively fires off 

2865 only once, but at the moment the attribute is accessed on the 

2866 non-persisted object. Normally, no change to the object's state 

2867 is made when an uninitialized attribute is accessed (much older 

2868 SQLAlchemy versions did in fact change the object's state). 

2869 

2870 If a default generator on a column returned a particular constant, 

2871 a handler might be used as follows:: 

2872 

2873 SOME_CONSTANT = 3.1415926 

2874 

2875 

2876 class MyClass(Base): 

2877 # ... 

2878 

2879 some_attribute = Column(Numeric, default=SOME_CONSTANT) 

2880 

2881 

2882 @event.listens_for( 

2883 MyClass.some_attribute, "init_scalar", retval=True, propagate=True 

2884 ) 

2885 def _init_some_attribute(target, dict_, value): 

2886 dict_["some_attribute"] = SOME_CONSTANT 

2887 return SOME_CONSTANT 

2888 

2889 Above, we initialize the attribute ``MyClass.some_attribute`` to the 

2890 value of ``SOME_CONSTANT``. The above code includes the following 

2891 features: 

2892 

2893 * By setting the value ``SOME_CONSTANT`` in the given ``dict_``, 

2894 we indicate that this value is to be persisted to the database. 

2895 This supersedes the use of ``SOME_CONSTANT`` in the default generator 

2896 for the :class:`_schema.Column`. The ``active_column_defaults.py`` 

2897 example given at :ref:`examples_instrumentation` illustrates using 

2898 the same approach for a changing default, e.g. a timestamp 

2899 generator. In this particular example, it is not strictly 

2900 necessary to do this since ``SOME_CONSTANT`` would be part of the 

2901 INSERT statement in either case. 

2902 

2903 * By establishing the ``retval=True`` flag, the value we return 

2904 from the function will be returned by the attribute getter. 

2905 Without this flag, the event is assumed to be a passive observer 

2906 and the return value of our function is ignored. 

2907 

2908 * The ``propagate=True`` flag is significant if the mapped class 

2909 includes inheriting subclasses, which would also make use of this 

2910 event listener. Without this flag, an inheriting subclass will 

2911 not use our event handler. 

2912 

2913 In the above example, the attribute set event 

2914 :meth:`.AttributeEvents.set` as well as the related validation feature 

2915 provided by :obj:`_orm.validates` is **not** invoked when we apply our 

2916 value to the given ``dict_``. To have these events to invoke in 

2917 response to our newly generated value, apply the value to the given 

2918 object as a normal attribute set operation:: 

2919 

2920 SOME_CONSTANT = 3.1415926 

2921 

2922 

2923 @event.listens_for( 

2924 MyClass.some_attribute, "init_scalar", retval=True, propagate=True 

2925 ) 

2926 def _init_some_attribute(target, dict_, value): 

2927 # will also fire off attribute set events 

2928 target.some_attribute = SOME_CONSTANT 

2929 return SOME_CONSTANT 

2930 

2931 When multiple listeners are set up, the generation of the value 

2932 is "chained" from one listener to the next by passing the value 

2933 returned by the previous listener that specifies ``retval=True`` 

2934 as the ``value`` argument of the next listener. 

2935 

2936 :param target: the object instance receiving the event. 

2937 If the listener is registered with ``raw=True``, this will 

2938 be the :class:`.InstanceState` object. 

2939 :param value: the value that is to be returned before this event 

2940 listener were invoked. This value begins as the value ``None``, 

2941 however will be the return value of the previous event handler 

2942 function if multiple listeners are present. 

2943 :param dict\_: the attribute dictionary of this mapped object. 

2944 This is normally the ``__dict__`` of the object, but in all cases 

2945 represents the destination that the attribute system uses to get 

2946 at the actual value of this attribute. Placing the value in this 

2947 dictionary has the effect that the value will be used in the 

2948 INSERT statement generated by the unit of work. 

2949 

2950 

2951 .. seealso:: 

2952 

2953 :meth:`.AttributeEvents.init_collection` - collection version 

2954 of this event 

2955 

2956 :class:`.AttributeEvents` - background on listener options such 

2957 as propagation to subclasses. 

2958 

2959 :ref:`examples_instrumentation` - see the 

2960 ``active_column_defaults.py`` example. 

2961 

2962 """ # noqa: E501 

2963 

2964 def init_collection( 

2965 self, 

2966 target: _O, 

2967 collection: Type[Collection[Any]], 

2968 collection_adapter: CollectionAdapter, 

2969 ) -> None: 

2970 """Receive a 'collection init' event. 

2971 

2972 This event is triggered for a collection-based attribute, when 

2973 the initial "empty collection" is first generated for a blank 

2974 attribute, as well as for when the collection is replaced with 

2975 a new one, such as via a set event. 

2976 

2977 E.g., given that ``User.addresses`` is a relationship-based 

2978 collection, the event is triggered here:: 

2979 

2980 u1 = User() 

2981 u1.addresses.append(a1) # <- new collection 

2982 

2983 and also during replace operations:: 

2984 

2985 u1.addresses = [a2, a3] # <- new collection 

2986 

2987 :param target: the object instance receiving the event. 

2988 If the listener is registered with ``raw=True``, this will 

2989 be the :class:`.InstanceState` object. 

2990 :param collection: the new collection. This will always be generated 

2991 from what was specified as 

2992 :paramref:`_orm.relationship.collection_class`, and will always 

2993 be empty. 

2994 :param collection_adapter: the :class:`.CollectionAdapter` that will 

2995 mediate internal access to the collection. 

2996 

2997 .. seealso:: 

2998 

2999 :class:`.AttributeEvents` - background on listener options such 

3000 as propagation to subclasses. 

3001 

3002 :meth:`.AttributeEvents.init_scalar` - "scalar" version of this 

3003 event. 

3004 

3005 """ 

3006 

3007 def dispose_collection( 

3008 self, 

3009 target: _O, 

3010 collection: Collection[Any], 

3011 collection_adapter: CollectionAdapter, 

3012 ) -> None: 

3013 """Receive a 'collection dispose' event. 

3014 

3015 This event is triggered for a collection-based attribute when 

3016 a collection is replaced, that is:: 

3017 

3018 u1.addresses.append(a1) 

3019 

3020 u1.addresses = [a2, a3] # <- old collection is disposed 

3021 

3022 The old collection received will contain its previous contents. 

3023 

3024 .. versionchanged:: 1.2 The collection passed to 

3025 :meth:`.AttributeEvents.dispose_collection` will now have its 

3026 contents before the dispose intact; previously, the collection 

3027 would be empty. 

3028 

3029 .. seealso:: 

3030 

3031 :class:`.AttributeEvents` - background on listener options such 

3032 as propagation to subclasses. 

3033 

3034 """ 

3035 

3036 def modified(self, target: _O, initiator: Event) -> None: 

3037 """Receive a 'modified' event. 

3038 

3039 This event is triggered when the :func:`.attributes.flag_modified` 

3040 function is used to trigger a modify event on an attribute without 

3041 any specific value being set. 

3042 

3043 .. versionadded:: 1.2 

3044 

3045 :param target: the object instance receiving the event. 

3046 If the listener is registered with ``raw=True``, this will 

3047 be the :class:`.InstanceState` object. 

3048 

3049 :param initiator: An instance of :class:`.attributes.Event` 

3050 representing the initiation of the event. 

3051 

3052 .. seealso:: 

3053 

3054 :class:`.AttributeEvents` - background on listener options such 

3055 as propagation to subclasses. 

3056 

3057 """ 

3058 

3059 

3060class QueryEvents(event.Events[Query[Any]]): 

3061 """Represent events within the construction of a :class:`_query.Query` 

3062 object. 

3063 

3064 .. legacy:: The :class:`_orm.QueryEvents` event methods are legacy 

3065 as of SQLAlchemy 2.0, and only apply to direct use of the 

3066 :class:`_orm.Query` object. They are not used for :term:`2.0 style` 

3067 statements. For events to intercept and modify 2.0 style ORM use, 

3068 use the :meth:`_orm.SessionEvents.do_orm_execute` hook. 

3069 

3070 

3071 The :class:`_orm.QueryEvents` hooks are now superseded by the 

3072 :meth:`_orm.SessionEvents.do_orm_execute` event hook. 

3073 

3074 """ 

3075 

3076 _target_class_doc = "SomeQuery" 

3077 _dispatch_target = Query 

3078 

3079 def before_compile(self, query: Query[Any]) -> None: 

3080 """Receive the :class:`_query.Query` 

3081 object before it is composed into a 

3082 core :class:`_expression.Select` object. 

3083 

3084 .. deprecated:: 1.4 The :meth:`_orm.QueryEvents.before_compile` event 

3085 is superseded by the much more capable 

3086 :meth:`_orm.SessionEvents.do_orm_execute` hook. In version 1.4, 

3087 the :meth:`_orm.QueryEvents.before_compile` event is **no longer 

3088 used** for ORM-level attribute loads, such as loads of deferred 

3089 or expired attributes as well as relationship loaders. See the 

3090 new examples in :ref:`examples_session_orm_events` which 

3091 illustrate new ways of intercepting and modifying ORM queries 

3092 for the most common purpose of adding arbitrary filter criteria. 

3093 

3094 

3095 This event is intended to allow changes to the query given:: 

3096 

3097 @event.listens_for(Query, "before_compile", retval=True) 

3098 def no_deleted(query): 

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 return query 

3104 

3105 The event should normally be listened with the ``retval=True`` 

3106 parameter set, so that the modified query may be returned. 

3107 

3108 The :meth:`.QueryEvents.before_compile` event by default 

3109 will disallow "baked" queries from caching a query, if the event 

3110 hook returns a new :class:`_query.Query` object. 

3111 This affects both direct 

3112 use of the baked query extension as well as its operation within 

3113 lazy loaders and eager loaders for relationships. In order to 

3114 re-establish the query being cached, apply the event adding the 

3115 ``bake_ok`` flag:: 

3116 

3117 @event.listens_for(Query, "before_compile", retval=True, bake_ok=True) 

3118 def my_event(query): 

3119 for desc in query.column_descriptions: 

3120 if desc["type"] is User: 

3121 entity = desc["entity"] 

3122 query = query.filter(entity.deleted == False) 

3123 return query 

3124 

3125 When ``bake_ok`` is set to True, the event hook will only be invoked 

3126 once, and not called for subsequent invocations of a particular query 

3127 that is being cached. 

3128 

3129 .. versionadded:: 1.3.11 - added the "bake_ok" flag to the 

3130 :meth:`.QueryEvents.before_compile` event and disallowed caching via 

3131 the "baked" extension from occurring for event handlers that 

3132 return a new :class:`_query.Query` object if this flag is not set. 

3133 

3134 .. seealso:: 

3135 

3136 :meth:`.QueryEvents.before_compile_update` 

3137 

3138 :meth:`.QueryEvents.before_compile_delete` 

3139 

3140 :ref:`baked_with_before_compile` 

3141 

3142 """ # noqa: E501 

3143 

3144 def before_compile_update( 

3145 self, query: Query[Any], update_context: BulkUpdate 

3146 ) -> None: 

3147 """Allow modifications to the :class:`_query.Query` object within 

3148 :meth:`_query.Query.update`. 

3149 

3150 .. deprecated:: 1.4 The :meth:`_orm.QueryEvents.before_compile_update` 

3151 event is superseded by the much more capable 

3152 :meth:`_orm.SessionEvents.do_orm_execute` hook. 

3153 

3154 Like the :meth:`.QueryEvents.before_compile` event, if the event 

3155 is to be used to alter the :class:`_query.Query` object, it should 

3156 be configured with ``retval=True``, and the modified 

3157 :class:`_query.Query` object returned, as in :: 

3158 

3159 @event.listens_for(Query, "before_compile_update", retval=True) 

3160 def no_deleted(query, update_context): 

3161 for desc in query.column_descriptions: 

3162 if desc["type"] is User: 

3163 entity = desc["entity"] 

3164 query = query.filter(entity.deleted == False) 

3165 

3166 update_context.values["timestamp"] = datetime.datetime.now( 

3167 datetime.UTC 

3168 ) 

3169 return query 

3170 

3171 The ``.values`` dictionary of the "update context" object can also 

3172 be modified in place as illustrated above. 

3173 

3174 :param query: a :class:`_query.Query` instance; this is also 

3175 the ``.query`` attribute of the given "update context" 

3176 object. 

3177 

3178 :param update_context: an "update context" object which is 

3179 the same kind of object as described in 

3180 :paramref:`.QueryEvents.after_bulk_update.update_context`. 

3181 The object has a ``.values`` attribute in an UPDATE context which is 

3182 the dictionary of parameters passed to :meth:`_query.Query.update`. 

3183 This 

3184 dictionary can be modified to alter the VALUES clause of the 

3185 resulting UPDATE statement. 

3186 

3187 .. versionadded:: 1.2.17 

3188 

3189 .. seealso:: 

3190 

3191 :meth:`.QueryEvents.before_compile` 

3192 

3193 :meth:`.QueryEvents.before_compile_delete` 

3194 

3195 

3196 """ # noqa: E501 

3197 

3198 def before_compile_delete( 

3199 self, query: Query[Any], delete_context: BulkDelete 

3200 ) -> None: 

3201 """Allow modifications to the :class:`_query.Query` object within 

3202 :meth:`_query.Query.delete`. 

3203 

3204 .. deprecated:: 1.4 The :meth:`_orm.QueryEvents.before_compile_delete` 

3205 event is superseded by the much more capable 

3206 :meth:`_orm.SessionEvents.do_orm_execute` hook. 

3207 

3208 Like the :meth:`.QueryEvents.before_compile` event, this event 

3209 should be configured with ``retval=True``, and the modified 

3210 :class:`_query.Query` object returned, as in :: 

3211 

3212 @event.listens_for(Query, "before_compile_delete", retval=True) 

3213 def no_deleted(query, delete_context): 

3214 for desc in query.column_descriptions: 

3215 if desc["type"] is User: 

3216 entity = desc["entity"] 

3217 query = query.filter(entity.deleted == False) 

3218 return query 

3219 

3220 :param query: a :class:`_query.Query` instance; this is also 

3221 the ``.query`` attribute of the given "delete context" 

3222 object. 

3223 

3224 :param delete_context: a "delete context" object which is 

3225 the same kind of object as described in 

3226 :paramref:`.QueryEvents.after_bulk_delete.delete_context`. 

3227 

3228 .. versionadded:: 1.2.17 

3229 

3230 .. seealso:: 

3231 

3232 :meth:`.QueryEvents.before_compile` 

3233 

3234 :meth:`.QueryEvents.before_compile_update` 

3235 

3236 

3237 """ 

3238 

3239 @classmethod 

3240 def _listen( 

3241 cls, 

3242 event_key: _EventKey[_ET], 

3243 retval: bool = False, 

3244 bake_ok: bool = False, 

3245 **kw: Any, 

3246 ) -> None: 

3247 fn = event_key._listen_fn 

3248 

3249 if not retval: 

3250 

3251 def wrap(*arg: Any, **kw: Any) -> Any: 

3252 if not retval: 

3253 query = arg[0] 

3254 fn(*arg, **kw) 

3255 return query 

3256 else: 

3257 return fn(*arg, **kw) 

3258 

3259 event_key = event_key.with_wrapper(wrap) 

3260 else: 

3261 # don't assume we can apply an attribute to the callable 

3262 def wrap(*arg: Any, **kw: Any) -> Any: 

3263 return fn(*arg, **kw) 

3264 

3265 event_key = event_key.with_wrapper(wrap) 

3266 

3267 wrap._bake_ok = bake_ok # type: ignore [attr-defined] 

3268 

3269 event_key.base_listen(**kw)