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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

421 statements  

1# orm/events.py 

2# Copyright (C) 2005-2024 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 def my_load_listener(target, context): 

211 print("on load!") 

212 

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

214 

215 Available targets include: 

216 

217 * mapped classes 

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

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

220 * :class:`_orm.Mapper` objects 

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

222 mappers. 

223 

224 Instance events are closely related to mapper events, but 

225 are more specific to the instance and its instrumentation, 

226 rather than its system of persistence. 

227 

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

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

230 

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

232 be applied to all inheriting classes as well as the 

233 class which is the target of this listener. 

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

235 to applicable event listener functions will be the 

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

237 object, rather than the mapped instance itself. 

238 :param restore_load_context=False: Applies to the 

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

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

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

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

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

244 events if this flag is not set. 

245 

246 .. versionadded:: 1.3.14 

247 

248 

249 """ 

250 

251 _target_class_doc = "SomeClass" 

252 

253 _dispatch_target = ClassManager 

254 

255 @classmethod 

256 def _new_classmanager_instance( 

257 cls, 

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

259 classmanager: ClassManager[_O], 

260 ) -> None: 

261 _InstanceEventsHold.populate(class_, classmanager) 

262 

263 @classmethod 

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

265 def _accept_with( 

266 cls, 

267 target: Union[ 

268 ClassManager[Any], 

269 Type[ClassManager[Any]], 

270 ], 

271 identifier: str, 

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

273 orm = util.preloaded.orm 

274 

275 if isinstance(target, ClassManager): 

276 return target 

277 elif isinstance(target, mapperlib.Mapper): 

278 return target.class_manager 

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

280 util.warn_deprecated( 

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

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

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

284 "2.0", 

285 ) 

286 return ClassManager 

287 elif isinstance(target, type): 

288 if issubclass(target, mapperlib.Mapper): 

289 return ClassManager 

290 else: 

291 manager = instrumentation.opt_manager_of_class(target) 

292 if manager: 

293 return manager 

294 else: 

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

296 return None 

297 

298 @classmethod 

299 def _listen( 

300 cls, 

301 event_key: _EventKey[ClassManager[Any]], 

302 raw: bool = False, 

303 propagate: bool = False, 

304 restore_load_context: bool = False, 

305 **kw: Any, 

306 ) -> None: 

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

308 

309 if not raw or restore_load_context: 

310 

311 def wrap( 

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

313 ) -> Optional[Any]: 

314 if not raw: 

315 target: Any = state.obj() 

316 else: 

317 target = state 

318 if restore_load_context: 

319 runid = state.runid 

320 try: 

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

322 finally: 

323 if restore_load_context: 

324 state.runid = runid 

325 

326 event_key = event_key.with_wrapper(wrap) 

327 

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

329 

330 if propagate: 

331 for mgr in target.subclass_managers(True): 

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

333 

334 @classmethod 

335 def _clear(cls) -> None: 

336 super()._clear() 

337 _InstanceEventsHold._clear() 

338 

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

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

341 

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

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

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

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

346 

347 """ 

348 

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

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

351 

352 This method is only called during a userland construction of 

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

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

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

356 event in order to intercept a database load. 

357 

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

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

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

361 ``__init__``. 

362 

363 :param target: the mapped instance. If 

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

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

366 object associated with the instance. 

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

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

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

370 This structure *can* be altered in place. 

371 

372 .. seealso:: 

373 

374 :meth:`.InstanceEvents.init_failure` 

375 

376 :meth:`.InstanceEvents.load` 

377 

378 """ 

379 

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

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

382 and raised an exception. 

383 

384 This method is only called during a userland construction of 

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

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

387 from the database. 

388 

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

390 method is caught. After the event 

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

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

393 actual exception and stack trace raised should be present in 

394 ``sys.exc_info()``. 

395 

396 :param target: the mapped instance. If 

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

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

399 object associated with the instance. 

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

401 method. 

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

403 method. 

404 

405 .. seealso:: 

406 

407 :meth:`.InstanceEvents.init` 

408 

409 :meth:`.InstanceEvents.load` 

410 

411 """ 

412 

413 def _sa_event_merge_wo_load( 

414 self, target: _O, context: QueryContext 

415 ) -> None: 

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

417 call, when load=False was passed. 

418 

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

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

421 overwrite operation does not use attribute events, instead just 

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

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

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

425 

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

427 

428 .. versionadded:: 1.4.41 

429 

430 """ 

431 

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

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

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

435 occurred. 

436 

437 This typically occurs when the instance is created based on 

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

439 instance's lifetime. 

440 

441 .. warning:: 

442 

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

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

445 eager loading with collection-oriented attributes, the additional 

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

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

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

449 if an operation occurs within this event handler that emits 

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

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

452 existing eager loaders still in progress. 

453 

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

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

456 

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

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

459 

460 * accessing attributes on a joined-inheritance subclass that 

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

462 

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

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

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

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

467 event is called:: 

468 

469 @event.listens_for( 

470 SomeClass, "load", restore_load_context=True) 

471 def on_load(instance, context): 

472 instance.some_unloaded_attribute 

473 

474 .. versionchanged:: 1.3.14 Added 

475 :paramref:`.InstanceEvents.restore_load_context` 

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

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

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

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

480 changes without this flag being set. 

481 

482 

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

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

485 

486 :param target: the mapped instance. If 

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

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

489 object associated with the instance. 

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

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

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

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

494 

495 .. seealso:: 

496 

497 :ref:`mapped_class_load_events` 

498 

499 :meth:`.InstanceEvents.init` 

500 

501 :meth:`.InstanceEvents.refresh` 

502 

503 :meth:`.SessionEvents.loaded_as_persistent` 

504 

505 """ 

506 

507 def refresh( 

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

509 ) -> None: 

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

511 been refreshed from a query. 

512 

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

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

515 

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

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

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

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

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

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

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

523 order to resolve this scenario. 

524 

525 :param target: the mapped instance. If 

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

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

528 object associated with the instance. 

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

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

531 :param attrs: sequence of attribute names which 

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

533 attributes were populated. 

534 

535 .. seealso:: 

536 

537 :ref:`mapped_class_load_events` 

538 

539 :meth:`.InstanceEvents.load` 

540 

541 """ 

542 

543 def refresh_flush( 

544 self, 

545 target: _O, 

546 flush_context: UOWTransaction, 

547 attrs: Optional[Iterable[str]], 

548 ) -> None: 

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

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

551 during persistence of the object's state. 

552 

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

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

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

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

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

558 

559 .. note:: 

560 

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

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

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

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

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

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

567 intercept the newly INSERTed state of an object, the 

568 :meth:`.SessionEvents.pending_to_persistent` and 

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

570 

571 :param target: the mapped instance. If 

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

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

574 object associated with the instance. 

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

576 which handles the details of the flush. 

577 :param attrs: sequence of attribute names which 

578 were populated. 

579 

580 .. seealso:: 

581 

582 :ref:`mapped_class_load_events` 

583 

584 :ref:`orm_server_defaults` 

585 

586 :ref:`metadata_defaults_toplevel` 

587 

588 """ 

589 

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

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

592 have been expired. 

593 

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

595 state was expired. 

596 

597 :param target: the mapped instance. If 

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

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

600 object associated with the instance. 

601 :param attrs: sequence of attribute 

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

603 expired. 

604 

605 """ 

606 

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

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

609 being pickled. 

610 

611 :param target: the mapped instance. If 

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

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

614 object associated with the instance. 

615 :param state_dict: the dictionary returned by 

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

617 to be pickled. 

618 

619 """ 

620 

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

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

623 been unpickled. 

624 

625 :param target: the mapped instance. If 

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

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

628 object associated with the instance. 

629 :param state_dict: the dictionary sent to 

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

631 dictionary which was pickled. 

632 

633 """ 

634 

635 

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

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

638 

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

640 those objects are created for that class. 

641 

642 """ 

643 

644 all_holds: weakref.WeakKeyDictionary[Any, Any] 

645 

646 def __init__( 

647 self, 

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

649 ) -> None: 

650 self.class_ = class_ 

651 

652 @classmethod 

653 def _clear(cls) -> None: 

654 cls.all_holds.clear() 

655 

656 class HoldEvents(Generic[_ET2]): 

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

658 

659 @classmethod 

660 def _listen( 

661 cls, 

662 event_key: _EventKey[_ET2], 

663 raw: bool = False, 

664 propagate: bool = False, 

665 retval: bool = False, 

666 **kw: Any, 

667 ) -> None: 

668 target = event_key.dispatch_target 

669 

670 if target.class_ in target.all_holds: 

671 collection = target.all_holds[target.class_] 

672 else: 

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

674 

675 event.registry._stored_in_collection(event_key, target) 

676 collection[event_key._key] = ( 

677 event_key, 

678 raw, 

679 propagate, 

680 retval, 

681 kw, 

682 ) 

683 

684 if propagate: 

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

686 while stack: 

687 subclass = stack.pop(0) 

688 stack.extend(subclass.__subclasses__()) 

689 subject = target.resolve(subclass) 

690 if subject is not None: 

691 # we are already going through __subclasses__() 

692 # so leave generic propagate flag False 

693 event_key.with_dispatch_target(subject).listen( 

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

695 ) 

696 

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

698 target = event_key.dispatch_target 

699 

700 if isinstance(target, _EventsHold): 

701 collection = target.all_holds[target.class_] 

702 del collection[event_key._key] 

703 

704 @classmethod 

705 def populate( 

706 cls, 

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

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

709 ) -> None: 

710 for subclass in class_.__mro__: 

711 if subclass in cls.all_holds: 

712 collection = cls.all_holds[subclass] 

713 for ( 

714 event_key, 

715 raw, 

716 propagate, 

717 retval, 

718 kw, 

719 ) in collection.values(): 

720 if propagate or subclass is class_: 

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

722 # classes in a hierarchy are triggered with 

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

724 # assignment, instead of using the generic propagate 

725 # flag. 

726 event_key.with_dispatch_target(subject).listen( 

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

728 ) 

729 

730 

731class _InstanceEventsHold(_EventsHold[_ET]): 

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

733 weakref.WeakKeyDictionary() 

734 ) 

735 

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

737 return instrumentation.opt_manager_of_class(class_) 

738 

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

740 pass 

741 

742 dispatch = event.dispatcher(HoldInstanceEvents) 

743 

744 

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

746 """Define events specific to mappings. 

747 

748 e.g.:: 

749 

750 from sqlalchemy import event 

751 

752 def my_before_insert_listener(mapper, connection, target): 

753 # execute a stored procedure upon INSERT, 

754 # apply the value to the row to be inserted 

755 target.calculated_value = connection.execute( 

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

757 ).scalar() 

758 

759 # associate the listener function with SomeClass, 

760 # to execute during the "before_insert" hook 

761 event.listen( 

762 SomeClass, 'before_insert', my_before_insert_listener) 

763 

764 Available targets include: 

765 

766 * mapped classes 

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

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

769 * :class:`_orm.Mapper` objects 

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

771 mappers. 

772 

773 Mapper events provide hooks into critical sections of the 

774 mapper, including those related to object instrumentation, 

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

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

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

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

779 methods operate with several significant restrictions. The 

780 user is encouraged to evaluate the 

781 :meth:`.SessionEvents.before_flush` and 

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

783 flexible and user-friendly hooks in which to apply 

784 additional database state during a flush. 

785 

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

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

788 

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

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

791 inheriting classes, as well as any 

792 mapper which is the target of this listener. 

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

794 to applicable event listener functions will be the 

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

796 object, rather than the mapped instance itself. 

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

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

799 control subsequent event propagation, or to otherwise alter 

800 the operation in progress by the mapper. Possible return 

801 values are: 

802 

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

804 processing normally. 

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

806 event handlers in the chain. 

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

808 

809 """ 

810 

811 _target_class_doc = "SomeClass" 

812 _dispatch_target = mapperlib.Mapper 

813 

814 @classmethod 

815 def _new_mapper_instance( 

816 cls, 

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

818 mapper: Mapper[_O], 

819 ) -> None: 

820 _MapperEventsHold.populate(class_, mapper) 

821 

822 @classmethod 

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

824 def _accept_with( 

825 cls, 

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

827 identifier: str, 

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

829 orm = util.preloaded.orm 

830 

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

832 util.warn_deprecated( 

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

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

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

836 "2.0", 

837 ) 

838 return mapperlib.Mapper 

839 elif isinstance(target, type): 

840 if issubclass(target, mapperlib.Mapper): 

841 return target 

842 else: 

843 mapper = _mapper_or_none(target) 

844 if mapper is not None: 

845 return mapper 

846 else: 

847 return _MapperEventsHold(target) 

848 else: 

849 return target 

850 

851 @classmethod 

852 def _listen( 

853 cls, 

854 event_key: _EventKey[_ET], 

855 raw: bool = False, 

856 retval: bool = False, 

857 propagate: bool = False, 

858 **kw: Any, 

859 ) -> None: 

860 target, identifier, fn = ( 

861 event_key.dispatch_target, 

862 event_key.identifier, 

863 event_key._listen_fn, 

864 ) 

865 

866 if ( 

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

868 and target is not mapperlib.Mapper 

869 ): 

870 util.warn( 

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

872 "only invoke with the Mapper class " 

873 "as the target." 

874 ) 

875 

876 if not raw or not retval: 

877 if not raw: 

878 meth = getattr(cls, identifier) 

879 try: 

880 target_index = ( 

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

882 ) 

883 except ValueError: 

884 target_index = None 

885 

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

887 if not raw and target_index is not None: 

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

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

890 if not retval: 

891 fn(*arg, **kw) 

892 return interfaces.EXT_CONTINUE 

893 else: 

894 return fn(*arg, **kw) 

895 

896 event_key = event_key.with_wrapper(wrap) 

897 

898 if propagate: 

899 for mapper in target.self_and_descendants: 

900 event_key.with_dispatch_target(mapper).base_listen( 

901 propagate=True, **kw 

902 ) 

903 else: 

904 event_key.base_listen(**kw) 

905 

906 @classmethod 

907 def _clear(cls) -> None: 

908 super()._clear() 

909 _MapperEventsHold._clear() 

910 

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

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

913 before instrumentation is applied to the mapped class. 

914 

915 This event is the earliest phase of mapper construction. 

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

917 receive an event within initial mapper construction where basic 

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

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

920 be a better choice. 

921 

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

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

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

925 

926 Base = declarative_base() 

927 

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

929 def on_new_class(mapper, cls_): 

930 " ... " 

931 

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

933 of this event. 

934 :param class\_: the mapped class. 

935 

936 .. seealso:: 

937 

938 :meth:`_orm.MapperEvents.after_mapper_constructed` 

939 

940 """ 

941 

942 def after_mapper_constructed( 

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

944 ) -> None: 

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

946 fully constructed. 

947 

948 This event is called after the initial constructor for 

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

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

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

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

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

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

955 

956 This event differs from the 

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

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

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

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

961 wish to create additional mapped classes in response to the 

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

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

964 

965 .. versionadded:: 2.0.2 

966 

967 .. seealso:: 

968 

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

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

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

972 objects. 

973 

974 """ 

975 

976 def before_mapper_configured( 

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

978 ) -> None: 

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

980 

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

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

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

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

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

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

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

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

989 available mappers. 

990 

991 In comparison to the other configure-level events, 

992 :meth:`.MapperEvents.before_configured`, 

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

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

995 :meth;`.MapperEvents.before_mapper_configured` event provides for a 

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

997 parameter. 

998 

999 .. versionadded:: 1.3 

1000 

1001 e.g.:: 

1002 

1003 from sqlalchemy.orm import EXT_SKIP 

1004 

1005 Base = declarative_base() 

1006 

1007 DontConfigureBase = declarative_base() 

1008 

1009 @event.listens_for( 

1010 DontConfigureBase, 

1011 "before_mapper_configured", retval=True, propagate=True) 

1012 def dont_configure(mapper, cls): 

1013 return EXT_SKIP 

1014 

1015 

1016 .. seealso:: 

1017 

1018 :meth:`.MapperEvents.before_configured` 

1019 

1020 :meth:`.MapperEvents.after_configured` 

1021 

1022 :meth:`.MapperEvents.mapper_configured` 

1023 

1024 """ 

1025 

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

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

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

1029 

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

1031 for each mapper that is encountered when the 

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

1033 list of not-yet-configured mappers. 

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

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

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

1037 detected. 

1038 

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

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

1041 other mappers; they might still be pending within the 

1042 configuration operation. Bidirectional relationships that 

1043 are instead configured via the 

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

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

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

1047 exist. 

1048 

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

1050 to go including backrefs that are defined only on other 

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

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

1053 fully configured. 

1054 

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

1056 :meth:`.MapperEvents.before_configured` or 

1057 :meth:`.MapperEvents.after_configured`, 

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

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

1060 a particular mapper. The event is therefore useful for 

1061 configurational steps that benefit from being invoked just once 

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

1063 configurations are necessarily ready yet. 

1064 

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

1066 of this event. 

1067 :param class\_: the mapped class. 

1068 

1069 .. seealso:: 

1070 

1071 :meth:`.MapperEvents.before_configured` 

1072 

1073 :meth:`.MapperEvents.after_configured` 

1074 

1075 :meth:`.MapperEvents.before_mapper_configured` 

1076 

1077 """ 

1078 # TODO: need coverage for this event 

1079 

1080 def before_configured(self) -> None: 

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

1082 

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

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

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

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

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

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

1089 detected. 

1090 

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

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

1093 for all mappings as a whole:: 

1094 

1095 from sqlalchemy.orm import Mapper 

1096 

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

1098 def go(): 

1099 ... 

1100 

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

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

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

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

1105 on a per-mapper basis. 

1106 

1107 Theoretically this event is called once per 

1108 application, but is actually called any time new mappers 

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

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

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

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

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

1114 

1115 from sqlalchemy.orm import mapper 

1116 

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

1118 def go(): 

1119 ... 

1120 

1121 

1122 .. seealso:: 

1123 

1124 :meth:`.MapperEvents.before_mapper_configured` 

1125 

1126 :meth:`.MapperEvents.mapper_configured` 

1127 

1128 :meth:`.MapperEvents.after_configured` 

1129 

1130 """ 

1131 

1132 def after_configured(self) -> None: 

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

1134 

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

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

1137 invoked, after the function has completed its work. 

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

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

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

1141 detected. 

1142 

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

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

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

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

1147 available for any mappers that were pending. 

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

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

1150 

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

1152 and not to individual mappings or 

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

1154 

1155 from sqlalchemy.orm import Mapper 

1156 

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

1158 def go(): 

1159 # ... 

1160 

1161 Theoretically this event is called once per 

1162 application, but is actually called any time new mappers 

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

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

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

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

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

1168 

1169 from sqlalchemy.orm import mapper 

1170 

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

1172 def go(): 

1173 # ... 

1174 

1175 .. seealso:: 

1176 

1177 :meth:`.MapperEvents.before_mapper_configured` 

1178 

1179 :meth:`.MapperEvents.mapper_configured` 

1180 

1181 :meth:`.MapperEvents.before_configured` 

1182 

1183 """ 

1184 

1185 def before_insert( 

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

1187 ) -> None: 

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

1189 is emitted corresponding to that instance. 

1190 

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

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

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

1194 :ref:`orm_expression_update_delete`. To intercept ORM 

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

1196 

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

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

1199 as to emit additional SQL statements on the given 

1200 connection. 

1201 

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

1203 same class before their INSERT statements are emitted at 

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

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

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

1207 batches of instances to be broken up into individual 

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

1209 steps. 

1210 

1211 .. warning:: 

1212 

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

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

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

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

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

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

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

1220 

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

1222 of this event. 

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

1224 emit INSERT statements for this instance. This 

1225 provides a handle into the current transaction on the 

1226 target database specific to this instance. 

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

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

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

1230 object associated with the instance. 

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

1232 

1233 .. seealso:: 

1234 

1235 :ref:`session_persistence_events` 

1236 

1237 """ 

1238 

1239 def after_insert( 

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

1241 ) -> None: 

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

1243 is emitted corresponding to that instance. 

1244 

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

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

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

1248 :ref:`orm_expression_update_delete`. To intercept ORM 

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

1250 

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

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

1253 as to emit additional SQL statements on the given 

1254 connection. 

1255 

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

1257 same class after their INSERT statements have been 

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

1259 rare case that this is not desirable, the 

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

1261 which will cause batches of instances to be broken up 

1262 into individual (and more poorly performing) 

1263 event->persist->event steps. 

1264 

1265 .. warning:: 

1266 

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

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

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

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

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

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

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

1274 

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

1276 of this event. 

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

1278 emit INSERT statements for this instance. This 

1279 provides a handle into the current transaction on the 

1280 target database specific to this instance. 

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

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

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

1284 object associated with the instance. 

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

1286 

1287 .. seealso:: 

1288 

1289 :ref:`session_persistence_events` 

1290 

1291 """ 

1292 

1293 def before_update( 

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

1295 ) -> None: 

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

1297 is emitted corresponding to that instance. 

1298 

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

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

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

1302 :ref:`orm_expression_update_delete`. To intercept ORM 

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

1304 

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

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

1307 as to emit additional SQL statements on the given 

1308 connection. 

1309 

1310 This method is called for all instances that are 

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

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

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

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

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

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

1317 statement will be issued. This means that an instance 

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

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

1320 issued, although you can affect the outcome here by 

1321 modifying attributes so that a net change in value does 

1322 exist. 

1323 

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

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

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

1327 include_collections=False)``. 

1328 

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

1330 same class before their UPDATE statements are emitted at 

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

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

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

1334 batches of instances to be broken up into individual 

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

1336 steps. 

1337 

1338 .. warning:: 

1339 

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

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

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

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

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

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

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

1347 

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

1349 of this event. 

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

1351 emit UPDATE statements for this instance. This 

1352 provides a handle into the current transaction on the 

1353 target database specific to this instance. 

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

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

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

1357 object associated with the instance. 

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

1359 

1360 .. seealso:: 

1361 

1362 :ref:`session_persistence_events` 

1363 

1364 """ 

1365 

1366 def after_update( 

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

1368 ) -> None: 

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

1370 is emitted corresponding to that instance. 

1371 

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

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

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

1375 :ref:`orm_expression_update_delete`. To intercept ORM 

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

1377 

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

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

1380 as to emit additional SQL statements on the given 

1381 connection. 

1382 

1383 This method is called for all instances that are 

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

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

1386 no UPDATE statement has proceeded. An object is marked 

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

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

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

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

1391 statement will be issued. This means that an instance 

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

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

1394 issued. 

1395 

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

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

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

1399 include_collections=False)``. 

1400 

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

1402 same class after their UPDATE statements have been emitted at 

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

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

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

1406 batches of instances to be broken up into individual 

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

1408 steps. 

1409 

1410 .. warning:: 

1411 

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

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

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

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

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

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

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

1419 

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

1421 of this event. 

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

1423 emit UPDATE statements for this instance. This 

1424 provides a handle into the current transaction on the 

1425 target database specific to this instance. 

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

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

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

1429 object associated with the instance. 

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

1431 

1432 .. seealso:: 

1433 

1434 :ref:`session_persistence_events` 

1435 

1436 """ 

1437 

1438 def before_delete( 

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

1440 ) -> None: 

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

1442 is emitted corresponding to that instance. 

1443 

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

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

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

1447 :ref:`orm_expression_update_delete`. To intercept ORM 

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

1449 

1450 This event is used to emit additional SQL statements on 

1451 the given connection as well as to perform application 

1452 specific bookkeeping related to a deletion event. 

1453 

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

1455 same class before their DELETE statements are emitted at 

1456 once in a later step. 

1457 

1458 .. warning:: 

1459 

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

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

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

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

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

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

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

1467 

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

1469 of this event. 

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

1471 emit DELETE statements for this instance. This 

1472 provides a handle into the current transaction on the 

1473 target database specific to this instance. 

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

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

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

1477 object associated with the instance. 

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

1479 

1480 .. seealso:: 

1481 

1482 :ref:`session_persistence_events` 

1483 

1484 """ 

1485 

1486 def after_delete( 

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

1488 ) -> None: 

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

1490 has been emitted corresponding to that instance. 

1491 

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

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

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

1495 :ref:`orm_expression_update_delete`. To intercept ORM 

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

1497 

1498 This event is used to emit additional SQL statements on 

1499 the given connection as well as to perform application 

1500 specific bookkeeping related to a deletion event. 

1501 

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

1503 same class after their DELETE statements have been emitted at 

1504 once in a previous step. 

1505 

1506 .. warning:: 

1507 

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

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

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

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

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

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

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

1515 

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

1517 of this event. 

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

1519 emit DELETE statements for this instance. This 

1520 provides a handle into the current transaction on the 

1521 target database specific to this instance. 

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

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

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

1525 object associated with the instance. 

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

1527 

1528 .. seealso:: 

1529 

1530 :ref:`session_persistence_events` 

1531 

1532 """ 

1533 

1534 

1535class _MapperEventsHold(_EventsHold[_ET]): 

1536 all_holds = weakref.WeakKeyDictionary() 

1537 

1538 def resolve( 

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

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

1541 return _mapper_or_none(class_) 

1542 

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

1544 pass 

1545 

1546 dispatch = event.dispatcher(HoldMapperEvents) 

1547 

1548 

1549_sessionevents_lifecycle_event_names: Set[str] = set() 

1550 

1551 

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

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

1554 

1555 e.g.:: 

1556 

1557 from sqlalchemy import event 

1558 from sqlalchemy.orm import sessionmaker 

1559 

1560 def my_before_commit(session): 

1561 print("before commit!") 

1562 

1563 Session = sessionmaker() 

1564 

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

1566 

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

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

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

1570 

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

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

1573 globally. 

1574 

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

1576 to applicable event listener functions that work on individual 

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

1578 object, rather than the mapped instance itself. 

1579 

1580 .. versionadded:: 1.3.14 

1581 

1582 :param restore_load_context=False: Applies to the 

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

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

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

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

1587 within this event if this flag is not set. 

1588 

1589 .. versionadded:: 1.3.14 

1590 

1591 """ 

1592 

1593 _target_class_doc = "SomeSessionClassOrObject" 

1594 

1595 _dispatch_target = Session 

1596 

1597 def _lifecycle_event( # type: ignore [misc] 

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

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

1600 _sessionevents_lifecycle_event_names.add(fn.__name__) 

1601 return fn 

1602 

1603 @classmethod 

1604 def _accept_with( # type: ignore [return] 

1605 cls, target: Any, identifier: str 

1606 ) -> Union[Session, type]: 

1607 if isinstance(target, scoped_session): 

1608 target = target.session_factory 

1609 if not isinstance(target, sessionmaker) and ( 

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

1611 ): 

1612 raise exc.ArgumentError( 

1613 "Session event listen on a scoped_session " 

1614 "requires that its creation callable " 

1615 "is associated with the Session class." 

1616 ) 

1617 

1618 if isinstance(target, sessionmaker): 

1619 return target.class_ 

1620 elif isinstance(target, type): 

1621 if issubclass(target, scoped_session): 

1622 return Session 

1623 elif issubclass(target, Session): 

1624 return target 

1625 elif isinstance(target, Session): 

1626 return target 

1627 elif hasattr(target, "_no_async_engine_events"): 

1628 target._no_async_engine_events() 

1629 else: 

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

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

1632 

1633 @classmethod 

1634 def _listen( 

1635 cls, 

1636 event_key: Any, 

1637 *, 

1638 raw: bool = False, 

1639 restore_load_context: bool = False, 

1640 **kw: Any, 

1641 ) -> None: 

1642 is_instance_event = ( 

1643 event_key.identifier in _sessionevents_lifecycle_event_names 

1644 ) 

1645 

1646 if is_instance_event: 

1647 if not raw or restore_load_context: 

1648 fn = event_key._listen_fn 

1649 

1650 def wrap( 

1651 session: Session, 

1652 state: InstanceState[_O], 

1653 *arg: Any, 

1654 **kw: Any, 

1655 ) -> Optional[Any]: 

1656 if not raw: 

1657 target = state.obj() 

1658 if target is None: 

1659 # existing behavior is that if the object is 

1660 # garbage collected, no event is emitted 

1661 return None 

1662 else: 

1663 target = state # type: ignore [assignment] 

1664 if restore_load_context: 

1665 runid = state.runid 

1666 try: 

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

1668 finally: 

1669 if restore_load_context: 

1670 state.runid = runid 

1671 

1672 event_key = event_key.with_wrapper(wrap) 

1673 

1674 event_key.base_listen(**kw) 

1675 

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

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

1678 ORM :class:`.Session` object. 

1679 

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

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

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

1683 SQLAlchemy 1.4, all ORM queries that run through the 

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

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

1686 will participate in this event. 

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

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

1689 process described at :ref:`session_flushing`. 

1690 

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

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

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

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

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

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

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

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

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

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

1701 :meth:`.ConnectionEvents.before_execute` and 

1702 :meth:`.ConnectionEvents.before_cursor_execute`. 

1703 

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

1705 emitted internally within the ORM flush process, 

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

1707 intercept steps within the flush process, see the event 

1708 hooks described at :ref:`session_persistence_events` as 

1709 well as :ref:`session_persistence_mapper`. 

1710 

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

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

1713 performs. The intended use for this includes sharding and 

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

1715 across multiple database connections, returning a result that is 

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

1717 instead returning data from a cache. 

1718 

1719 The hook intends to replace the use of the 

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

1721 to SQLAlchemy 1.4. 

1722 

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

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

1725 as helper functions used to derive other commonly required 

1726 information. See that object for details. 

1727 

1728 .. seealso:: 

1729 

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

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

1732 

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

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

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

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

1737 and parameters as well as an option that allows programmatic 

1738 invocation of the statement at any point. 

1739 

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

1741 :meth:`_orm.SessionEvents.do_orm_execute` 

1742 

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

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

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

1746 

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

1748 extension relies upon the 

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

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

1751 

1752 

1753 .. versionadded:: 1.4 

1754 

1755 """ 

1756 

1757 def after_transaction_create( 

1758 self, session: Session, transaction: SessionTransaction 

1759 ) -> None: 

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

1761 

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

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

1764 overall, as opposed to when transactions are begun 

1765 on individual database connections. It is also invoked 

1766 for nested transactions and subtransactions, and is always 

1767 matched by a corresponding 

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

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

1770 

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

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

1773 

1774 To detect if this is the outermost 

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

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

1777 is ``None``:: 

1778 

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

1780 def after_transaction_create(session, transaction): 

1781 if transaction.parent is None: 

1782 # work with top-level transaction 

1783 

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

1785 :attr:`.SessionTransaction.nested` attribute:: 

1786 

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

1788 def after_transaction_create(session, transaction): 

1789 if transaction.nested: 

1790 # work with SAVEPOINT transaction 

1791 

1792 

1793 .. seealso:: 

1794 

1795 :class:`.SessionTransaction` 

1796 

1797 :meth:`~.SessionEvents.after_transaction_end` 

1798 

1799 """ 

1800 

1801 def after_transaction_end( 

1802 self, session: Session, transaction: SessionTransaction 

1803 ) -> None: 

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

1805 

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

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

1808 objects in use, including those for nested transactions 

1809 and subtransactions, and is always matched by a corresponding 

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

1811 

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

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

1814 

1815 To detect if this is the outermost 

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

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

1818 is ``None``:: 

1819 

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

1821 def after_transaction_end(session, transaction): 

1822 if transaction.parent is None: 

1823 # work with top-level transaction 

1824 

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

1826 :attr:`.SessionTransaction.nested` attribute:: 

1827 

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

1829 def after_transaction_end(session, transaction): 

1830 if transaction.nested: 

1831 # work with SAVEPOINT transaction 

1832 

1833 

1834 .. seealso:: 

1835 

1836 :class:`.SessionTransaction` 

1837 

1838 :meth:`~.SessionEvents.after_transaction_create` 

1839 

1840 """ 

1841 

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

1843 """Execute before commit is called. 

1844 

1845 .. note:: 

1846 

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

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

1849 many times within the scope of a transaction. 

1850 For interception of these events, use the 

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

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

1853 :meth:`~.SessionEvents.after_flush_postexec` 

1854 events. 

1855 

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

1857 

1858 .. seealso:: 

1859 

1860 :meth:`~.SessionEvents.after_commit` 

1861 

1862 :meth:`~.SessionEvents.after_begin` 

1863 

1864 :meth:`~.SessionEvents.after_transaction_create` 

1865 

1866 :meth:`~.SessionEvents.after_transaction_end` 

1867 

1868 """ 

1869 

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

1871 """Execute after a commit has occurred. 

1872 

1873 .. note:: 

1874 

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

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

1877 many times within the scope of a transaction. 

1878 For interception of these events, use the 

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

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

1881 :meth:`~.SessionEvents.after_flush_postexec` 

1882 events. 

1883 

1884 .. note:: 

1885 

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

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

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

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

1890 event. 

1891 

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

1893 

1894 .. seealso:: 

1895 

1896 :meth:`~.SessionEvents.before_commit` 

1897 

1898 :meth:`~.SessionEvents.after_begin` 

1899 

1900 :meth:`~.SessionEvents.after_transaction_create` 

1901 

1902 :meth:`~.SessionEvents.after_transaction_end` 

1903 

1904 """ 

1905 

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

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

1908 

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

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

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

1912 DBAPI transaction has already been rolled back. In many 

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

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

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

1916 which is active after the outermost rollback has proceeded, 

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

1918 :attr:`.Session.is_active` flag. 

1919 

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

1921 

1922 """ 

1923 

1924 def after_soft_rollback( 

1925 self, session: Session, previous_transaction: SessionTransaction 

1926 ) -> None: 

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

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

1929 

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

1931 the innermost rollback that calls the DBAPI's 

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

1933 calls that only pop themselves from the transaction stack. 

1934 

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

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

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

1938 

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

1940 def do_something(session, previous_transaction): 

1941 if session.is_active: 

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

1943 

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

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

1946 transactional marker object which was just closed. The current 

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

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

1949 

1950 """ 

1951 

1952 def before_flush( 

1953 self, 

1954 session: Session, 

1955 flush_context: UOWTransaction, 

1956 instances: Optional[Sequence[_O]], 

1957 ) -> None: 

1958 """Execute before flush process has started. 

1959 

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

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

1962 which handles the details of the flush. 

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

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

1965 (note this usage is deprecated). 

1966 

1967 .. seealso:: 

1968 

1969 :meth:`~.SessionEvents.after_flush` 

1970 

1971 :meth:`~.SessionEvents.after_flush_postexec` 

1972 

1973 :ref:`session_persistence_events` 

1974 

1975 """ 

1976 

1977 def after_flush( 

1978 self, session: Session, flush_context: UOWTransaction 

1979 ) -> None: 

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

1981 called. 

1982 

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

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

1985 as the history settings on instance attributes. 

1986 

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

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

1989 internal state to reflect those changes, including that newly 

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

1991 emitted within this event such as loads of related items 

1992 may produce new identity map entries that will immediately 

1993 be replaced, sometimes causing confusing results. SQLAlchemy will 

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

1995 

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

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

1998 which handles the details of the flush. 

1999 

2000 .. seealso:: 

2001 

2002 :meth:`~.SessionEvents.before_flush` 

2003 

2004 :meth:`~.SessionEvents.after_flush_postexec` 

2005 

2006 :ref:`session_persistence_events` 

2007 

2008 """ 

2009 

2010 def after_flush_postexec( 

2011 self, session: Session, flush_context: UOWTransaction 

2012 ) -> None: 

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

2014 state occurs. 

2015 

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

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

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

2019 transaction or participated in a larger transaction. 

2020 

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

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

2023 which handles the details of the flush. 

2024 

2025 

2026 .. seealso:: 

2027 

2028 :meth:`~.SessionEvents.before_flush` 

2029 

2030 :meth:`~.SessionEvents.after_flush` 

2031 

2032 :ref:`session_persistence_events` 

2033 

2034 """ 

2035 

2036 def after_begin( 

2037 self, 

2038 session: Session, 

2039 transaction: SessionTransaction, 

2040 connection: Connection, 

2041 ) -> None: 

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

2043 

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

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

2046 To invoke SQL operations within this hook, use the 

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

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

2049 directly. 

2050 

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

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

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

2054 which will be used for SQL statements. 

2055 

2056 .. seealso:: 

2057 

2058 :meth:`~.SessionEvents.before_commit` 

2059 

2060 :meth:`~.SessionEvents.after_commit` 

2061 

2062 :meth:`~.SessionEvents.after_transaction_create` 

2063 

2064 :meth:`~.SessionEvents.after_transaction_end` 

2065 

2066 """ 

2067 

2068 @_lifecycle_event 

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

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

2071 

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

2073 the object to be part of the session. 

2074 

2075 .. seealso:: 

2076 

2077 :meth:`~.SessionEvents.after_attach` 

2078 

2079 :ref:`session_lifecycle_events` 

2080 

2081 """ 

2082 

2083 @_lifecycle_event 

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

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

2086 

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

2088 

2089 .. note:: 

2090 

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

2092 has been fully associated with the session, which is 

2093 different than previous releases. For event 

2094 handlers that require the object not yet 

2095 be part of session state (such as handlers which 

2096 may autoflush while the target object is not 

2097 yet complete) consider the 

2098 new :meth:`.before_attach` event. 

2099 

2100 .. seealso:: 

2101 

2102 :meth:`~.SessionEvents.before_attach` 

2103 

2104 :ref:`session_lifecycle_events` 

2105 

2106 """ 

2107 

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

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

2110 has been called. 

2111 

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

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

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

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

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

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

2118 these calls. 

2119 

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

2121 details about the update, including these attributes: 

2122 

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

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

2125 object that this update operation 

2126 was called upon. 

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

2128 :meth:`_query.Query.update`. 

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

2130 returned as a result of the 

2131 bulk UPDATE operation. 

2132 

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

2134 ``QueryContext`` object associated with it. 

2135 

2136 .. seealso:: 

2137 

2138 :meth:`.QueryEvents.before_compile_update` 

2139 

2140 :meth:`.SessionEvents.after_bulk_delete` 

2141 

2142 """ 

2143 

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

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

2146 has been called. 

2147 

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

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

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

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

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

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

2154 these calls. 

2155 

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

2157 details about the update, including these attributes: 

2158 

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

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

2161 object that this update operation 

2162 was called upon. 

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

2164 returned as a result of the 

2165 bulk DELETE operation. 

2166 

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

2168 ``QueryContext`` object associated with it. 

2169 

2170 .. seealso:: 

2171 

2172 :meth:`.QueryEvents.before_compile_delete` 

2173 

2174 :meth:`.SessionEvents.after_bulk_update` 

2175 

2176 """ 

2177 

2178 @_lifecycle_event 

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

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

2181 object. 

2182 

2183 This event is a specialization of the 

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

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

2186 :meth:`.Session.add` call. 

2187 

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

2189 

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

2191 

2192 .. seealso:: 

2193 

2194 :ref:`session_lifecycle_events` 

2195 

2196 """ 

2197 

2198 @_lifecycle_event 

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

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

2201 object. 

2202 

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

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

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

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

2207 

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

2209 

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

2211 

2212 .. seealso:: 

2213 

2214 :ref:`session_lifecycle_events` 

2215 

2216 """ 

2217 

2218 @_lifecycle_event 

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

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

2221 object. 

2222 

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

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

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

2226 

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

2228 

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

2230 

2231 .. seealso:: 

2232 

2233 :ref:`session_lifecycle_events` 

2234 

2235 """ 

2236 

2237 @_lifecycle_event 

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

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

2240 object. 

2241 

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

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

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

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

2246 when the event is called. 

2247 

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

2249 

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

2251 

2252 .. seealso:: 

2253 

2254 :ref:`session_lifecycle_events` 

2255 

2256 """ 

2257 

2258 @_lifecycle_event 

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

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

2261 object. 

2262 

2263 This event is a specialization of the 

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

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

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

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

2268 associated with the 

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

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

2271 

2272 .. note:: 

2273 

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

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

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

2277 check the ``deleted`` flag sent to the 

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

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

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

2281 objects need to be intercepted before the flush. 

2282 

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

2284 

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

2286 

2287 .. seealso:: 

2288 

2289 :ref:`session_lifecycle_events` 

2290 

2291 """ 

2292 

2293 @_lifecycle_event 

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

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

2296 object. 

2297 

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

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

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

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

2302 with the other session lifecycle events smoothly. The object 

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

2304 this event is called. 

2305 

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

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

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

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

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

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

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

2313 works in the same manner as that of 

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

2315 resolve this scenario. 

2316 

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

2318 

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

2320 

2321 .. seealso:: 

2322 

2323 :ref:`session_lifecycle_events` 

2324 

2325 """ 

2326 

2327 @_lifecycle_event 

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

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

2330 object. 

2331 

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

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

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

2335 transaction completes. 

2336 

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

2338 to the persistent state, and the 

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

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

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

2342 event. 

2343 

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

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

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

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

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

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

2350 invoked at the end of a flush. 

2351 

2352 .. seealso:: 

2353 

2354 :ref:`session_lifecycle_events` 

2355 

2356 """ 

2357 

2358 @_lifecycle_event 

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

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

2361 object. 

2362 

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

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

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

2366 any other circumstances. 

2367 

2368 .. seealso:: 

2369 

2370 :ref:`session_lifecycle_events` 

2371 

2372 """ 

2373 

2374 @_lifecycle_event 

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

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

2377 object. 

2378 

2379 This event is invoked when a deleted object is evicted 

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

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

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

2383 state to the detached state. 

2384 

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

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

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

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

2389 

2390 .. seealso:: 

2391 

2392 :ref:`session_lifecycle_events` 

2393 

2394 """ 

2395 

2396 @_lifecycle_event 

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

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

2399 object. 

2400 

2401 This event is invoked when a persistent object is evicted 

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

2403 to happen, including: 

2404 

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

2406 or :meth:`.Session.close` 

2407 

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

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

2410 

2411 

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

2413 

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

2415 

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

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

2418 

2419 

2420 .. seealso:: 

2421 

2422 :ref:`session_lifecycle_events` 

2423 

2424 """ 

2425 

2426 

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

2428 r"""Define events for object attributes. 

2429 

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

2431 target class. 

2432 

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

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

2435 

2436 from sqlalchemy import event 

2437 

2438 @event.listens_for(MyClass.collection, 'append', propagate=True) 

2439 def my_append_listener(target, value, initiator): 

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

2441 

2442 

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

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

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

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

2447 

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

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

2450 

2451 return re.sub(r'\D', '', value) 

2452 

2453 # setup listener on UserContact.phone attribute, instructing 

2454 # it to use the return value 

2455 listen(UserContact.phone, 'set', validate_phone, retval=True) 

2456 

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

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

2459 

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

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

2462 as when using mapper inheritance patterns:: 

2463 

2464 

2465 @event.listens_for(MySuperClass.attr, 'set', propagate=True) 

2466 def receive_set(target, value, initiator): 

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

2468 

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

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

2471 

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

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

2474 replaced unconditionally, even if this requires firing off 

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

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

2477 :func:`_orm.relationship`. 

2478 

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

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

2481 for attributes of the same name on all current subclasses 

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

2483 class, using an additional listener that listens for 

2484 instrumentation events. 

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

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

2487 object, rather than the mapped instance itself. 

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

2489 listening must return the "value" argument from the 

2490 function. This gives the listening function the opportunity 

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

2492 or "append" event. 

2493 

2494 """ 

2495 

2496 _target_class_doc = "SomeClass.some_attribute" 

2497 _dispatch_target = QueryableAttribute 

2498 

2499 @staticmethod 

2500 def _set_dispatch( 

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

2502 ) -> _Dispatch[Any]: 

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

2504 dispatch_cls._active_history = False 

2505 return dispatch 

2506 

2507 @classmethod 

2508 def _accept_with( 

2509 cls, 

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

2511 identifier: str, 

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

2513 # TODO: coverage 

2514 if isinstance(target, interfaces.MapperProperty): 

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

2516 else: 

2517 return target 

2518 

2519 @classmethod 

2520 def _listen( # type: ignore [override] 

2521 cls, 

2522 event_key: _EventKey[QueryableAttribute[Any]], 

2523 active_history: bool = False, 

2524 raw: bool = False, 

2525 retval: bool = False, 

2526 propagate: bool = False, 

2527 include_key: bool = False, 

2528 ) -> None: 

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

2530 

2531 if active_history: 

2532 target.dispatch._active_history = True 

2533 

2534 if not raw or not retval or not include_key: 

2535 

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

2537 if not raw: 

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

2539 if not retval: 

2540 if arg: 

2541 value = arg[0] 

2542 else: 

2543 value = None 

2544 if include_key: 

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

2546 else: 

2547 fn(target, *arg) 

2548 return value 

2549 else: 

2550 if include_key: 

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

2552 else: 

2553 return fn(target, *arg) 

2554 

2555 event_key = event_key.with_wrapper(wrap) 

2556 

2557 event_key.base_listen(propagate=propagate) 

2558 

2559 if propagate: 

2560 manager = instrumentation.manager_of_class(target.class_) 

2561 

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

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

2564 propagate=True 

2565 ) 

2566 if active_history: 

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

2568 

2569 def append( 

2570 self, 

2571 target: _O, 

2572 value: _T, 

2573 initiator: Event, 

2574 *, 

2575 key: EventConstants = NO_KEY, 

2576 ) -> Optional[_T]: 

2577 """Receive a collection append event. 

2578 

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

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

2581 as for a "bulk replace" operation. 

2582 

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

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

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

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

2587 is registered with ``retval=True``, the listener 

2588 function must return this value, or a new value which 

2589 replaces it. 

2590 :param initiator: An instance of :class:`.attributes.Event` 

2591 representing the initiation of the event. May be modified 

2592 from its original value by backref handlers in order to control 

2593 chained event propagation, as well as be inspected for information 

2594 about the source of the event. 

2595 :param key: When the event is established using the 

2596 :paramref:`.AttributeEvents.include_key` parameter set to 

2597 True, this will be the key used in the operation, such as 

2598 ``collection[some_key_or_index] = value``. 

2599 The parameter is not passed 

2600 to the event at all if the the 

2601 :paramref:`.AttributeEvents.include_key` 

2602 was not used to set up the event; this is to allow backwards 

2603 compatibility with existing event handlers that don't include the 

2604 ``key`` parameter. 

2605 

2606 .. versionadded:: 2.0 

2607 

2608 :return: if the event was registered with ``retval=True``, 

2609 the given value, or a new effective value, should be returned. 

2610 

2611 .. seealso:: 

2612 

2613 :class:`.AttributeEvents` - background on listener options such 

2614 as propagation to subclasses. 

2615 

2616 :meth:`.AttributeEvents.bulk_replace` 

2617 

2618 """ 

2619 

2620 def append_wo_mutation( 

2621 self, 

2622 target: _O, 

2623 value: _T, 

2624 initiator: Event, 

2625 *, 

2626 key: EventConstants = NO_KEY, 

2627 ) -> None: 

2628 """Receive a collection append event where the collection was not 

2629 actually mutated. 

2630 

2631 This event differs from :meth:`_orm.AttributeEvents.append` in that 

2632 it is fired off for de-duplicating collections such as sets and 

2633 dictionaries, when the object already exists in the target collection. 

2634 The event does not have a return value and the identity of the 

2635 given object cannot be changed. 

2636 

2637 The event is used for cascading objects into a :class:`_orm.Session` 

2638 when the collection has already been mutated via a backref event. 

2639 

2640 :param target: the object instance receiving the event. 

2641 If the listener is registered with ``raw=True``, this will 

2642 be the :class:`.InstanceState` object. 

2643 :param value: the value that would be appended if the object did not 

2644 already exist in the collection. 

2645 :param initiator: An instance of :class:`.attributes.Event` 

2646 representing the initiation of the event. May be modified 

2647 from its original value by backref handlers in order to control 

2648 chained event propagation, as well as be inspected for information 

2649 about the source of the event. 

2650 :param key: When the event is established using the 

2651 :paramref:`.AttributeEvents.include_key` parameter set to 

2652 True, this will be the key used in the operation, such as 

2653 ``collection[some_key_or_index] = value``. 

2654 The parameter is not passed 

2655 to the event at all if the the 

2656 :paramref:`.AttributeEvents.include_key` 

2657 was not used to set up the event; this is to allow backwards 

2658 compatibility with existing event handlers that don't include the 

2659 ``key`` parameter. 

2660 

2661 .. versionadded:: 2.0 

2662 

2663 :return: No return value is defined for this event. 

2664 

2665 .. versionadded:: 1.4.15 

2666 

2667 """ 

2668 

2669 def bulk_replace( 

2670 self, 

2671 target: _O, 

2672 values: Iterable[_T], 

2673 initiator: Event, 

2674 *, 

2675 keys: Optional[Iterable[EventConstants]] = None, 

2676 ) -> None: 

2677 """Receive a collection 'bulk replace' event. 

2678 

2679 This event is invoked for a sequence of values as they are incoming 

2680 to a bulk collection set operation, which can be 

2681 modified in place before the values are treated as ORM objects. 

2682 This is an "early hook" that runs before the bulk replace routine 

2683 attempts to reconcile which objects are already present in the 

2684 collection and which are being removed by the net replace operation. 

2685 

2686 It is typical that this method be combined with use of the 

2687 :meth:`.AttributeEvents.append` event. When using both of these 

2688 events, note that a bulk replace operation will invoke 

2689 the :meth:`.AttributeEvents.append` event for all new items, 

2690 even after :meth:`.AttributeEvents.bulk_replace` has been invoked 

2691 for the collection as a whole. In order to determine if an 

2692 :meth:`.AttributeEvents.append` event is part of a bulk replace, 

2693 use the symbol :attr:`~.attributes.OP_BULK_REPLACE` to test the 

2694 incoming initiator:: 

2695 

2696 from sqlalchemy.orm.attributes import OP_BULK_REPLACE 

2697 

2698 @event.listens_for(SomeObject.collection, "bulk_replace") 

2699 def process_collection(target, values, initiator): 

2700 values[:] = [_make_value(value) for value in values] 

2701 

2702 @event.listens_for(SomeObject.collection, "append", retval=True) 

2703 def process_collection(target, value, initiator): 

2704 # make sure bulk_replace didn't already do it 

2705 if initiator is None or initiator.op is not OP_BULK_REPLACE: 

2706 return _make_value(value) 

2707 else: 

2708 return value 

2709 

2710 .. versionadded:: 1.2 

2711 

2712 :param target: the object instance receiving the event. 

2713 If the listener is registered with ``raw=True``, this will 

2714 be the :class:`.InstanceState` object. 

2715 :param value: a sequence (e.g. a list) of the values being set. The 

2716 handler can modify this list in place. 

2717 :param initiator: An instance of :class:`.attributes.Event` 

2718 representing the initiation of the event. 

2719 :param keys: When the event is established using the 

2720 :paramref:`.AttributeEvents.include_key` parameter set to 

2721 True, this will be the sequence of keys used in the operation, 

2722 typically only for a dictionary update. The parameter is not passed 

2723 to the event at all if the the 

2724 :paramref:`.AttributeEvents.include_key` 

2725 was not used to set up the event; this is to allow backwards 

2726 compatibility with existing event handlers that don't include the 

2727 ``key`` parameter. 

2728 

2729 .. versionadded:: 2.0 

2730 

2731 .. seealso:: 

2732 

2733 :class:`.AttributeEvents` - background on listener options such 

2734 as propagation to subclasses. 

2735 

2736 

2737 """ 

2738 

2739 def remove( 

2740 self, 

2741 target: _O, 

2742 value: _T, 

2743 initiator: Event, 

2744 *, 

2745 key: EventConstants = NO_KEY, 

2746 ) -> None: 

2747 """Receive a collection remove event. 

2748 

2749 :param target: the object instance receiving the event. 

2750 If the listener is registered with ``raw=True``, this will 

2751 be the :class:`.InstanceState` object. 

2752 :param value: the value being removed. 

2753 :param initiator: An instance of :class:`.attributes.Event` 

2754 representing the initiation of the event. May be modified 

2755 from its original value by backref handlers in order to control 

2756 chained event propagation. 

2757 

2758 :param key: When the event is established using the 

2759 :paramref:`.AttributeEvents.include_key` parameter set to 

2760 True, this will be the key used in the operation, such as 

2761 ``del collection[some_key_or_index]``. The parameter is not passed 

2762 to the event at all if the the 

2763 :paramref:`.AttributeEvents.include_key` 

2764 was not used to set up the event; this is to allow backwards 

2765 compatibility with existing event handlers that don't include the 

2766 ``key`` parameter. 

2767 

2768 .. versionadded:: 2.0 

2769 

2770 :return: No return value is defined for this event. 

2771 

2772 

2773 .. seealso:: 

2774 

2775 :class:`.AttributeEvents` - background on listener options such 

2776 as propagation to subclasses. 

2777 

2778 """ 

2779 

2780 def set( 

2781 self, target: _O, value: _T, oldvalue: _T, initiator: Event 

2782 ) -> None: 

2783 """Receive a scalar set event. 

2784 

2785 :param target: the object instance receiving the event. 

2786 If the listener is registered with ``raw=True``, this will 

2787 be the :class:`.InstanceState` object. 

2788 :param value: the value being set. If this listener 

2789 is registered with ``retval=True``, the listener 

2790 function must return this value, or a new value which 

2791 replaces it. 

2792 :param oldvalue: the previous value being replaced. This 

2793 may also be the symbol ``NEVER_SET`` or ``NO_VALUE``. 

2794 If the listener is registered with ``active_history=True``, 

2795 the previous value of the attribute will be loaded from 

2796 the database if the existing value is currently unloaded 

2797 or expired. 

2798 :param initiator: An instance of :class:`.attributes.Event` 

2799 representing the initiation of the event. May be modified 

2800 from its original value by backref handlers in order to control 

2801 chained event propagation. 

2802 

2803 :return: if the event was registered with ``retval=True``, 

2804 the given value, or a new effective value, should be returned. 

2805 

2806 .. seealso:: 

2807 

2808 :class:`.AttributeEvents` - background on listener options such 

2809 as propagation to subclasses. 

2810 

2811 """ 

2812 

2813 def init_scalar( 

2814 self, target: _O, value: _T, dict_: Dict[Any, Any] 

2815 ) -> None: 

2816 r"""Receive a scalar "init" event. 

2817 

2818 This event is invoked when an uninitialized, unpersisted scalar 

2819 attribute is accessed, e.g. read:: 

2820 

2821 

2822 x = my_object.some_attribute 

2823 

2824 The ORM's default behavior when this occurs for an un-initialized 

2825 attribute is to return the value ``None``; note this differs from 

2826 Python's usual behavior of raising ``AttributeError``. The 

2827 event here can be used to customize what value is actually returned, 

2828 with the assumption that the event listener would be mirroring 

2829 a default generator that is configured on the Core 

2830 :class:`_schema.Column` 

2831 object as well. 

2832 

2833 Since a default generator on a :class:`_schema.Column` 

2834 might also produce 

2835 a changing value such as a timestamp, the 

2836 :meth:`.AttributeEvents.init_scalar` 

2837 event handler can also be used to **set** the newly returned value, so 

2838 that a Core-level default generation function effectively fires off 

2839 only once, but at the moment the attribute is accessed on the 

2840 non-persisted object. Normally, no change to the object's state 

2841 is made when an uninitialized attribute is accessed (much older 

2842 SQLAlchemy versions did in fact change the object's state). 

2843 

2844 If a default generator on a column returned a particular constant, 

2845 a handler might be used as follows:: 

2846 

2847 SOME_CONSTANT = 3.1415926 

2848 

2849 class MyClass(Base): 

2850 # ... 

2851 

2852 some_attribute = Column(Numeric, default=SOME_CONSTANT) 

2853 

2854 @event.listens_for( 

2855 MyClass.some_attribute, "init_scalar", 

2856 retval=True, propagate=True) 

2857 def _init_some_attribute(target, dict_, value): 

2858 dict_['some_attribute'] = SOME_CONSTANT 

2859 return SOME_CONSTANT 

2860 

2861 Above, we initialize the attribute ``MyClass.some_attribute`` to the 

2862 value of ``SOME_CONSTANT``. The above code includes the following 

2863 features: 

2864 

2865 * By setting the value ``SOME_CONSTANT`` in the given ``dict_``, 

2866 we indicate that this value is to be persisted to the database. 

2867 This supersedes the use of ``SOME_CONSTANT`` in the default generator 

2868 for the :class:`_schema.Column`. The ``active_column_defaults.py`` 

2869 example given at :ref:`examples_instrumentation` illustrates using 

2870 the same approach for a changing default, e.g. a timestamp 

2871 generator. In this particular example, it is not strictly 

2872 necessary to do this since ``SOME_CONSTANT`` would be part of the 

2873 INSERT statement in either case. 

2874 

2875 * By establishing the ``retval=True`` flag, the value we return 

2876 from the function will be returned by the attribute getter. 

2877 Without this flag, the event is assumed to be a passive observer 

2878 and the return value of our function is ignored. 

2879 

2880 * The ``propagate=True`` flag is significant if the mapped class 

2881 includes inheriting subclasses, which would also make use of this 

2882 event listener. Without this flag, an inheriting subclass will 

2883 not use our event handler. 

2884 

2885 In the above example, the attribute set event 

2886 :meth:`.AttributeEvents.set` as well as the related validation feature 

2887 provided by :obj:`_orm.validates` is **not** invoked when we apply our 

2888 value to the given ``dict_``. To have these events to invoke in 

2889 response to our newly generated value, apply the value to the given 

2890 object as a normal attribute set operation:: 

2891 

2892 SOME_CONSTANT = 3.1415926 

2893 

2894 @event.listens_for( 

2895 MyClass.some_attribute, "init_scalar", 

2896 retval=True, propagate=True) 

2897 def _init_some_attribute(target, dict_, value): 

2898 # will also fire off attribute set events 

2899 target.some_attribute = SOME_CONSTANT 

2900 return SOME_CONSTANT 

2901 

2902 When multiple listeners are set up, the generation of the value 

2903 is "chained" from one listener to the next by passing the value 

2904 returned by the previous listener that specifies ``retval=True`` 

2905 as the ``value`` argument of the next listener. 

2906 

2907 :param target: the object instance receiving the event. 

2908 If the listener is registered with ``raw=True``, this will 

2909 be the :class:`.InstanceState` object. 

2910 :param value: the value that is to be returned before this event 

2911 listener were invoked. This value begins as the value ``None``, 

2912 however will be the return value of the previous event handler 

2913 function if multiple listeners are present. 

2914 :param dict\_: the attribute dictionary of this mapped object. 

2915 This is normally the ``__dict__`` of the object, but in all cases 

2916 represents the destination that the attribute system uses to get 

2917 at the actual value of this attribute. Placing the value in this 

2918 dictionary has the effect that the value will be used in the 

2919 INSERT statement generated by the unit of work. 

2920 

2921 

2922 .. seealso:: 

2923 

2924 :meth:`.AttributeEvents.init_collection` - collection version 

2925 of this event 

2926 

2927 :class:`.AttributeEvents` - background on listener options such 

2928 as propagation to subclasses. 

2929 

2930 :ref:`examples_instrumentation` - see the 

2931 ``active_column_defaults.py`` example. 

2932 

2933 """ 

2934 

2935 def init_collection( 

2936 self, 

2937 target: _O, 

2938 collection: Type[Collection[Any]], 

2939 collection_adapter: CollectionAdapter, 

2940 ) -> None: 

2941 """Receive a 'collection init' event. 

2942 

2943 This event is triggered for a collection-based attribute, when 

2944 the initial "empty collection" is first generated for a blank 

2945 attribute, as well as for when the collection is replaced with 

2946 a new one, such as via a set event. 

2947 

2948 E.g., given that ``User.addresses`` is a relationship-based 

2949 collection, the event is triggered here:: 

2950 

2951 u1 = User() 

2952 u1.addresses.append(a1) # <- new collection 

2953 

2954 and also during replace operations:: 

2955 

2956 u1.addresses = [a2, a3] # <- new collection 

2957 

2958 :param target: the object instance receiving the event. 

2959 If the listener is registered with ``raw=True``, this will 

2960 be the :class:`.InstanceState` object. 

2961 :param collection: the new collection. This will always be generated 

2962 from what was specified as 

2963 :paramref:`_orm.relationship.collection_class`, and will always 

2964 be empty. 

2965 :param collection_adapter: the :class:`.CollectionAdapter` that will 

2966 mediate internal access to the collection. 

2967 

2968 .. seealso:: 

2969 

2970 :class:`.AttributeEvents` - background on listener options such 

2971 as propagation to subclasses. 

2972 

2973 :meth:`.AttributeEvents.init_scalar` - "scalar" version of this 

2974 event. 

2975 

2976 """ 

2977 

2978 def dispose_collection( 

2979 self, 

2980 target: _O, 

2981 collection: Collection[Any], 

2982 collection_adapter: CollectionAdapter, 

2983 ) -> None: 

2984 """Receive a 'collection dispose' event. 

2985 

2986 This event is triggered for a collection-based attribute when 

2987 a collection is replaced, that is:: 

2988 

2989 u1.addresses.append(a1) 

2990 

2991 u1.addresses = [a2, a3] # <- old collection is disposed 

2992 

2993 The old collection received will contain its previous contents. 

2994 

2995 .. versionchanged:: 1.2 The collection passed to 

2996 :meth:`.AttributeEvents.dispose_collection` will now have its 

2997 contents before the dispose intact; previously, the collection 

2998 would be empty. 

2999 

3000 .. seealso:: 

3001 

3002 :class:`.AttributeEvents` - background on listener options such 

3003 as propagation to subclasses. 

3004 

3005 """ 

3006 

3007 def modified(self, target: _O, initiator: Event) -> None: 

3008 """Receive a 'modified' event. 

3009 

3010 This event is triggered when the :func:`.attributes.flag_modified` 

3011 function is used to trigger a modify event on an attribute without 

3012 any specific value being set. 

3013 

3014 .. versionadded:: 1.2 

3015 

3016 :param target: the object instance receiving the event. 

3017 If the listener is registered with ``raw=True``, this will 

3018 be the :class:`.InstanceState` object. 

3019 

3020 :param initiator: An instance of :class:`.attributes.Event` 

3021 representing the initiation of the event. 

3022 

3023 .. seealso:: 

3024 

3025 :class:`.AttributeEvents` - background on listener options such 

3026 as propagation to subclasses. 

3027 

3028 """ 

3029 

3030 

3031class QueryEvents(event.Events[Query[Any]]): 

3032 """Represent events within the construction of a :class:`_query.Query` 

3033 object. 

3034 

3035 .. legacy:: The :class:`_orm.QueryEvents` event methods are legacy 

3036 as of SQLAlchemy 2.0, and only apply to direct use of the 

3037 :class:`_orm.Query` object. They are not used for :term:`2.0 style` 

3038 statements. For events to intercept and modify 2.0 style ORM use, 

3039 use the :meth:`_orm.SessionEvents.do_orm_execute` hook. 

3040 

3041 

3042 The :class:`_orm.QueryEvents` hooks are now superseded by the 

3043 :meth:`_orm.SessionEvents.do_orm_execute` event hook. 

3044 

3045 """ 

3046 

3047 _target_class_doc = "SomeQuery" 

3048 _dispatch_target = Query 

3049 

3050 def before_compile(self, query: Query[Any]) -> None: 

3051 """Receive the :class:`_query.Query` 

3052 object before it is composed into a 

3053 core :class:`_expression.Select` object. 

3054 

3055 .. deprecated:: 1.4 The :meth:`_orm.QueryEvents.before_compile` event 

3056 is superseded by the much more capable 

3057 :meth:`_orm.SessionEvents.do_orm_execute` hook. In version 1.4, 

3058 the :meth:`_orm.QueryEvents.before_compile` event is **no longer 

3059 used** for ORM-level attribute loads, such as loads of deferred 

3060 or expired attributes as well as relationship loaders. See the 

3061 new examples in :ref:`examples_session_orm_events` which 

3062 illustrate new ways of intercepting and modifying ORM queries 

3063 for the most common purpose of adding arbitrary filter criteria. 

3064 

3065 

3066 This event is intended to allow changes to the query given:: 

3067 

3068 @event.listens_for(Query, "before_compile", retval=True) 

3069 def no_deleted(query): 

3070 for desc in query.column_descriptions: 

3071 if desc['type'] is User: 

3072 entity = desc['entity'] 

3073 query = query.filter(entity.deleted == False) 

3074 return query 

3075 

3076 The event should normally be listened with the ``retval=True`` 

3077 parameter set, so that the modified query may be returned. 

3078 

3079 The :meth:`.QueryEvents.before_compile` event by default 

3080 will disallow "baked" queries from caching a query, if the event 

3081 hook returns a new :class:`_query.Query` object. 

3082 This affects both direct 

3083 use of the baked query extension as well as its operation within 

3084 lazy loaders and eager loaders for relationships. In order to 

3085 re-establish the query being cached, apply the event adding the 

3086 ``bake_ok`` flag:: 

3087 

3088 @event.listens_for( 

3089 Query, "before_compile", retval=True, bake_ok=True) 

3090 def my_event(query): 

3091 for desc in query.column_descriptions: 

3092 if desc['type'] is User: 

3093 entity = desc['entity'] 

3094 query = query.filter(entity.deleted == False) 

3095 return query 

3096 

3097 When ``bake_ok`` is set to True, the event hook will only be invoked 

3098 once, and not called for subsequent invocations of a particular query 

3099 that is being cached. 

3100 

3101 .. versionadded:: 1.3.11 - added the "bake_ok" flag to the 

3102 :meth:`.QueryEvents.before_compile` event and disallowed caching via 

3103 the "baked" extension from occurring for event handlers that 

3104 return a new :class:`_query.Query` object if this flag is not set. 

3105 

3106 .. seealso:: 

3107 

3108 :meth:`.QueryEvents.before_compile_update` 

3109 

3110 :meth:`.QueryEvents.before_compile_delete` 

3111 

3112 :ref:`baked_with_before_compile` 

3113 

3114 """ 

3115 

3116 def before_compile_update( 

3117 self, query: Query[Any], update_context: BulkUpdate 

3118 ) -> None: 

3119 """Allow modifications to the :class:`_query.Query` object within 

3120 :meth:`_query.Query.update`. 

3121 

3122 .. deprecated:: 1.4 The :meth:`_orm.QueryEvents.before_compile_update` 

3123 event is superseded by the much more capable 

3124 :meth:`_orm.SessionEvents.do_orm_execute` hook. 

3125 

3126 Like the :meth:`.QueryEvents.before_compile` event, if the event 

3127 is to be used to alter the :class:`_query.Query` object, it should 

3128 be configured with ``retval=True``, and the modified 

3129 :class:`_query.Query` object returned, as in :: 

3130 

3131 @event.listens_for(Query, "before_compile_update", retval=True) 

3132 def no_deleted(query, update_context): 

3133 for desc in query.column_descriptions: 

3134 if desc['type'] is User: 

3135 entity = desc['entity'] 

3136 query = query.filter(entity.deleted == False) 

3137 

3138 update_context.values['timestamp'] = datetime.utcnow() 

3139 return query 

3140 

3141 The ``.values`` dictionary of the "update context" object can also 

3142 be modified in place as illustrated above. 

3143 

3144 :param query: a :class:`_query.Query` instance; this is also 

3145 the ``.query`` attribute of the given "update context" 

3146 object. 

3147 

3148 :param update_context: an "update context" object which is 

3149 the same kind of object as described in 

3150 :paramref:`.QueryEvents.after_bulk_update.update_context`. 

3151 The object has a ``.values`` attribute in an UPDATE context which is 

3152 the dictionary of parameters passed to :meth:`_query.Query.update`. 

3153 This 

3154 dictionary can be modified to alter the VALUES clause of the 

3155 resulting UPDATE statement. 

3156 

3157 .. versionadded:: 1.2.17 

3158 

3159 .. seealso:: 

3160 

3161 :meth:`.QueryEvents.before_compile` 

3162 

3163 :meth:`.QueryEvents.before_compile_delete` 

3164 

3165 

3166 """ 

3167 

3168 def before_compile_delete( 

3169 self, query: Query[Any], delete_context: BulkDelete 

3170 ) -> None: 

3171 """Allow modifications to the :class:`_query.Query` object within 

3172 :meth:`_query.Query.delete`. 

3173 

3174 .. deprecated:: 1.4 The :meth:`_orm.QueryEvents.before_compile_delete` 

3175 event is superseded by the much more capable 

3176 :meth:`_orm.SessionEvents.do_orm_execute` hook. 

3177 

3178 Like the :meth:`.QueryEvents.before_compile` event, this event 

3179 should be configured with ``retval=True``, and the modified 

3180 :class:`_query.Query` object returned, as in :: 

3181 

3182 @event.listens_for(Query, "before_compile_delete", retval=True) 

3183 def no_deleted(query, delete_context): 

3184 for desc in query.column_descriptions: 

3185 if desc['type'] is User: 

3186 entity = desc['entity'] 

3187 query = query.filter(entity.deleted == False) 

3188 return query 

3189 

3190 :param query: a :class:`_query.Query` instance; this is also 

3191 the ``.query`` attribute of the given "delete context" 

3192 object. 

3193 

3194 :param delete_context: a "delete context" object which is 

3195 the same kind of object as described in 

3196 :paramref:`.QueryEvents.after_bulk_delete.delete_context`. 

3197 

3198 .. versionadded:: 1.2.17 

3199 

3200 .. seealso:: 

3201 

3202 :meth:`.QueryEvents.before_compile` 

3203 

3204 :meth:`.QueryEvents.before_compile_update` 

3205 

3206 

3207 """ 

3208 

3209 @classmethod 

3210 def _listen( 

3211 cls, 

3212 event_key: _EventKey[_ET], 

3213 retval: bool = False, 

3214 bake_ok: bool = False, 

3215 **kw: Any, 

3216 ) -> None: 

3217 fn = event_key._listen_fn 

3218 

3219 if not retval: 

3220 

3221 def wrap(*arg: Any, **kw: Any) -> Any: 

3222 if not retval: 

3223 query = arg[0] 

3224 fn(*arg, **kw) 

3225 return query 

3226 else: 

3227 return fn(*arg, **kw) 

3228 

3229 event_key = event_key.with_wrapper(wrap) 

3230 else: 

3231 # don't assume we can apply an attribute to the callable 

3232 def wrap(*arg: Any, **kw: Any) -> Any: 

3233 return fn(*arg, **kw) 

3234 

3235 event_key = event_key.with_wrapper(wrap) 

3236 

3237 wrap._bake_ok = bake_ok # type: ignore [attr-defined] 

3238 

3239 event_key.base_listen(**kw)