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. 

9 

10""" 

11from __future__ import annotations 

12 

13from typing import Any 

14from typing import Callable 

15from typing import Collection 

16from typing import Dict 

17from typing import Generic 

18from typing import Iterable 

19from typing import Optional 

20from typing import Sequence 

21from typing import Set 

22from typing import Type 

23from typing import TYPE_CHECKING 

24from typing import TypeVar 

25from typing import Union 

26import weakref 

27 

28from . import instrumentation 

29from . import interfaces 

30from . import mapperlib 

31from .attributes import QueryableAttribute 

32from .base import _mapper_or_none 

33from .base import NO_KEY 

34from .instrumentation import ClassManager 

35from .instrumentation import InstrumentationFactory 

36from .query import BulkDelete 

37from .query import BulkUpdate 

38from .query import Query 

39from .scoping import scoped_session 

40from .session import Session 

41from .session import sessionmaker 

42from .. import event 

43from .. import exc 

44from .. import util 

45from ..event import EventTarget 

46from ..event.registry import _ET 

47from ..util.compat import inspect_getfullargspec 

48 

49if TYPE_CHECKING: 

50 from weakref import ReferenceType 

51 

52 from ._typing import _InstanceDict 

53 from ._typing import _InternalEntityType 

54 from ._typing import _O 

55 from ._typing import _T 

56 from .attributes import Event 

57 from .base import EventConstants 

58 from .session import ORMExecuteState 

59 from .session import SessionTransaction 

60 from .unitofwork import UOWTransaction 

61 from ..engine import Connection 

62 from ..event.base import _Dispatch 

63 from ..event.base import _HasEventsDispatch 

64 from ..event.registry import _EventKey 

65 from ..orm.collections import CollectionAdapter 

66 from ..orm.context import QueryContext 

67 from ..orm.decl_api import DeclarativeAttributeIntercept 

68 from ..orm.decl_api import DeclarativeMeta 

69 from ..orm.mapper import Mapper 

70 from ..orm.state import InstanceState 

71 

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

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

74 

75 

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

77 """Events related to class instrumentation events. 

78 

79 The listeners here support being established against 

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

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

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

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

84 of that class as well. 

85 

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

87 which when used has the effect of events being emitted 

88 for all classes. 

89 

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

91 unlike the other class level events where it defaults 

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

93 be the subject of these events, when a listener 

94 is established on a superclass. 

95 

96 """ 

97 

98 _target_class_doc = "SomeBaseClass" 

99 _dispatch_target = InstrumentationFactory 

100 

101 @classmethod 

102 def _accept_with( 

103 cls, 

104 target: Union[ 

105 InstrumentationFactory, 

106 Type[InstrumentationFactory], 

107 ], 

108 identifier: str, 

109 ) -> Optional[ 

110 Union[ 

111 InstrumentationFactory, 

112 Type[InstrumentationFactory], 

113 ] 

114 ]: 

115 if isinstance(target, type): 

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

117 else: 

118 return None 

119 

120 @classmethod 

121 def _listen( 

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

123 ) -> None: 

124 target, identifier, fn = ( 

125 event_key.dispatch_target, 

126 event_key.identifier, 

127 event_key._listen_fn, 

128 ) 

129 

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

131 listen_cls = target() 

132 

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

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

135 # between mapper/registry/instrumentation_manager, however this 

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

137 if listen_cls is None: 

138 return None 

139 

140 if propagate and issubclass(target_cls, listen_cls): 

141 return fn(target_cls, *arg) 

142 elif not propagate and target_cls is listen_cls: 

143 return fn(target_cls, *arg) 

144 else: 

145 return None 

146 

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

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

149 None, 

150 identifier, 

151 listen, 

152 instrumentation._instrumentation_factory, 

153 ) 

154 getattr( 

155 instrumentation._instrumentation_factory.dispatch, identifier 

156 ).remove(key) 

157 

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

159 

160 event_key.with_dispatch_target( 

161 instrumentation._instrumentation_factory 

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

163 

164 @classmethod 

165 def _clear(cls) -> None: 

166 super()._clear() 

167 instrumentation._instrumentation_factory.dispatch._clear() 

168 

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

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

171 

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

173 :func:`.manager_of_class`. 

174 

175 """ 

176 

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

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

179 

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

181 :func:`.manager_of_class`. 

182 

183 """ 

184 

185 def attribute_instrument( 

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

187 ) -> None: 

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

189 

190 

191class _InstrumentationEventsHold: 

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

193 _listen() on the InstrumentationEvents class. 

194 

195 """ 

196 

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

198 self.class_ = class_ 

199 

200 dispatch = event.dispatcher(InstrumentationEvents) 

201 

202 

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

204 """Define events specific to object lifecycle. 

205 

206 e.g.:: 

207 

208 from sqlalchemy import event 

209 

210 

211 def my_load_listener(target, context): 

212 print("on load!") 

213 

214 

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

216 

217 Available targets include: 

218 

219 * mapped classes 

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

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

222 * :class:`_orm.Mapper` objects 

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

224 mappers. 

225 

226 Instance events are closely related to mapper events, but 

227 are more specific to the instance and its instrumentation, 

228 rather than its system of persistence. 

229 

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

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

232 

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

234 be applied to all inheriting classes as well as the 

235 class which is the target of this listener. 

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

237 to applicable event listener functions will be the 

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

239 object, rather than the mapped instance itself. 

240 :param restore_load_context=False: Applies to the 

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

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

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

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

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

246 events if this flag is not set. 

247 

248 .. versionadded:: 1.3.14 

249 

250 

251 """ 

252 

253 _target_class_doc = "SomeClass" 

254 

255 _dispatch_target = ClassManager 

256 

257 @classmethod 

258 def _new_classmanager_instance( 

259 cls, 

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

261 classmanager: ClassManager[_O], 

262 ) -> None: 

263 _InstanceEventsHold.populate(class_, classmanager) 

264 

265 @classmethod 

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

267 def _accept_with( 

268 cls, 

269 target: Union[ 

270 ClassManager[Any], 

271 Type[ClassManager[Any]], 

272 ], 

273 identifier: str, 

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

275 orm = util.preloaded.orm 

276 

277 if isinstance(target, ClassManager): 

278 return target 

279 elif isinstance(target, mapperlib.Mapper): 

280 return target.class_manager 

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

282 util.warn_deprecated( 

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

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

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

286 "2.0", 

287 ) 

288 return ClassManager 

289 elif isinstance(target, type): 

290 if issubclass(target, mapperlib.Mapper): 

291 return ClassManager 

292 else: 

293 manager = instrumentation.opt_manager_of_class(target) 

294 if manager: 

295 return manager 

296 else: 

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

298 return None 

299 

300 @classmethod 

301 def _listen( 

302 cls, 

303 event_key: _EventKey[ClassManager[Any]], 

304 raw: bool = False, 

305 propagate: bool = False, 

306 restore_load_context: bool = False, 

307 **kw: Any, 

308 ) -> None: 

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

310 

311 if not raw or restore_load_context: 

312 

313 def wrap( 

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

315 ) -> Optional[Any]: 

316 if not raw: 

317 target: Any = state.obj() 

318 else: 

319 target = state 

320 if restore_load_context: 

321 runid = state.runid 

322 try: 

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

324 finally: 

325 if restore_load_context: 

326 state.runid = runid 

327 

328 event_key = event_key.with_wrapper(wrap) 

329 

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

331 

332 if propagate: 

333 for mgr in target.subclass_managers(True): 

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

335 

336 @classmethod 

337 def _clear(cls) -> None: 

338 super()._clear() 

339 _InstanceEventsHold._clear() 

340 

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

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

343 

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

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

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

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

348 

349 """ 

350 

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

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

353 

354 This method is only called during a userland construction of 

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

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

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

358 event in order to intercept a database load. 

359 

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

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

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

363 ``__init__``. 

364 

365 :param target: the mapped instance. If 

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

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

368 object associated with the instance. 

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

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

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

372 This structure *can* be altered in place. 

373 

374 .. seealso:: 

375 

376 :meth:`.InstanceEvents.init_failure` 

377 

378 :meth:`.InstanceEvents.load` 

379 

380 """ 

381 

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

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

384 and raised an exception. 

385 

386 This method is only called during a userland construction of 

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

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

389 from the database. 

390 

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

392 method is caught. After the event 

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

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

395 actual exception and stack trace raised should be present in 

396 ``sys.exc_info()``. 

397 

398 :param target: the mapped instance. If 

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

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

401 object associated with the instance. 

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

403 method. 

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

405 method. 

406 

407 .. seealso:: 

408 

409 :meth:`.InstanceEvents.init` 

410 

411 :meth:`.InstanceEvents.load` 

412 

413 """ 

414 

415 def _sa_event_merge_wo_load( 

416 self, target: _O, context: QueryContext 

417 ) -> None: 

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

419 call, when load=False was passed. 

420 

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

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

423 overwrite operation does not use attribute events, instead just 

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

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

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

427 

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

429 

430 .. versionadded:: 1.4.41 

431 

432 """ 

433 

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

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

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

437 occurred. 

438 

439 This typically occurs when the instance is created based on 

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

441 instance's lifetime. 

442 

443 .. warning:: 

444 

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

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

447 eager loading with collection-oriented attributes, the additional 

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

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

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

451 if an operation occurs within this event handler that emits 

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

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

454 existing eager loaders still in progress. 

455 

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

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

458 

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

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

461 

462 * accessing attributes on a joined-inheritance subclass that 

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

464 

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

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

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

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

469 event is called:: 

470 

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

472 def on_load(instance, context): 

473 instance.some_unloaded_attribute 

474 

475 .. versionchanged:: 1.3.14 Added 

476 :paramref:`.InstanceEvents.restore_load_context` 

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

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

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

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

481 changes without this flag being set. 

482 

483 

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

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

486 

487 :param target: the mapped instance. If 

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

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

490 object associated with the instance. 

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

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

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

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

495 

496 .. seealso:: 

497 

498 :ref:`mapped_class_load_events` 

499 

500 :meth:`.InstanceEvents.init` 

501 

502 :meth:`.InstanceEvents.refresh` 

503 

504 :meth:`.SessionEvents.loaded_as_persistent` 

505 

506 """ # noqa: E501 

507 

508 def refresh( 

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

510 ) -> None: 

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

512 been refreshed from a query. 

513 

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

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

516 

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

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

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

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

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

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

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

524 order to resolve this scenario. 

525 

526 :param target: the mapped instance. If 

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

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

529 object associated with the instance. 

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

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

532 :param attrs: sequence of attribute names which 

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

534 attributes were populated. 

535 

536 .. seealso:: 

537 

538 :ref:`mapped_class_load_events` 

539 

540 :meth:`.InstanceEvents.load` 

541 

542 """ 

543 

544 def refresh_flush( 

545 self, 

546 target: _O, 

547 flush_context: UOWTransaction, 

548 attrs: Optional[Iterable[str]], 

549 ) -> None: 

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

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

552 during persistence of the object's state. 

553 

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

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

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

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

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

559 

560 .. note:: 

561 

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

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

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

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

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

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

568 intercept the newly INSERTed state of an object, the 

569 :meth:`.SessionEvents.pending_to_persistent` and 

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

571 

572 :param target: the mapped instance. If 

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

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

575 object associated with the instance. 

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

577 which handles the details of the flush. 

578 :param attrs: sequence of attribute names which 

579 were populated. 

580 

581 .. seealso:: 

582 

583 :ref:`mapped_class_load_events` 

584 

585 :ref:`orm_server_defaults` 

586 

587 :ref:`metadata_defaults_toplevel` 

588 

589 """ 

590 

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

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

593 have been expired. 

594 

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

596 state was expired. 

597 

598 :param target: the mapped instance. If 

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

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

601 object associated with the instance. 

602 :param attrs: sequence of attribute 

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

604 expired. 

605 

606 """ 

607 

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

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

610 being pickled. 

611 

612 :param target: the mapped instance. If 

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

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

615 object associated with the instance. 

616 :param state_dict: the dictionary returned by 

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

618 to be pickled. 

619 

620 """ 

621 

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

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

624 been unpickled. 

625 

626 :param target: the mapped instance. If 

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

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

629 object associated with the instance. 

630 :param state_dict: the dictionary sent to 

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

632 dictionary which was pickled. 

633 

634 """ 

635 

636 

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

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

639 

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

641 those objects are created for that class. 

642 

643 """ 

644 

645 all_holds: weakref.WeakKeyDictionary[Any, Any] 

646 

647 def __init__( 

648 self, 

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

650 ) -> None: 

651 self.class_ = class_ 

652 

653 @classmethod 

654 def _clear(cls) -> None: 

655 cls.all_holds.clear() 

656 

657 class HoldEvents(Generic[_ET2]): 

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

659 

660 @classmethod 

661 def _listen( 

662 cls, 

663 event_key: _EventKey[_ET2], 

664 raw: bool = False, 

665 propagate: bool = False, 

666 retval: bool = False, 

667 **kw: Any, 

668 ) -> None: 

669 target = event_key.dispatch_target 

670 

671 if target.class_ in target.all_holds: 

672 collection = target.all_holds[target.class_] 

673 else: 

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

675 

676 event.registry._stored_in_collection(event_key, target) 

677 collection[event_key._key] = ( 

678 event_key, 

679 raw, 

680 propagate, 

681 retval, 

682 kw, 

683 ) 

684 

685 if propagate: 

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

687 while stack: 

688 subclass = stack.pop(0) 

689 stack.extend(subclass.__subclasses__()) 

690 subject = target.resolve(subclass) 

691 if subject is not None: 

692 # we are already going through __subclasses__() 

693 # so leave generic propagate flag False 

694 event_key.with_dispatch_target(subject).listen( 

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

696 ) 

697 

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

699 target = event_key.dispatch_target 

700 

701 if isinstance(target, _EventsHold): 

702 collection = target.all_holds[target.class_] 

703 del collection[event_key._key] 

704 

705 @classmethod 

706 def populate( 

707 cls, 

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

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

710 ) -> None: 

711 for subclass in class_.__mro__: 

712 if subclass in cls.all_holds: 

713 collection = cls.all_holds[subclass] 

714 for ( 

715 event_key, 

716 raw, 

717 propagate, 

718 retval, 

719 kw, 

720 ) in collection.values(): 

721 if propagate or subclass is class_: 

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

723 # classes in a hierarchy are triggered with 

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

725 # assignment, instead of using the generic propagate 

726 # flag. 

727 event_key.with_dispatch_target(subject).listen( 

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

729 ) 

730 

731 

732class _InstanceEventsHold(_EventsHold[_ET]): 

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

734 weakref.WeakKeyDictionary() 

735 ) 

736 

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

738 return instrumentation.opt_manager_of_class(class_) 

739 

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

741 pass 

742 

743 dispatch = event.dispatcher(HoldInstanceEvents) 

744 

745 

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

747 """Define events specific to mappings. 

748 

749 e.g.:: 

750 

751 from sqlalchemy import event 

752 

753 

754 def my_before_insert_listener(mapper, connection, target): 

755 # execute a stored procedure upon INSERT, 

756 # apply the value to the row to be inserted 

757 target.calculated_value = connection.execute( 

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

759 ).scalar() 

760 

761 

762 # associate the listener function with SomeClass, 

763 # to execute during the "before_insert" hook 

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

765 

766 Available targets include: 

767 

768 * mapped classes 

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

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

771 * :class:`_orm.Mapper` objects 

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

773 mappers. 

774 

775 Mapper events provide hooks into critical sections of the 

776 mapper, including those related to object instrumentation, 

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

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

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

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

781 methods operate with several significant restrictions. The 

782 user is encouraged to evaluate the 

783 :meth:`.SessionEvents.before_flush` and 

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

785 flexible and user-friendly hooks in which to apply 

786 additional database state during a flush. 

787 

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

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

790 

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

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

793 inheriting classes, as well as any 

794 mapper which is the target of this listener. 

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

796 to applicable event listener functions will be the 

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

798 object, rather than the mapped instance itself. 

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

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

801 control subsequent event propagation, or to otherwise alter 

802 the operation in progress by the mapper. Possible return 

803 values are: 

804 

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

806 processing normally. 

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

808 event handlers in the chain. 

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

810 

811 """ 

812 

813 _target_class_doc = "SomeClass" 

814 _dispatch_target = mapperlib.Mapper 

815 

816 @classmethod 

817 def _new_mapper_instance( 

818 cls, 

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

820 mapper: Mapper[_O], 

821 ) -> None: 

822 _MapperEventsHold.populate(class_, mapper) 

823 

824 @classmethod 

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

826 def _accept_with( 

827 cls, 

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

829 identifier: str, 

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

831 orm = util.preloaded.orm 

832 

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

834 util.warn_deprecated( 

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

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

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

838 "2.0", 

839 ) 

840 return mapperlib.Mapper 

841 elif isinstance(target, type): 

842 if issubclass(target, mapperlib.Mapper): 

843 return target 

844 else: 

845 mapper = _mapper_or_none(target) 

846 if mapper is not None: 

847 return mapper 

848 else: 

849 return _MapperEventsHold(target) 

850 else: 

851 return target 

852 

853 @classmethod 

854 def _listen( 

855 cls, 

856 event_key: _EventKey[_ET], 

857 raw: bool = False, 

858 retval: bool = False, 

859 propagate: bool = False, 

860 **kw: Any, 

861 ) -> None: 

862 target, identifier, fn = ( 

863 event_key.dispatch_target, 

864 event_key.identifier, 

865 event_key._listen_fn, 

866 ) 

867 

868 if ( 

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

870 and target is not mapperlib.Mapper 

871 ): 

872 util.warn( 

873 "'before_configured' and 'after_configured' ORM events " 

874 "only invoke with the Mapper class " 

875 "as the target." 

876 ) 

877 

878 if not raw or not retval: 

879 if not raw: 

880 meth = getattr(cls, identifier) 

881 try: 

882 target_index = ( 

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

884 ) 

885 except ValueError: 

886 target_index = None 

887 

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

889 if not raw and target_index is not None: 

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

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

892 if not retval: 

893 fn(*arg, **kw) 

894 return interfaces.EXT_CONTINUE 

895 else: 

896 return fn(*arg, **kw) 

897 

898 event_key = event_key.with_wrapper(wrap) 

899 

900 if propagate: 

901 for mapper in target.self_and_descendants: 

902 event_key.with_dispatch_target(mapper).base_listen( 

903 propagate=True, **kw 

904 ) 

905 else: 

906 event_key.base_listen(**kw) 

907 

908 @classmethod 

909 def _clear(cls) -> None: 

910 super()._clear() 

911 _MapperEventsHold._clear() 

912 

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

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

915 before instrumentation is applied to the mapped class. 

916 

917 This event is the earliest phase of mapper construction. 

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

919 receive an event within initial mapper construction where basic 

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

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

922 be a better choice. 

923 

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

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

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

927 

928 Base = declarative_base() 

929 

930 

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

932 def on_new_class(mapper, cls_): 

933 "..." 

934 

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

936 of this event. 

937 :param class\_: the mapped class. 

938 

939 .. seealso:: 

940 

941 :meth:`_orm.MapperEvents.after_mapper_constructed` 

942 

943 """ 

944 

945 def after_mapper_constructed( 

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

947 ) -> None: 

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

949 fully constructed. 

950 

951 This event is called after the initial constructor for 

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

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

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

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

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

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

958 

959 This event differs from the 

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

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

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

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

964 wish to create additional mapped classes in response to the 

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

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

967 

968 .. versionadded:: 2.0.2 

969 

970 .. seealso:: 

971 

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

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

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

975 objects. 

976 

977 """ 

978 

979 def before_mapper_configured( 

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

981 ) -> None: 

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

983 

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

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

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

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

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

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

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

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

992 available mappers. 

993 

994 In comparison to the other configure-level events, 

995 :meth:`.MapperEvents.before_configured`, 

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

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

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

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

1000 parameter. 

1001 

1002 .. versionadded:: 1.3 

1003 

1004 e.g.:: 

1005 

1006 from sqlalchemy.orm import EXT_SKIP 

1007 

1008 Base = declarative_base() 

1009 

1010 DontConfigureBase = declarative_base() 

1011 

1012 

1013 @event.listens_for( 

1014 DontConfigureBase, 

1015 "before_mapper_configured", 

1016 retval=True, 

1017 propagate=True, 

1018 ) 

1019 def dont_configure(mapper, cls): 

1020 return EXT_SKIP 

1021 

1022 .. seealso:: 

1023 

1024 :meth:`.MapperEvents.before_configured` 

1025 

1026 :meth:`.MapperEvents.after_configured` 

1027 

1028 :meth:`.MapperEvents.mapper_configured` 

1029 

1030 """ 

1031 

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

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

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

1035 

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

1037 for each mapper that is encountered when the 

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

1039 list of not-yet-configured mappers. 

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

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

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

1043 detected. 

1044 

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

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

1047 other mappers; they might still be pending within the 

1048 configuration operation. Bidirectional relationships that 

1049 are instead configured via the 

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

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

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

1053 exist. 

1054 

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

1056 to go including backrefs that are defined only on other 

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

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

1059 fully configured. 

1060 

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

1062 :meth:`.MapperEvents.before_configured` or 

1063 :meth:`.MapperEvents.after_configured`, 

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

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

1066 a particular mapper. The event is therefore useful for 

1067 configurational steps that benefit from being invoked just once 

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

1069 configurations are necessarily ready yet. 

1070 

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

1072 of this event. 

1073 :param class\_: the mapped class. 

1074 

1075 .. seealso:: 

1076 

1077 :meth:`.MapperEvents.before_configured` 

1078 

1079 :meth:`.MapperEvents.after_configured` 

1080 

1081 :meth:`.MapperEvents.before_mapper_configured` 

1082 

1083 """ 

1084 # TODO: need coverage for this event 

1085 

1086 def before_configured(self) -> None: 

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

1088 

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

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

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

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

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

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

1095 detected. 

1096 

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

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

1099 for all mappings as a whole:: 

1100 

1101 from sqlalchemy.orm import Mapper 

1102 

1103 

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

1105 def go(): ... 

1106 

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

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

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

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

1111 on a per-mapper basis. 

1112 

1113 Theoretically this event is called once per 

1114 application, but is actually called any time new mappers 

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

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

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

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

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

1120 

1121 from sqlalchemy.orm import mapper 

1122 

1123 

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

1125 def go(): ... 

1126 

1127 .. seealso:: 

1128 

1129 :meth:`.MapperEvents.before_mapper_configured` 

1130 

1131 :meth:`.MapperEvents.mapper_configured` 

1132 

1133 :meth:`.MapperEvents.after_configured` 

1134 

1135 """ 

1136 

1137 def after_configured(self) -> None: 

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

1139 

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

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

1142 invoked, after the function has completed its work. 

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

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

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

1146 detected. 

1147 

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

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

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

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

1152 available for any mappers that were pending. 

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

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

1155 

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

1157 and not to individual mappings or 

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

1159 

1160 from sqlalchemy.orm import Mapper 

1161 

1162 

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

1164 def go(): ... 

1165 

1166 Theoretically this event is called once per 

1167 application, but is actually called any time new mappers 

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

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

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

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

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

1173 

1174 from sqlalchemy.orm import mapper 

1175 

1176 

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

1178 def go(): ... 

1179 

1180 .. seealso:: 

1181 

1182 :meth:`.MapperEvents.before_mapper_configured` 

1183 

1184 :meth:`.MapperEvents.mapper_configured` 

1185 

1186 :meth:`.MapperEvents.before_configured` 

1187 

1188 """ 

1189 

1190 def before_insert( 

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

1192 ) -> None: 

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

1194 is emitted corresponding to that instance. 

1195 

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

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

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

1199 :ref:`orm_expression_update_delete`. To intercept ORM 

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

1201 

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

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

1204 as to emit additional SQL statements on the given 

1205 connection. 

1206 

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

1208 same class before their INSERT statements are emitted at 

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

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

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

1212 batches of instances to be broken up into individual 

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

1214 steps. 

1215 

1216 .. warning:: 

1217 

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

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

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

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

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

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

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

1225 

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

1227 of this event. 

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

1229 emit INSERT statements for this instance. This 

1230 provides a handle into the current transaction on the 

1231 target database specific to this instance. 

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

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

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

1235 object associated with the instance. 

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

1237 

1238 .. seealso:: 

1239 

1240 :ref:`session_persistence_events` 

1241 

1242 """ 

1243 

1244 def after_insert( 

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

1246 ) -> None: 

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

1248 is emitted corresponding to that instance. 

1249 

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

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

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

1253 :ref:`orm_expression_update_delete`. To intercept ORM 

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

1255 

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

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

1258 as to emit additional SQL statements on the given 

1259 connection. 

1260 

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

1262 same class after their INSERT statements have been 

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

1264 rare case that this is not desirable, the 

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

1266 which will cause batches of instances to be broken up 

1267 into individual (and more poorly performing) 

1268 event->persist->event steps. 

1269 

1270 .. warning:: 

1271 

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

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

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

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

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

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

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

1279 

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

1281 of this event. 

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

1283 emit INSERT statements for this instance. This 

1284 provides a handle into the current transaction on the 

1285 target database specific to this instance. 

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

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

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

1289 object associated with the instance. 

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

1291 

1292 .. seealso:: 

1293 

1294 :ref:`session_persistence_events` 

1295 

1296 """ 

1297 

1298 def before_update( 

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

1300 ) -> None: 

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

1302 is emitted corresponding to that instance. 

1303 

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

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

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

1307 :ref:`orm_expression_update_delete`. To intercept ORM 

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

1309 

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

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

1312 as to emit additional SQL statements on the given 

1313 connection. 

1314 

1315 This method is called for all instances that are 

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

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

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

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

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

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

1322 statement will be issued. This means that an instance 

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

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

1325 issued, although you can affect the outcome here by 

1326 modifying attributes so that a net change in value does 

1327 exist. 

1328 

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

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

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

1332 include_collections=False)``. 

1333 

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

1335 same class before their UPDATE statements are emitted at 

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

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

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

1339 batches of instances to be broken up into individual 

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

1341 steps. 

1342 

1343 .. warning:: 

1344 

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

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

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

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

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

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

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

1352 

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

1354 of this event. 

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

1356 emit UPDATE statements for this instance. This 

1357 provides a handle into the current transaction on the 

1358 target database specific to this instance. 

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

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

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

1362 object associated with the instance. 

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

1364 

1365 .. seealso:: 

1366 

1367 :ref:`session_persistence_events` 

1368 

1369 """ 

1370 

1371 def after_update( 

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

1373 ) -> None: 

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

1375 is emitted corresponding to that instance. 

1376 

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

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

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

1380 :ref:`orm_expression_update_delete`. To intercept ORM 

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

1382 

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

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

1385 as to emit additional SQL statements on the given 

1386 connection. 

1387 

1388 This method is called for all instances that are 

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

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

1391 no UPDATE statement has proceeded. An object is marked 

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

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

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

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

1396 statement will be issued. This means that an instance 

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

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

1399 issued. 

1400 

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

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

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

1404 include_collections=False)``. 

1405 

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

1407 same class after their UPDATE statements have been emitted at 

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

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

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

1411 batches of instances to be broken up into individual 

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

1413 steps. 

1414 

1415 .. warning:: 

1416 

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

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

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

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

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

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

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

1424 

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

1426 of this event. 

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

1428 emit UPDATE statements for this instance. This 

1429 provides a handle into the current transaction on the 

1430 target database specific to this instance. 

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

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

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

1434 object associated with the instance. 

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

1436 

1437 .. seealso:: 

1438 

1439 :ref:`session_persistence_events` 

1440 

1441 """ 

1442 

1443 def before_delete( 

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

1445 ) -> None: 

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

1447 is emitted corresponding to that instance. 

1448 

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

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

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

1452 :ref:`orm_expression_update_delete`. To intercept ORM 

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

1454 

1455 This event is used to emit additional SQL statements on 

1456 the given connection as well as to perform application 

1457 specific bookkeeping related to a deletion event. 

1458 

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

1460 same class before their DELETE statements are emitted at 

1461 once in a later step. 

1462 

1463 .. warning:: 

1464 

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

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

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

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

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

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

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

1472 

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

1474 of this event. 

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

1476 emit DELETE statements for this instance. This 

1477 provides a handle into the current transaction on the 

1478 target database specific to this instance. 

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

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

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

1482 object associated with the instance. 

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

1484 

1485 .. seealso:: 

1486 

1487 :ref:`session_persistence_events` 

1488 

1489 """ 

1490 

1491 def after_delete( 

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

1493 ) -> None: 

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

1495 has been emitted corresponding to that instance. 

1496 

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

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

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

1500 :ref:`orm_expression_update_delete`. To intercept ORM 

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

1502 

1503 This event is used to emit additional SQL statements on 

1504 the given connection as well as to perform application 

1505 specific bookkeeping related to a deletion event. 

1506 

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

1508 same class after their DELETE statements have been emitted at 

1509 once in a previous step. 

1510 

1511 .. warning:: 

1512 

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

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

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

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

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

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

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

1520 

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

1522 of this event. 

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

1524 emit DELETE statements for this instance. This 

1525 provides a handle into the current transaction on the 

1526 target database specific to this instance. 

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

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

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

1530 object associated with the instance. 

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

1532 

1533 .. seealso:: 

1534 

1535 :ref:`session_persistence_events` 

1536 

1537 """ 

1538 

1539 

1540class _MapperEventsHold(_EventsHold[_ET]): 

1541 all_holds = weakref.WeakKeyDictionary() 

1542 

1543 def resolve( 

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

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

1546 return _mapper_or_none(class_) 

1547 

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

1549 pass 

1550 

1551 dispatch = event.dispatcher(HoldMapperEvents) 

1552 

1553 

1554_sessionevents_lifecycle_event_names: Set[str] = set() 

1555 

1556 

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

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

1559 

1560 e.g.:: 

1561 

1562 from sqlalchemy import event 

1563 from sqlalchemy.orm import sessionmaker 

1564 

1565 

1566 def my_before_commit(session): 

1567 print("before commit!") 

1568 

1569 

1570 Session = sessionmaker() 

1571 

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

1573 

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

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

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

1577 

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

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

1580 globally. 

1581 

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

1583 to applicable event listener functions that work on individual 

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

1585 object, rather than the mapped instance itself. 

1586 

1587 .. versionadded:: 1.3.14 

1588 

1589 :param restore_load_context=False: Applies to the 

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

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

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

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

1594 within this event if this flag is not set. 

1595 

1596 .. versionadded:: 1.3.14 

1597 

1598 """ 

1599 

1600 _target_class_doc = "SomeSessionClassOrObject" 

1601 

1602 _dispatch_target = Session 

1603 

1604 def _lifecycle_event( # type: ignore [misc] 

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

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

1607 _sessionevents_lifecycle_event_names.add(fn.__name__) 

1608 return fn 

1609 

1610 @classmethod 

1611 def _accept_with( # type: ignore [return] 

1612 cls, target: Any, identifier: str 

1613 ) -> Union[Session, type]: 

1614 if isinstance(target, scoped_session): 

1615 target = target.session_factory 

1616 if not isinstance(target, sessionmaker) and ( 

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

1618 ): 

1619 raise exc.ArgumentError( 

1620 "Session event listen on a scoped_session " 

1621 "requires that its creation callable " 

1622 "is associated with the Session class." 

1623 ) 

1624 

1625 if isinstance(target, sessionmaker): 

1626 return target.class_ 

1627 elif isinstance(target, type): 

1628 if issubclass(target, scoped_session): 

1629 return Session 

1630 elif issubclass(target, Session): 

1631 return target 

1632 elif isinstance(target, Session): 

1633 return target 

1634 elif hasattr(target, "_no_async_engine_events"): 

1635 target._no_async_engine_events() 

1636 else: 

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

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

1639 

1640 @classmethod 

1641 def _listen( 

1642 cls, 

1643 event_key: Any, 

1644 *, 

1645 raw: bool = False, 

1646 restore_load_context: bool = False, 

1647 **kw: Any, 

1648 ) -> None: 

1649 is_instance_event = ( 

1650 event_key.identifier in _sessionevents_lifecycle_event_names 

1651 ) 

1652 

1653 if is_instance_event: 

1654 if not raw or restore_load_context: 

1655 fn = event_key._listen_fn 

1656 

1657 def wrap( 

1658 session: Session, 

1659 state: InstanceState[_O], 

1660 *arg: Any, 

1661 **kw: Any, 

1662 ) -> Optional[Any]: 

1663 if not raw: 

1664 target = state.obj() 

1665 if target is None: 

1666 # existing behavior is that if the object is 

1667 # garbage collected, no event is emitted 

1668 return None 

1669 else: 

1670 target = state # type: ignore [assignment] 

1671 if restore_load_context: 

1672 runid = state.runid 

1673 try: 

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

1675 finally: 

1676 if restore_load_context: 

1677 state.runid = runid 

1678 

1679 event_key = event_key.with_wrapper(wrap) 

1680 

1681 event_key.base_listen(**kw) 

1682 

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

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

1685 ORM :class:`.Session` object. 

1686 

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

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

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

1690 SQLAlchemy 1.4, all ORM queries that run through the 

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

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

1693 will participate in this event. 

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

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

1696 process described at :ref:`session_flushing`. 

1697 

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

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

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

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

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

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

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

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

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

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

1708 :meth:`.ConnectionEvents.before_execute` and 

1709 :meth:`.ConnectionEvents.before_cursor_execute`. 

1710 

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

1712 emitted internally within the ORM flush process, 

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

1714 intercept steps within the flush process, see the event 

1715 hooks described at :ref:`session_persistence_events` as 

1716 well as :ref:`session_persistence_mapper`. 

1717 

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

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

1720 performs. The intended use for this includes sharding and 

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

1722 across multiple database connections, returning a result that is 

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

1724 instead returning data from a cache. 

1725 

1726 The hook intends to replace the use of the 

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

1728 to SQLAlchemy 1.4. 

1729 

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

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

1732 as helper functions used to derive other commonly required 

1733 information. See that object for details. 

1734 

1735 .. seealso:: 

1736 

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

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

1739 

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

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

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

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

1744 and parameters as well as an option that allows programmatic 

1745 invocation of the statement at any point. 

1746 

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

1748 :meth:`_orm.SessionEvents.do_orm_execute` 

1749 

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

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

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

1753 

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

1755 extension relies upon the 

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

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

1758 

1759 

1760 .. versionadded:: 1.4 

1761 

1762 """ 

1763 

1764 def after_transaction_create( 

1765 self, session: Session, transaction: SessionTransaction 

1766 ) -> None: 

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

1768 

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

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

1771 overall, as opposed to when transactions are begun 

1772 on individual database connections. It is also invoked 

1773 for nested transactions and subtransactions, and is always 

1774 matched by a corresponding 

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

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

1777 

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

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

1780 

1781 To detect if this is the outermost 

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

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

1784 is ``None``:: 

1785 

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

1787 def after_transaction_create(session, transaction): 

1788 if transaction.parent is None: 

1789 ... # work with top-level transaction 

1790 

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

1792 :attr:`.SessionTransaction.nested` attribute:: 

1793 

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

1795 def after_transaction_create(session, transaction): 

1796 if transaction.nested: 

1797 ... # work with SAVEPOINT transaction 

1798 

1799 .. seealso:: 

1800 

1801 :class:`.SessionTransaction` 

1802 

1803 :meth:`~.SessionEvents.after_transaction_end` 

1804 

1805 """ 

1806 

1807 def after_transaction_end( 

1808 self, session: Session, transaction: SessionTransaction 

1809 ) -> None: 

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

1811 

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

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

1814 objects in use, including those for nested transactions 

1815 and subtransactions, and is always matched by a corresponding 

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

1817 

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

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

1820 

1821 To detect if this is the outermost 

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

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

1824 is ``None``:: 

1825 

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

1827 def after_transaction_end(session, transaction): 

1828 if transaction.parent is None: 

1829 ... # work with top-level transaction 

1830 

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

1832 :attr:`.SessionTransaction.nested` attribute:: 

1833 

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

1835 def after_transaction_end(session, transaction): 

1836 if transaction.nested: 

1837 ... # work with SAVEPOINT transaction 

1838 

1839 .. seealso:: 

1840 

1841 :class:`.SessionTransaction` 

1842 

1843 :meth:`~.SessionEvents.after_transaction_create` 

1844 

1845 """ 

1846 

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

1848 """Execute before commit is called. 

1849 

1850 .. note:: 

1851 

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

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

1854 many times within the scope of a transaction. 

1855 For interception of these events, use the 

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

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

1858 :meth:`~.SessionEvents.after_flush_postexec` 

1859 events. 

1860 

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

1862 

1863 .. seealso:: 

1864 

1865 :meth:`~.SessionEvents.after_commit` 

1866 

1867 :meth:`~.SessionEvents.after_begin` 

1868 

1869 :meth:`~.SessionEvents.after_transaction_create` 

1870 

1871 :meth:`~.SessionEvents.after_transaction_end` 

1872 

1873 """ 

1874 

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

1876 """Execute after a commit has occurred. 

1877 

1878 .. note:: 

1879 

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

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

1882 many times within the scope of a transaction. 

1883 For interception of these events, use the 

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

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

1886 :meth:`~.SessionEvents.after_flush_postexec` 

1887 events. 

1888 

1889 .. note:: 

1890 

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

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

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

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

1895 event. 

1896 

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

1898 

1899 .. seealso:: 

1900 

1901 :meth:`~.SessionEvents.before_commit` 

1902 

1903 :meth:`~.SessionEvents.after_begin` 

1904 

1905 :meth:`~.SessionEvents.after_transaction_create` 

1906 

1907 :meth:`~.SessionEvents.after_transaction_end` 

1908 

1909 """ 

1910 

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

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

1913 

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

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

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

1917 DBAPI transaction has already been rolled back. In many 

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

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

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

1921 which is active after the outermost rollback has proceeded, 

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

1923 :attr:`.Session.is_active` flag. 

1924 

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

1926 

1927 """ 

1928 

1929 def after_soft_rollback( 

1930 self, session: Session, previous_transaction: SessionTransaction 

1931 ) -> None: 

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

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

1934 

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

1936 the innermost rollback that calls the DBAPI's 

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

1938 calls that only pop themselves from the transaction stack. 

1939 

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

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

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

1943 

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

1945 def do_something(session, previous_transaction): 

1946 if session.is_active: 

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

1948 

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

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

1951 transactional marker object which was just closed. The current 

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

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

1954 

1955 """ 

1956 

1957 def before_flush( 

1958 self, 

1959 session: Session, 

1960 flush_context: UOWTransaction, 

1961 instances: Optional[Sequence[_O]], 

1962 ) -> None: 

1963 """Execute before flush process has started. 

1964 

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

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

1967 which handles the details of the flush. 

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

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

1970 (note this usage is deprecated). 

1971 

1972 .. seealso:: 

1973 

1974 :meth:`~.SessionEvents.after_flush` 

1975 

1976 :meth:`~.SessionEvents.after_flush_postexec` 

1977 

1978 :ref:`session_persistence_events` 

1979 

1980 """ 

1981 

1982 def after_flush( 

1983 self, session: Session, flush_context: UOWTransaction 

1984 ) -> None: 

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

1986 called. 

1987 

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

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

1990 as the history settings on instance attributes. 

1991 

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

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

1994 internal state to reflect those changes, including that newly 

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

1996 emitted within this event such as loads of related items 

1997 may produce new identity map entries that will immediately 

1998 be replaced, sometimes causing confusing results. SQLAlchemy will 

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

2000 

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

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

2003 which handles the details of the flush. 

2004 

2005 .. seealso:: 

2006 

2007 :meth:`~.SessionEvents.before_flush` 

2008 

2009 :meth:`~.SessionEvents.after_flush_postexec` 

2010 

2011 :ref:`session_persistence_events` 

2012 

2013 """ 

2014 

2015 def after_flush_postexec( 

2016 self, session: Session, flush_context: UOWTransaction 

2017 ) -> None: 

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

2019 state occurs. 

2020 

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

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

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

2024 transaction or participated in a larger transaction. 

2025 

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

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

2028 which handles the details of the flush. 

2029 

2030 

2031 .. seealso:: 

2032 

2033 :meth:`~.SessionEvents.before_flush` 

2034 

2035 :meth:`~.SessionEvents.after_flush` 

2036 

2037 :ref:`session_persistence_events` 

2038 

2039 """ 

2040 

2041 def after_begin( 

2042 self, 

2043 session: Session, 

2044 transaction: SessionTransaction, 

2045 connection: Connection, 

2046 ) -> None: 

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

2048 

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

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

2051 To invoke SQL operations within this hook, use the 

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

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

2054 directly. 

2055 

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

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

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

2059 which will be used for SQL statements. 

2060 

2061 .. seealso:: 

2062 

2063 :meth:`~.SessionEvents.before_commit` 

2064 

2065 :meth:`~.SessionEvents.after_commit` 

2066 

2067 :meth:`~.SessionEvents.after_transaction_create` 

2068 

2069 :meth:`~.SessionEvents.after_transaction_end` 

2070 

2071 """ 

2072 

2073 @_lifecycle_event 

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

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

2076 

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

2078 the object to be part of the session. 

2079 

2080 .. seealso:: 

2081 

2082 :meth:`~.SessionEvents.after_attach` 

2083 

2084 :ref:`session_lifecycle_events` 

2085 

2086 """ 

2087 

2088 @_lifecycle_event 

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

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

2091 

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

2093 

2094 .. note:: 

2095 

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

2097 has been fully associated with the session, which is 

2098 different than previous releases. For event 

2099 handlers that require the object not yet 

2100 be part of session state (such as handlers which 

2101 may autoflush while the target object is not 

2102 yet complete) consider the 

2103 new :meth:`.before_attach` event. 

2104 

2105 .. seealso:: 

2106 

2107 :meth:`~.SessionEvents.before_attach` 

2108 

2109 :ref:`session_lifecycle_events` 

2110 

2111 """ 

2112 

2113 @event._legacy_signature( 

2114 "0.9", 

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

2116 lambda update_context: ( 

2117 update_context.session, 

2118 update_context.query, 

2119 None, 

2120 update_context.result, 

2121 ), 

2122 ) 

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

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

2125 has been called. 

2126 

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

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

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

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

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

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

2133 these calls. 

2134 

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

2136 details about the update, including these attributes: 

2137 

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

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

2140 object that this update operation 

2141 was called upon. 

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

2143 :meth:`_query.Query.update`. 

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

2145 returned as a result of the 

2146 bulk UPDATE operation. 

2147 

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

2149 ``QueryContext`` object associated with it. 

2150 

2151 .. seealso:: 

2152 

2153 :meth:`.QueryEvents.before_compile_update` 

2154 

2155 :meth:`.SessionEvents.after_bulk_delete` 

2156 

2157 """ 

2158 

2159 @event._legacy_signature( 

2160 "0.9", 

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

2162 lambda delete_context: ( 

2163 delete_context.session, 

2164 delete_context.query, 

2165 None, 

2166 delete_context.result, 

2167 ), 

2168 ) 

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

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

2171 has been called. 

2172 

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

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

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

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

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

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

2179 these calls. 

2180 

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

2182 details about the update, including these attributes: 

2183 

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

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

2186 object that this update operation 

2187 was called upon. 

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

2189 returned as a result of the 

2190 bulk DELETE operation. 

2191 

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

2193 ``QueryContext`` object associated with it. 

2194 

2195 .. seealso:: 

2196 

2197 :meth:`.QueryEvents.before_compile_delete` 

2198 

2199 :meth:`.SessionEvents.after_bulk_update` 

2200 

2201 """ 

2202 

2203 @_lifecycle_event 

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

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

2206 object. 

2207 

2208 This event is a specialization of the 

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

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

2211 :meth:`.Session.add` call. 

2212 

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

2214 

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

2216 

2217 .. seealso:: 

2218 

2219 :ref:`session_lifecycle_events` 

2220 

2221 """ 

2222 

2223 @_lifecycle_event 

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

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

2226 object. 

2227 

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

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

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

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

2232 

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

2234 

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

2236 

2237 .. seealso:: 

2238 

2239 :ref:`session_lifecycle_events` 

2240 

2241 """ 

2242 

2243 @_lifecycle_event 

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

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

2246 object. 

2247 

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

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

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

2251 

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

2253 

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

2255 

2256 .. seealso:: 

2257 

2258 :ref:`session_lifecycle_events` 

2259 

2260 """ 

2261 

2262 @_lifecycle_event 

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

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

2265 object. 

2266 

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

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

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

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

2271 when the event is called. 

2272 

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

2274 

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

2276 

2277 .. seealso:: 

2278 

2279 :ref:`session_lifecycle_events` 

2280 

2281 """ 

2282 

2283 @_lifecycle_event 

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

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

2286 object. 

2287 

2288 This event is a specialization of the 

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

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

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

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

2293 associated with the 

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

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

2296 

2297 .. note:: 

2298 

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

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

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

2302 check the ``deleted`` flag sent to the 

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

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

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

2306 objects need to be intercepted before the flush. 

2307 

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

2309 

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

2311 

2312 .. seealso:: 

2313 

2314 :ref:`session_lifecycle_events` 

2315 

2316 """ 

2317 

2318 @_lifecycle_event 

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

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

2321 object. 

2322 

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

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

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

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

2327 with the other session lifecycle events smoothly. The object 

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

2329 this event is called. 

2330 

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

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

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

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

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

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

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

2338 works in the same manner as that of 

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

2340 resolve this scenario. 

2341 

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

2343 

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

2345 

2346 .. seealso:: 

2347 

2348 :ref:`session_lifecycle_events` 

2349 

2350 """ 

2351 

2352 @_lifecycle_event 

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

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

2355 object. 

2356 

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

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

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

2360 transaction completes. 

2361 

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

2363 to the persistent state, and the 

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

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

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

2367 event. 

2368 

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

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

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

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

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

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

2375 invoked at the end of a flush. 

2376 

2377 .. seealso:: 

2378 

2379 :ref:`session_lifecycle_events` 

2380 

2381 """ 

2382 

2383 @_lifecycle_event 

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

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

2386 object. 

2387 

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

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

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

2391 any other circumstances. 

2392 

2393 .. seealso:: 

2394 

2395 :ref:`session_lifecycle_events` 

2396 

2397 """ 

2398 

2399 @_lifecycle_event 

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

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

2402 object. 

2403 

2404 This event is invoked when a deleted object is evicted 

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

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

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

2408 state to the detached state. 

2409 

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

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

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

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

2414 

2415 .. seealso:: 

2416 

2417 :ref:`session_lifecycle_events` 

2418 

2419 """ 

2420 

2421 @_lifecycle_event 

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

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

2424 object. 

2425 

2426 This event is invoked when a persistent object is evicted 

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

2428 to happen, including: 

2429 

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

2431 or :meth:`.Session.close` 

2432 

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

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

2435 

2436 

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

2438 

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

2440 

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

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

2443 

2444 

2445 .. seealso:: 

2446 

2447 :ref:`session_lifecycle_events` 

2448 

2449 """ 

2450 

2451 

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

2453 r"""Define events for object attributes. 

2454 

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

2456 target class. 

2457 

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

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

2460 

2461 from sqlalchemy import event 

2462 

2463 

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

2465 def my_append_listener(target, value, initiator): 

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

2467 

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

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

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

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

2472 

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

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

2475 

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

2477 

2478 

2479 # setup listener on UserContact.phone attribute, instructing 

2480 # it to use the return value 

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

2482 

2483 A validation function like the above can also raise an exception 

2484 such as :exc:`ValueError` to halt the operation. 

2485 

2486 The :paramref:`.AttributeEvents.propagate` flag is also important when 

2487 applying listeners to mapped classes that also have mapped subclasses, 

2488 as when using mapper inheritance patterns:: 

2489 

2490 

2491 @event.listens_for(MySuperClass.attr, "set", propagate=True) 

2492 def receive_set(target, value, initiator): 

2493 print("value set: %s" % target) 

2494 

2495 The full list of modifiers available to the :func:`.event.listen` 

2496 and :func:`.event.listens_for` functions are below. 

2497 

2498 :param active_history=False: When True, indicates that the 

2499 "set" event would like to receive the "old" value being 

2500 replaced unconditionally, even if this requires firing off 

2501 database loads. Note that ``active_history`` can also be 

2502 set directly via :func:`.column_property` and 

2503 :func:`_orm.relationship`. 

2504 

2505 :param propagate=False: When True, the listener function will 

2506 be established not just for the class attribute given, but 

2507 for attributes of the same name on all current subclasses 

2508 of that class, as well as all future subclasses of that 

2509 class, using an additional listener that listens for 

2510 instrumentation events. 

2511 :param raw=False: When True, the "target" argument to the 

2512 event will be the :class:`.InstanceState` management 

2513 object, rather than the mapped instance itself. 

2514 :param retval=False: when True, the user-defined event 

2515 listening must return the "value" argument from the 

2516 function. This gives the listening function the opportunity 

2517 to change the value that is ultimately used for a "set" 

2518 or "append" event. 

2519 

2520 """ 

2521 

2522 _target_class_doc = "SomeClass.some_attribute" 

2523 _dispatch_target = QueryableAttribute 

2524 

2525 @staticmethod 

2526 def _set_dispatch( 

2527 cls: Type[_HasEventsDispatch[Any]], dispatch_cls: Type[_Dispatch[Any]] 

2528 ) -> _Dispatch[Any]: 

2529 dispatch = event.Events._set_dispatch(cls, dispatch_cls) 

2530 dispatch_cls._active_history = False 

2531 return dispatch 

2532 

2533 @classmethod 

2534 def _accept_with( 

2535 cls, 

2536 target: Union[QueryableAttribute[Any], Type[QueryableAttribute[Any]]], 

2537 identifier: str, 

2538 ) -> Union[QueryableAttribute[Any], Type[QueryableAttribute[Any]]]: 

2539 # TODO: coverage 

2540 if isinstance(target, interfaces.MapperProperty): 

2541 return getattr(target.parent.class_, target.key) 

2542 else: 

2543 return target 

2544 

2545 @classmethod 

2546 def _listen( # type: ignore [override] 

2547 cls, 

2548 event_key: _EventKey[QueryableAttribute[Any]], 

2549 active_history: bool = False, 

2550 raw: bool = False, 

2551 retval: bool = False, 

2552 propagate: bool = False, 

2553 include_key: bool = False, 

2554 ) -> None: 

2555 target, fn = event_key.dispatch_target, event_key._listen_fn 

2556 

2557 if active_history: 

2558 target.dispatch._active_history = True 

2559 

2560 if not raw or not retval or not include_key: 

2561 

2562 def wrap(target: InstanceState[_O], *arg: Any, **kw: Any) -> Any: 

2563 if not raw: 

2564 target = target.obj() # type: ignore [assignment] 

2565 if not retval: 

2566 if arg: 

2567 value = arg[0] 

2568 else: 

2569 value = None 

2570 if include_key: 

2571 fn(target, *arg, **kw) 

2572 else: 

2573 fn(target, *arg) 

2574 return value 

2575 else: 

2576 if include_key: 

2577 return fn(target, *arg, **kw) 

2578 else: 

2579 return fn(target, *arg) 

2580 

2581 event_key = event_key.with_wrapper(wrap) 

2582 

2583 event_key.base_listen(propagate=propagate) 

2584 

2585 if propagate: 

2586 manager = instrumentation.manager_of_class(target.class_) 

2587 

2588 for mgr in manager.subclass_managers(True): # type: ignore [no-untyped-call] # noqa: E501 

2589 event_key.with_dispatch_target(mgr[target.key]).base_listen( 

2590 propagate=True 

2591 ) 

2592 if active_history: 

2593 mgr[target.key].dispatch._active_history = True 

2594 

2595 def append( 

2596 self, 

2597 target: _O, 

2598 value: _T, 

2599 initiator: Event, 

2600 *, 

2601 key: EventConstants = NO_KEY, 

2602 ) -> Optional[_T]: 

2603 """Receive a collection append event. 

2604 

2605 The append event is invoked for each element as it is appended 

2606 to the collection. This occurs for single-item appends as well 

2607 as for a "bulk replace" operation. 

2608 

2609 :param target: the object instance receiving the event. 

2610 If the listener is registered with ``raw=True``, this will 

2611 be the :class:`.InstanceState` object. 

2612 :param value: the value being appended. If this listener 

2613 is registered with ``retval=True``, the listener 

2614 function must return this value, or a new value which 

2615 replaces it. 

2616 :param initiator: An instance of :class:`.attributes.Event` 

2617 representing the initiation of the event. May be modified 

2618 from its original value by backref handlers in order to control 

2619 chained event propagation, as well as be inspected for information 

2620 about the source of the event. 

2621 :param key: When the event is established using the 

2622 :paramref:`.AttributeEvents.include_key` parameter set to 

2623 True, this will be the key used in the operation, such as 

2624 ``collection[some_key_or_index] = value``. 

2625 The parameter is not passed 

2626 to the event at all if the the 

2627 :paramref:`.AttributeEvents.include_key` 

2628 was not used to set up the event; this is to allow backwards 

2629 compatibility with existing event handlers that don't include the 

2630 ``key`` parameter. 

2631 

2632 .. versionadded:: 2.0 

2633 

2634 :return: if the event was registered with ``retval=True``, 

2635 the given value, or a new effective value, should be returned. 

2636 

2637 .. seealso:: 

2638 

2639 :class:`.AttributeEvents` - background on listener options such 

2640 as propagation to subclasses. 

2641 

2642 :meth:`.AttributeEvents.bulk_replace` 

2643 

2644 """ 

2645 

2646 def append_wo_mutation( 

2647 self, 

2648 target: _O, 

2649 value: _T, 

2650 initiator: Event, 

2651 *, 

2652 key: EventConstants = NO_KEY, 

2653 ) -> None: 

2654 """Receive a collection append event where the collection was not 

2655 actually mutated. 

2656 

2657 This event differs from :meth:`_orm.AttributeEvents.append` in that 

2658 it is fired off for de-duplicating collections such as sets and 

2659 dictionaries, when the object already exists in the target collection. 

2660 The event does not have a return value and the identity of the 

2661 given object cannot be changed. 

2662 

2663 The event is used for cascading objects into a :class:`_orm.Session` 

2664 when the collection has already been mutated via a backref event. 

2665 

2666 :param target: the object instance receiving the event. 

2667 If the listener is registered with ``raw=True``, this will 

2668 be the :class:`.InstanceState` object. 

2669 :param value: the value that would be appended if the object did not 

2670 already exist in the collection. 

2671 :param initiator: An instance of :class:`.attributes.Event` 

2672 representing the initiation of the event. May be modified 

2673 from its original value by backref handlers in order to control 

2674 chained event propagation, as well as be inspected for information 

2675 about the source of the event. 

2676 :param key: When the event is established using the 

2677 :paramref:`.AttributeEvents.include_key` parameter set to 

2678 True, this will be the key used in the operation, such as 

2679 ``collection[some_key_or_index] = value``. 

2680 The parameter is not passed 

2681 to the event at all if the the 

2682 :paramref:`.AttributeEvents.include_key` 

2683 was not used to set up the event; this is to allow backwards 

2684 compatibility with existing event handlers that don't include the 

2685 ``key`` parameter. 

2686 

2687 .. versionadded:: 2.0 

2688 

2689 :return: No return value is defined for this event. 

2690 

2691 .. versionadded:: 1.4.15 

2692 

2693 """ 

2694 

2695 def bulk_replace( 

2696 self, 

2697 target: _O, 

2698 values: Iterable[_T], 

2699 initiator: Event, 

2700 *, 

2701 keys: Optional[Iterable[EventConstants]] = None, 

2702 ) -> None: 

2703 """Receive a collection 'bulk replace' event. 

2704 

2705 This event is invoked for a sequence of values as they are incoming 

2706 to a bulk collection set operation, which can be 

2707 modified in place before the values are treated as ORM objects. 

2708 This is an "early hook" that runs before the bulk replace routine 

2709 attempts to reconcile which objects are already present in the 

2710 collection and which are being removed by the net replace operation. 

2711 

2712 It is typical that this method be combined with use of the 

2713 :meth:`.AttributeEvents.append` event. When using both of these 

2714 events, note that a bulk replace operation will invoke 

2715 the :meth:`.AttributeEvents.append` event for all new items, 

2716 even after :meth:`.AttributeEvents.bulk_replace` has been invoked 

2717 for the collection as a whole. In order to determine if an 

2718 :meth:`.AttributeEvents.append` event is part of a bulk replace, 

2719 use the symbol :attr:`~.attributes.OP_BULK_REPLACE` to test the 

2720 incoming initiator:: 

2721 

2722 from sqlalchemy.orm.attributes import OP_BULK_REPLACE 

2723 

2724 

2725 @event.listens_for(SomeObject.collection, "bulk_replace") 

2726 def process_collection(target, values, initiator): 

2727 values[:] = [_make_value(value) for value in values] 

2728 

2729 

2730 @event.listens_for(SomeObject.collection, "append", retval=True) 

2731 def process_collection(target, value, initiator): 

2732 # make sure bulk_replace didn't already do it 

2733 if initiator is None or initiator.op is not OP_BULK_REPLACE: 

2734 return _make_value(value) 

2735 else: 

2736 return value 

2737 

2738 .. versionadded:: 1.2 

2739 

2740 :param target: the object instance receiving the event. 

2741 If the listener is registered with ``raw=True``, this will 

2742 be the :class:`.InstanceState` object. 

2743 :param value: a sequence (e.g. a list) of the values being set. The 

2744 handler can modify this list in place. 

2745 :param initiator: An instance of :class:`.attributes.Event` 

2746 representing the initiation of the event. 

2747 :param keys: When the event is established using the 

2748 :paramref:`.AttributeEvents.include_key` parameter set to 

2749 True, this will be the sequence of keys used in the operation, 

2750 typically only for a dictionary update. The parameter is not passed 

2751 to the event at all if the the 

2752 :paramref:`.AttributeEvents.include_key` 

2753 was not used to set up the event; this is to allow backwards 

2754 compatibility with existing event handlers that don't include the 

2755 ``key`` parameter. 

2756 

2757 .. versionadded:: 2.0 

2758 

2759 .. seealso:: 

2760 

2761 :class:`.AttributeEvents` - background on listener options such 

2762 as propagation to subclasses. 

2763 

2764 

2765 """ 

2766 

2767 def remove( 

2768 self, 

2769 target: _O, 

2770 value: _T, 

2771 initiator: Event, 

2772 *, 

2773 key: EventConstants = NO_KEY, 

2774 ) -> None: 

2775 """Receive a collection remove event. 

2776 

2777 :param target: the object instance receiving the event. 

2778 If the listener is registered with ``raw=True``, this will 

2779 be the :class:`.InstanceState` object. 

2780 :param value: the value being removed. 

2781 :param initiator: An instance of :class:`.attributes.Event` 

2782 representing the initiation of the event. May be modified 

2783 from its original value by backref handlers in order to control 

2784 chained event propagation. 

2785 

2786 :param key: When the event is established using the 

2787 :paramref:`.AttributeEvents.include_key` parameter set to 

2788 True, this will be the key used in the operation, such as 

2789 ``del collection[some_key_or_index]``. The parameter is not passed 

2790 to the event at all if the the 

2791 :paramref:`.AttributeEvents.include_key` 

2792 was not used to set up the event; this is to allow backwards 

2793 compatibility with existing event handlers that don't include the 

2794 ``key`` parameter. 

2795 

2796 .. versionadded:: 2.0 

2797 

2798 :return: No return value is defined for this event. 

2799 

2800 

2801 .. seealso:: 

2802 

2803 :class:`.AttributeEvents` - background on listener options such 

2804 as propagation to subclasses. 

2805 

2806 """ 

2807 

2808 def set( 

2809 self, target: _O, value: _T, oldvalue: _T, initiator: Event 

2810 ) -> None: 

2811 """Receive a scalar set event. 

2812 

2813 :param target: the object instance receiving the event. 

2814 If the listener is registered with ``raw=True``, this will 

2815 be the :class:`.InstanceState` object. 

2816 :param value: the value being set. If this listener 

2817 is registered with ``retval=True``, the listener 

2818 function must return this value, or a new value which 

2819 replaces it. 

2820 :param oldvalue: the previous value being replaced. This 

2821 may also be the symbol ``NEVER_SET`` or ``NO_VALUE``. 

2822 If the listener is registered with ``active_history=True``, 

2823 the previous value of the attribute will be loaded from 

2824 the database if the existing value is currently unloaded 

2825 or expired. 

2826 :param initiator: An instance of :class:`.attributes.Event` 

2827 representing the initiation of the event. May be modified 

2828 from its original value by backref handlers in order to control 

2829 chained event propagation. 

2830 

2831 :return: if the event was registered with ``retval=True``, 

2832 the given value, or a new effective value, should be returned. 

2833 

2834 .. seealso:: 

2835 

2836 :class:`.AttributeEvents` - background on listener options such 

2837 as propagation to subclasses. 

2838 

2839 """ 

2840 

2841 def init_scalar( 

2842 self, target: _O, value: _T, dict_: Dict[Any, Any] 

2843 ) -> None: 

2844 r"""Receive a scalar "init" event. 

2845 

2846 This event is invoked when an uninitialized, unpersisted scalar 

2847 attribute is accessed, e.g. read:: 

2848 

2849 

2850 x = my_object.some_attribute 

2851 

2852 The ORM's default behavior when this occurs for an un-initialized 

2853 attribute is to return the value ``None``; note this differs from 

2854 Python's usual behavior of raising ``AttributeError``. The 

2855 event here can be used to customize what value is actually returned, 

2856 with the assumption that the event listener would be mirroring 

2857 a default generator that is configured on the Core 

2858 :class:`_schema.Column` 

2859 object as well. 

2860 

2861 Since a default generator on a :class:`_schema.Column` 

2862 might also produce 

2863 a changing value such as a timestamp, the 

2864 :meth:`.AttributeEvents.init_scalar` 

2865 event handler can also be used to **set** the newly returned value, so 

2866 that a Core-level default generation function effectively fires off 

2867 only once, but at the moment the attribute is accessed on the 

2868 non-persisted object. Normally, no change to the object's state 

2869 is made when an uninitialized attribute is accessed (much older 

2870 SQLAlchemy versions did in fact change the object's state). 

2871 

2872 If a default generator on a column returned a particular constant, 

2873 a handler might be used as follows:: 

2874 

2875 SOME_CONSTANT = 3.1415926 

2876 

2877 

2878 class MyClass(Base): 

2879 # ... 

2880 

2881 some_attribute = Column(Numeric, default=SOME_CONSTANT) 

2882 

2883 

2884 @event.listens_for( 

2885 MyClass.some_attribute, "init_scalar", retval=True, propagate=True 

2886 ) 

2887 def _init_some_attribute(target, dict_, value): 

2888 dict_["some_attribute"] = SOME_CONSTANT 

2889 return SOME_CONSTANT 

2890 

2891 Above, we initialize the attribute ``MyClass.some_attribute`` to the 

2892 value of ``SOME_CONSTANT``. The above code includes the following 

2893 features: 

2894 

2895 * By setting the value ``SOME_CONSTANT`` in the given ``dict_``, 

2896 we indicate that this value is to be persisted to the database. 

2897 This supersedes the use of ``SOME_CONSTANT`` in the default generator 

2898 for the :class:`_schema.Column`. The ``active_column_defaults.py`` 

2899 example given at :ref:`examples_instrumentation` illustrates using 

2900 the same approach for a changing default, e.g. a timestamp 

2901 generator. In this particular example, it is not strictly 

2902 necessary to do this since ``SOME_CONSTANT`` would be part of the 

2903 INSERT statement in either case. 

2904 

2905 * By establishing the ``retval=True`` flag, the value we return 

2906 from the function will be returned by the attribute getter. 

2907 Without this flag, the event is assumed to be a passive observer 

2908 and the return value of our function is ignored. 

2909 

2910 * The ``propagate=True`` flag is significant if the mapped class 

2911 includes inheriting subclasses, which would also make use of this 

2912 event listener. Without this flag, an inheriting subclass will 

2913 not use our event handler. 

2914 

2915 In the above example, the attribute set event 

2916 :meth:`.AttributeEvents.set` as well as the related validation feature 

2917 provided by :obj:`_orm.validates` is **not** invoked when we apply our 

2918 value to the given ``dict_``. To have these events to invoke in 

2919 response to our newly generated value, apply the value to the given 

2920 object as a normal attribute set operation:: 

2921 

2922 SOME_CONSTANT = 3.1415926 

2923 

2924 

2925 @event.listens_for( 

2926 MyClass.some_attribute, "init_scalar", retval=True, propagate=True 

2927 ) 

2928 def _init_some_attribute(target, dict_, value): 

2929 # will also fire off attribute set events 

2930 target.some_attribute = SOME_CONSTANT 

2931 return SOME_CONSTANT 

2932 

2933 When multiple listeners are set up, the generation of the value 

2934 is "chained" from one listener to the next by passing the value 

2935 returned by the previous listener that specifies ``retval=True`` 

2936 as the ``value`` argument of the next listener. 

2937 

2938 :param target: the object instance receiving the event. 

2939 If the listener is registered with ``raw=True``, this will 

2940 be the :class:`.InstanceState` object. 

2941 :param value: the value that is to be returned before this event 

2942 listener were invoked. This value begins as the value ``None``, 

2943 however will be the return value of the previous event handler 

2944 function if multiple listeners are present. 

2945 :param dict\_: the attribute dictionary of this mapped object. 

2946 This is normally the ``__dict__`` of the object, but in all cases 

2947 represents the destination that the attribute system uses to get 

2948 at the actual value of this attribute. Placing the value in this 

2949 dictionary has the effect that the value will be used in the 

2950 INSERT statement generated by the unit of work. 

2951 

2952 

2953 .. seealso:: 

2954 

2955 :meth:`.AttributeEvents.init_collection` - collection version 

2956 of this event 

2957 

2958 :class:`.AttributeEvents` - background on listener options such 

2959 as propagation to subclasses. 

2960 

2961 :ref:`examples_instrumentation` - see the 

2962 ``active_column_defaults.py`` example. 

2963 

2964 """ # noqa: E501 

2965 

2966 def init_collection( 

2967 self, 

2968 target: _O, 

2969 collection: Type[Collection[Any]], 

2970 collection_adapter: CollectionAdapter, 

2971 ) -> None: 

2972 """Receive a 'collection init' event. 

2973 

2974 This event is triggered for a collection-based attribute, when 

2975 the initial "empty collection" is first generated for a blank 

2976 attribute, as well as for when the collection is replaced with 

2977 a new one, such as via a set event. 

2978 

2979 E.g., given that ``User.addresses`` is a relationship-based 

2980 collection, the event is triggered here:: 

2981 

2982 u1 = User() 

2983 u1.addresses.append(a1) # <- new collection 

2984 

2985 and also during replace operations:: 

2986 

2987 u1.addresses = [a2, a3] # <- new collection 

2988 

2989 :param target: the object instance receiving the event. 

2990 If the listener is registered with ``raw=True``, this will 

2991 be the :class:`.InstanceState` object. 

2992 :param collection: the new collection. This will always be generated 

2993 from what was specified as 

2994 :paramref:`_orm.relationship.collection_class`, and will always 

2995 be empty. 

2996 :param collection_adapter: the :class:`.CollectionAdapter` that will 

2997 mediate internal access to the collection. 

2998 

2999 .. seealso:: 

3000 

3001 :class:`.AttributeEvents` - background on listener options such 

3002 as propagation to subclasses. 

3003 

3004 :meth:`.AttributeEvents.init_scalar` - "scalar" version of this 

3005 event. 

3006 

3007 """ 

3008 

3009 def dispose_collection( 

3010 self, 

3011 target: _O, 

3012 collection: Collection[Any], 

3013 collection_adapter: CollectionAdapter, 

3014 ) -> None: 

3015 """Receive a 'collection dispose' event. 

3016 

3017 This event is triggered for a collection-based attribute when 

3018 a collection is replaced, that is:: 

3019 

3020 u1.addresses.append(a1) 

3021 

3022 u1.addresses = [a2, a3] # <- old collection is disposed 

3023 

3024 The old collection received will contain its previous contents. 

3025 

3026 .. versionchanged:: 1.2 The collection passed to 

3027 :meth:`.AttributeEvents.dispose_collection` will now have its 

3028 contents before the dispose intact; previously, the collection 

3029 would be empty. 

3030 

3031 .. seealso:: 

3032 

3033 :class:`.AttributeEvents` - background on listener options such 

3034 as propagation to subclasses. 

3035 

3036 """ 

3037 

3038 def modified(self, target: _O, initiator: Event) -> None: 

3039 """Receive a 'modified' event. 

3040 

3041 This event is triggered when the :func:`.attributes.flag_modified` 

3042 function is used to trigger a modify event on an attribute without 

3043 any specific value being set. 

3044 

3045 .. versionadded:: 1.2 

3046 

3047 :param target: the object instance receiving the event. 

3048 If the listener is registered with ``raw=True``, this will 

3049 be the :class:`.InstanceState` object. 

3050 

3051 :param initiator: An instance of :class:`.attributes.Event` 

3052 representing the initiation of the event. 

3053 

3054 .. seealso:: 

3055 

3056 :class:`.AttributeEvents` - background on listener options such 

3057 as propagation to subclasses. 

3058 

3059 """ 

3060 

3061 

3062class QueryEvents(event.Events[Query[Any]]): 

3063 """Represent events within the construction of a :class:`_query.Query` 

3064 object. 

3065 

3066 .. legacy:: The :class:`_orm.QueryEvents` event methods are legacy 

3067 as of SQLAlchemy 2.0, and only apply to direct use of the 

3068 :class:`_orm.Query` object. They are not used for :term:`2.0 style` 

3069 statements. For events to intercept and modify 2.0 style ORM use, 

3070 use the :meth:`_orm.SessionEvents.do_orm_execute` hook. 

3071 

3072 

3073 The :class:`_orm.QueryEvents` hooks are now superseded by the 

3074 :meth:`_orm.SessionEvents.do_orm_execute` event hook. 

3075 

3076 """ 

3077 

3078 _target_class_doc = "SomeQuery" 

3079 _dispatch_target = Query 

3080 

3081 def before_compile(self, query: Query[Any]) -> None: 

3082 """Receive the :class:`_query.Query` 

3083 object before it is composed into a 

3084 core :class:`_expression.Select` object. 

3085 

3086 .. deprecated:: 1.4 The :meth:`_orm.QueryEvents.before_compile` event 

3087 is superseded by the much more capable 

3088 :meth:`_orm.SessionEvents.do_orm_execute` hook. In version 1.4, 

3089 the :meth:`_orm.QueryEvents.before_compile` event is **no longer 

3090 used** for ORM-level attribute loads, such as loads of deferred 

3091 or expired attributes as well as relationship loaders. See the 

3092 new examples in :ref:`examples_session_orm_events` which 

3093 illustrate new ways of intercepting and modifying ORM queries 

3094 for the most common purpose of adding arbitrary filter criteria. 

3095 

3096 

3097 This event is intended to allow changes to the query given:: 

3098 

3099 @event.listens_for(Query, "before_compile", retval=True) 

3100 def no_deleted(query): 

3101 for desc in query.column_descriptions: 

3102 if desc["type"] is User: 

3103 entity = desc["entity"] 

3104 query = query.filter(entity.deleted == False) 

3105 return query 

3106 

3107 The event should normally be listened with the ``retval=True`` 

3108 parameter set, so that the modified query may be returned. 

3109 

3110 The :meth:`.QueryEvents.before_compile` event by default 

3111 will disallow "baked" queries from caching a query, if the event 

3112 hook returns a new :class:`_query.Query` object. 

3113 This affects both direct 

3114 use of the baked query extension as well as its operation within 

3115 lazy loaders and eager loaders for relationships. In order to 

3116 re-establish the query being cached, apply the event adding the 

3117 ``bake_ok`` flag:: 

3118 

3119 @event.listens_for(Query, "before_compile", retval=True, bake_ok=True) 

3120 def my_event(query): 

3121 for desc in query.column_descriptions: 

3122 if desc["type"] is User: 

3123 entity = desc["entity"] 

3124 query = query.filter(entity.deleted == False) 

3125 return query 

3126 

3127 When ``bake_ok`` is set to True, the event hook will only be invoked 

3128 once, and not called for subsequent invocations of a particular query 

3129 that is being cached. 

3130 

3131 .. versionadded:: 1.3.11 - added the "bake_ok" flag to the 

3132 :meth:`.QueryEvents.before_compile` event and disallowed caching via 

3133 the "baked" extension from occurring for event handlers that 

3134 return a new :class:`_query.Query` object if this flag is not set. 

3135 

3136 .. seealso:: 

3137 

3138 :meth:`.QueryEvents.before_compile_update` 

3139 

3140 :meth:`.QueryEvents.before_compile_delete` 

3141 

3142 :ref:`baked_with_before_compile` 

3143 

3144 """ # noqa: E501 

3145 

3146 def before_compile_update( 

3147 self, query: Query[Any], update_context: BulkUpdate 

3148 ) -> None: 

3149 """Allow modifications to the :class:`_query.Query` object within 

3150 :meth:`_query.Query.update`. 

3151 

3152 .. deprecated:: 1.4 The :meth:`_orm.QueryEvents.before_compile_update` 

3153 event is superseded by the much more capable 

3154 :meth:`_orm.SessionEvents.do_orm_execute` hook. 

3155 

3156 Like the :meth:`.QueryEvents.before_compile` event, if the event 

3157 is to be used to alter the :class:`_query.Query` object, it should 

3158 be configured with ``retval=True``, and the modified 

3159 :class:`_query.Query` object returned, as in :: 

3160 

3161 @event.listens_for(Query, "before_compile_update", retval=True) 

3162 def no_deleted(query, update_context): 

3163 for desc in query.column_descriptions: 

3164 if desc["type"] is User: 

3165 entity = desc["entity"] 

3166 query = query.filter(entity.deleted == False) 

3167 

3168 update_context.values["timestamp"] = datetime.datetime.now( 

3169 datetime.UTC 

3170 ) 

3171 return query 

3172 

3173 The ``.values`` dictionary of the "update context" object can also 

3174 be modified in place as illustrated above. 

3175 

3176 :param query: a :class:`_query.Query` instance; this is also 

3177 the ``.query`` attribute of the given "update context" 

3178 object. 

3179 

3180 :param update_context: an "update context" object which is 

3181 the same kind of object as described in 

3182 :paramref:`.QueryEvents.after_bulk_update.update_context`. 

3183 The object has a ``.values`` attribute in an UPDATE context which is 

3184 the dictionary of parameters passed to :meth:`_query.Query.update`. 

3185 This 

3186 dictionary can be modified to alter the VALUES clause of the 

3187 resulting UPDATE statement. 

3188 

3189 .. versionadded:: 1.2.17 

3190 

3191 .. seealso:: 

3192 

3193 :meth:`.QueryEvents.before_compile` 

3194 

3195 :meth:`.QueryEvents.before_compile_delete` 

3196 

3197 

3198 """ # noqa: E501 

3199 

3200 def before_compile_delete( 

3201 self, query: Query[Any], delete_context: BulkDelete 

3202 ) -> None: 

3203 """Allow modifications to the :class:`_query.Query` object within 

3204 :meth:`_query.Query.delete`. 

3205 

3206 .. deprecated:: 1.4 The :meth:`_orm.QueryEvents.before_compile_delete` 

3207 event is superseded by the much more capable 

3208 :meth:`_orm.SessionEvents.do_orm_execute` hook. 

3209 

3210 Like the :meth:`.QueryEvents.before_compile` event, this event 

3211 should be configured with ``retval=True``, and the modified 

3212 :class:`_query.Query` object returned, as in :: 

3213 

3214 @event.listens_for(Query, "before_compile_delete", retval=True) 

3215 def no_deleted(query, delete_context): 

3216 for desc in query.column_descriptions: 

3217 if desc["type"] is User: 

3218 entity = desc["entity"] 

3219 query = query.filter(entity.deleted == False) 

3220 return query 

3221 

3222 :param query: a :class:`_query.Query` instance; this is also 

3223 the ``.query`` attribute of the given "delete context" 

3224 object. 

3225 

3226 :param delete_context: a "delete context" object which is 

3227 the same kind of object as described in 

3228 :paramref:`.QueryEvents.after_bulk_delete.delete_context`. 

3229 

3230 .. versionadded:: 1.2.17 

3231 

3232 .. seealso:: 

3233 

3234 :meth:`.QueryEvents.before_compile` 

3235 

3236 :meth:`.QueryEvents.before_compile_update` 

3237 

3238 

3239 """ 

3240 

3241 @classmethod 

3242 def _listen( 

3243 cls, 

3244 event_key: _EventKey[_ET], 

3245 retval: bool = False, 

3246 bake_ok: bool = False, 

3247 **kw: Any, 

3248 ) -> None: 

3249 fn = event_key._listen_fn 

3250 

3251 if not retval: 

3252 

3253 def wrap(*arg: Any, **kw: Any) -> Any: 

3254 if not retval: 

3255 query = arg[0] 

3256 fn(*arg, **kw) 

3257 return query 

3258 else: 

3259 return fn(*arg, **kw) 

3260 

3261 event_key = event_key.with_wrapper(wrap) 

3262 else: 

3263 # don't assume we can apply an attribute to the callable 

3264 def wrap(*arg: Any, **kw: Any) -> Any: 

3265 return fn(*arg, **kw) 

3266 

3267 event_key = event_key.with_wrapper(wrap) 

3268 

3269 wrap._bake_ok = bake_ok # type: ignore [attr-defined] 

3270 

3271 event_key.base_listen(**kw)