1# orm/session.py
2# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
3# <see AUTHORS file>
4#
5# This module is part of SQLAlchemy and is released under
6# the MIT License: https://www.opensource.org/licenses/mit-license.php
7
8"""Provides the Session class and related utilities."""
9
10from __future__ import annotations
11
12import contextlib
13from enum import Enum
14import itertools
15import sys
16import typing
17from typing import Any
18from typing import Callable
19from typing import cast
20from typing import Dict
21from typing import FrozenSet
22from typing import Generic
23from typing import Iterable
24from typing import Iterator
25from typing import List
26from typing import Literal
27from typing import Mapping
28from typing import NoReturn
29from typing import Optional
30from typing import overload
31from typing import Protocol
32from typing import Sequence
33from typing import Set
34from typing import Tuple
35from typing import Type
36from typing import TYPE_CHECKING
37from typing import TypeVar
38from typing import Union
39import weakref
40
41from . import attributes
42from . import bulk_persistence
43from . import context
44from . import descriptor_props
45from . import exc
46from . import identity
47from . import loading
48from . import query
49from . import state as statelib
50from ._typing import _O
51from ._typing import insp_is_mapper
52from ._typing import is_composite_class
53from ._typing import is_orm_option
54from ._typing import is_user_defined_option
55from .base import _class_to_mapper
56from .base import _none_set
57from .base import _state_mapper
58from .base import instance_str
59from .base import LoaderCallableStatus
60from .base import object_mapper
61from .base import object_state
62from .base import PassiveFlag
63from .base import state_str
64from .context import _ORMCompileState
65from .context import FromStatement
66from .identity import IdentityMap
67from .query import Query
68from .state import InstanceState
69from .state_changes import _StateChange
70from .state_changes import _StateChangeState
71from .state_changes import _StateChangeStates
72from .unitofwork import UOWTransaction
73from .. import engine
74from .. import exc as sa_exc
75from .. import sql
76from .. import util
77from ..engine import Connection
78from ..engine import Engine
79from ..engine.util import TransactionalContext
80from ..event import dispatcher
81from ..event import EventTarget
82from ..inspection import inspect
83from ..inspection import Inspectable
84from ..sql import coercions
85from ..sql import dml
86from ..sql import roles
87from ..sql import Select
88from ..sql import TableClause
89from ..sql import visitors
90from ..sql.base import _NoArg
91from ..sql.base import CompileState
92from ..sql.schema import Table
93from ..sql.selectable import ForUpdateArg
94from ..util import deprecated_params
95from ..util import IdentitySet
96from ..util.typing import Never
97from ..util.typing import TupleAny
98from ..util.typing import TypeVarTuple
99from ..util.typing import Unpack
100
101if typing.TYPE_CHECKING:
102 from ._typing import _EntityType
103 from ._typing import _IdentityKeyType
104 from ._typing import _InstanceDict
105 from ._typing import OrmExecuteOptionsParameter
106 from .interfaces import ORMOption
107 from .interfaces import UserDefinedOption
108 from .mapper import Mapper
109 from .path_registry import PathRegistry
110 from .query import RowReturningQuery
111 from ..engine import Result
112 from ..engine import Row
113 from ..engine import RowMapping
114 from ..engine.base import Transaction
115 from ..engine.base import TwoPhaseTransaction
116 from ..engine.interfaces import _CoreAnyExecuteParams
117 from ..engine.interfaces import _CoreSingleExecuteParams
118 from ..engine.interfaces import _ExecuteOptions
119 from ..engine.interfaces import CoreExecuteOptionsParameter
120 from ..engine.result import ScalarResult
121 from ..event import _InstanceLevelDispatch
122 from ..sql._typing import _ColumnsClauseArgument
123 from ..sql._typing import _InfoType
124 from ..sql._typing import _T0
125 from ..sql._typing import _T1
126 from ..sql._typing import _T2
127 from ..sql._typing import _T3
128 from ..sql._typing import _T4
129 from ..sql._typing import _T5
130 from ..sql._typing import _T6
131 from ..sql._typing import _T7
132 from ..sql._typing import _TypedColumnClauseArgument as _TCCA
133 from ..sql.base import Executable
134 from ..sql.base import ExecutableOption
135 from ..sql.elements import ClauseElement
136 from ..sql.roles import TypedColumnsClauseRole
137 from ..sql.selectable import ForUpdateParameter
138 from ..sql.selectable import TypedReturnsRows
139
140_T = TypeVar("_T", bound=Any)
141_Ts = TypeVarTuple("_Ts")
142
143__all__ = [
144 "Session",
145 "SessionTransaction",
146 "sessionmaker",
147 "ORMExecuteState",
148 "close_all_sessions",
149 "make_transient",
150 "make_transient_to_detached",
151 "object_session",
152]
153
154_sessions: weakref.WeakValueDictionary[int, Session] = (
155 weakref.WeakValueDictionary()
156)
157"""Weak-referencing dictionary of :class:`.Session` objects.
158"""
159
160statelib._sessions = _sessions
161
162_PKIdentityArgument = Union[Any, Tuple[Any, ...]]
163
164_BindArguments = Dict[str, Any]
165
166_EntityBindKey = Union[Type[_O], "Mapper[_O]"]
167_SessionBindKey = Union[Type[Any], "Mapper[Any]", "TableClause", str]
168_SessionBind = Union["Engine", "Connection"]
169
170JoinTransactionMode = Literal[
171 "conditional_savepoint",
172 "rollback_only",
173 "control_fully",
174 "create_savepoint",
175]
176
177
178class _ConnectionCallableProto(Protocol):
179 """a callable that returns a :class:`.Connection` given an instance.
180
181 This callable, when present on a :class:`.Session`, is called only from the
182 ORM's persistence mechanism (i.e. the unit of work flush process) to allow
183 for connection-per-instance schemes (i.e. horizontal sharding) to be used
184 as persistence time.
185
186 This callable is not present on a plain :class:`.Session`, however
187 is established when using the horizontal sharding extension.
188
189 """
190
191 def __call__(
192 self,
193 mapper: Optional[Mapper[Any]] = None,
194 instance: Optional[object] = None,
195 **kw: Any,
196 ) -> Connection: ...
197
198
199def _state_session(state: InstanceState[Any]) -> Optional[Session]:
200 """Given an :class:`.InstanceState`, return the :class:`.Session`
201 associated, if any.
202 """
203 return state.session
204
205
206class _SessionClassMethods:
207 """Class-level methods for :class:`.Session`, :class:`.sessionmaker`."""
208
209 @classmethod
210 @util.preload_module("sqlalchemy.orm.util")
211 def identity_key(
212 cls,
213 class_: Optional[Type[Any]] = None,
214 ident: Union[Any, Tuple[Any, ...]] = None,
215 *,
216 instance: Optional[Any] = None,
217 row: Optional[Union[Row[Unpack[TupleAny]], RowMapping]] = None,
218 identity_token: Optional[Any] = None,
219 ) -> _IdentityKeyType[Any]:
220 """Return an identity key.
221
222 This is an alias of :func:`.util.identity_key`.
223
224 """
225 return util.preloaded.orm_util.identity_key(
226 class_,
227 ident,
228 instance=instance,
229 row=row,
230 identity_token=identity_token,
231 )
232
233 @classmethod
234 def object_session(cls, instance: object) -> Optional[Session]:
235 """Return the :class:`.Session` to which an object belongs.
236
237 This is an alias of :func:`.object_session`.
238
239 """
240
241 return object_session(instance)
242
243
244class SessionTransactionState(_StateChangeState):
245 ACTIVE = 1
246 PREPARED = 2
247 COMMITTED = 3
248 DEACTIVE = 4
249 CLOSED = 5
250 PROVISIONING_CONNECTION = 6
251
252
253# backwards compatibility
254ACTIVE, PREPARED, COMMITTED, DEACTIVE, CLOSED, PROVISIONING_CONNECTION = tuple(
255 SessionTransactionState
256)
257
258
259class ORMExecuteState(util.MemoizedSlots):
260 """Represents a call to the :meth:`_orm.Session.execute` method, as passed
261 to the :meth:`.SessionEvents.do_orm_execute` event hook.
262
263 .. versionadded:: 1.4
264
265 .. seealso::
266
267 :ref:`session_execute_events` - top level documentation on how
268 to use :meth:`_orm.SessionEvents.do_orm_execute`
269
270 """
271
272 __slots__ = (
273 "session",
274 "statement",
275 "parameters",
276 "execution_options",
277 "local_execution_options",
278 "bind_arguments",
279 "identity_token",
280 "_compile_state_cls",
281 "_starting_event_idx",
282 "_events_todo",
283 "_update_execution_options",
284 )
285
286 session: Session
287 """The :class:`_orm.Session` in use."""
288
289 statement: Executable
290 """The SQL statement being invoked.
291
292 For an ORM selection as would
293 be retrieved from :class:`_orm.Query`, this is an instance of
294 :class:`_sql.select` that was generated from the ORM query.
295 """
296
297 parameters: Optional[_CoreAnyExecuteParams]
298 """Optional mapping or list of mappings of parameters that was passed to
299 :meth:`_orm.Session.execute`.
300
301 May be mutated or re-assigned in place, which will take effect as the
302 effective parameters passed to the method.
303
304 .. versionchanged:: 2.1 :attr:`.ORMExecuteState.parameters` may now be
305 mutated or replaced.
306
307 """
308
309 execution_options: _ExecuteOptions
310 """The complete dictionary of current execution options.
311
312 This is a merge of the statement level options with the
313 locally passed execution options.
314
315 .. seealso::
316
317 :attr:`_orm.ORMExecuteState.local_execution_options`
318
319 :meth:`_sql.Executable.execution_options`
320
321 :ref:`orm_queryguide_execution_options`
322
323 """
324
325 local_execution_options: _ExecuteOptions
326 """Dictionary view of the execution options passed to the
327 :meth:`.Session.execute` method.
328
329 This does not include options that may be associated with the statement
330 being invoked.
331
332 .. seealso::
333
334 :attr:`_orm.ORMExecuteState.execution_options`
335
336 """
337
338 bind_arguments: _BindArguments
339 """The dictionary passed as the
340 :paramref:`_orm.Session.execute.bind_arguments` dictionary.
341
342 This dictionary may be used by extensions to :class:`_orm.Session` to pass
343 arguments that will assist in determining amongst a set of database
344 connections which one should be used to invoke this statement.
345
346 """
347
348 _compile_state_cls: Optional[Type[_ORMCompileState]]
349 _starting_event_idx: int
350 _events_todo: List[Any]
351 _update_execution_options: _ExecuteOptions
352
353 def __init__(
354 self,
355 session: Session,
356 statement: Executable,
357 parameters: Optional[_CoreAnyExecuteParams],
358 execution_options: _ExecuteOptions,
359 bind_arguments: _BindArguments,
360 compile_state_cls: Optional[Type[_ORMCompileState]],
361 events_todo: List[_InstanceLevelDispatch[Session]],
362 ):
363 """Construct a new :class:`_orm.ORMExecuteState`.
364
365 this object is constructed internally.
366
367 """
368 self.session = session
369 self.statement = statement
370 self.parameters = parameters
371 self.local_execution_options = execution_options
372 self.execution_options = statement._execution_options.union(
373 execution_options
374 )
375 self.bind_arguments = bind_arguments
376 self._compile_state_cls = compile_state_cls
377 self._events_todo = list(events_todo)
378 self._update_execution_options = util.EMPTY_DICT
379
380 def _remaining_events(self) -> List[_InstanceLevelDispatch[Session]]:
381 return self._events_todo[self._starting_event_idx + 1 :]
382
383 def invoke_statement(
384 self,
385 statement: Optional[Executable] = None,
386 params: Optional[_CoreAnyExecuteParams] = None,
387 execution_options: Optional[OrmExecuteOptionsParameter] = None,
388 bind_arguments: Optional[_BindArguments] = None,
389 ) -> Result[Unpack[TupleAny]]:
390 """Execute the statement represented by this
391 :class:`.ORMExecuteState`, without re-invoking events that have
392 already proceeded.
393
394 This method essentially performs a re-entrant execution of the current
395 statement for which the :meth:`.SessionEvents.do_orm_execute` event is
396 being currently invoked. The use case for this is for event handlers
397 that want to override how the ultimate
398 :class:`_engine.Result` object is returned, such as for schemes that
399 retrieve results from an offline cache or which concatenate results
400 from multiple executions.
401
402 When the :class:`_engine.Result` object is returned by the actual
403 handler function within :meth:`_orm.SessionEvents.do_orm_execute` and
404 is propagated to the calling
405 :meth:`_orm.Session.execute` method, the remainder of the
406 :meth:`_orm.Session.execute` method is preempted and the
407 :class:`_engine.Result` object is returned to the caller of
408 :meth:`_orm.Session.execute` immediately.
409
410 :param statement: optional statement to be invoked, in place of the
411 statement currently represented by :attr:`.ORMExecuteState.statement`.
412
413 :param params: optional dictionary of parameters or list of parameters
414 which will be merged into the existing
415 :attr:`.ORMExecuteState.parameters` of this :class:`.ORMExecuteState`.
416
417 .. versionchanged:: 2.0 a list of parameter dictionaries is accepted
418 for executemany executions.
419
420 :param execution_options: optional dictionary of execution options
421 will be merged into the existing
422 :attr:`.ORMExecuteState.execution_options` of this
423 :class:`.ORMExecuteState`.
424
425 :param bind_arguments: optional dictionary of bind_arguments
426 which will be merged amongst the current
427 :attr:`.ORMExecuteState.bind_arguments`
428 of this :class:`.ORMExecuteState`.
429
430 :return: a :class:`_engine.Result` object with ORM-level results.
431
432 .. seealso::
433
434 :ref:`do_orm_execute_re_executing` - background and examples on the
435 appropriate usage of :meth:`_orm.ORMExecuteState.invoke_statement`.
436
437
438 """
439
440 if statement is None:
441 statement = self.statement
442
443 _bind_arguments = dict(self.bind_arguments)
444 if bind_arguments:
445 _bind_arguments.update(bind_arguments)
446 _bind_arguments["_sa_skip_events"] = True
447
448 _params: Optional[_CoreAnyExecuteParams]
449 if params:
450 if self.is_executemany:
451 _params = []
452 exec_many_parameters = cast(
453 "List[Dict[str, Any]]", self.parameters
454 )
455 for _existing_params, _new_params in itertools.zip_longest(
456 exec_many_parameters,
457 cast("List[Dict[str, Any]]", params),
458 ):
459 if _existing_params is None or _new_params is None:
460 raise sa_exc.InvalidRequestError(
461 f"Can't apply executemany parameters to "
462 f"statement; number of parameter sets passed to "
463 f"Session.execute() ({len(exec_many_parameters)}) "
464 f"does not match number of parameter sets given "
465 f"to ORMExecuteState.invoke_statement() "
466 f"({len(params)})"
467 )
468 _existing_params = dict(_existing_params)
469 _existing_params.update(_new_params)
470 _params.append(_existing_params)
471 else:
472 _params = dict(cast("Dict[str, Any]", self.parameters))
473 _params.update(cast("Dict[str, Any]", params))
474 else:
475 _params = self.parameters
476
477 _execution_options = self.local_execution_options
478 if execution_options:
479 _execution_options = _execution_options.union(execution_options)
480
481 return self.session._execute_internal(
482 statement,
483 _params,
484 execution_options=_execution_options,
485 bind_arguments=_bind_arguments,
486 _parent_execute_state=self,
487 )
488
489 @property
490 def bind_mapper(self) -> Optional[Mapper[Any]]:
491 """Return the :class:`_orm.Mapper` that is the primary "bind" mapper.
492
493 For an :class:`_orm.ORMExecuteState` object invoking an ORM
494 statement, that is, the :attr:`_orm.ORMExecuteState.is_orm_statement`
495 attribute is ``True``, this attribute will return the
496 :class:`_orm.Mapper` that is considered to be the "primary" mapper
497 of the statement. The term "bind mapper" refers to the fact that
498 a :class:`_orm.Session` object may be "bound" to multiple
499 :class:`_engine.Engine` objects keyed to mapped classes, and the
500 "bind mapper" determines which of those :class:`_engine.Engine` objects
501 would be selected.
502
503 For a statement that is invoked against a single mapped class,
504 :attr:`_orm.ORMExecuteState.bind_mapper` is intended to be a reliable
505 way of getting this mapper.
506
507 .. versionadded:: 1.4.0b2
508
509 .. seealso::
510
511 :attr:`_orm.ORMExecuteState.all_mappers`
512
513
514 """
515 mp: Optional[Mapper[Any]] = self.bind_arguments.get("mapper", None)
516 return mp
517
518 @property
519 def all_mappers(self) -> Sequence[Mapper[Any]]:
520 """Return a sequence of all :class:`_orm.Mapper` objects that are
521 involved at the top level of this statement.
522
523 By "top level" we mean those :class:`_orm.Mapper` objects that would
524 be represented in the result set rows for a :func:`_sql.select`
525 query, or for a :func:`_dml.update` or :func:`_dml.delete` query,
526 the mapper that is the main subject of the UPDATE or DELETE.
527
528 .. versionadded:: 1.4.0b2
529
530 .. seealso::
531
532 :attr:`_orm.ORMExecuteState.bind_mapper`
533
534
535
536 """
537 if not self.is_orm_statement:
538 return []
539 elif isinstance(self.statement, (Select, FromStatement)):
540 result = []
541 seen = set()
542 for d in self.statement.column_descriptions:
543 ent = d["entity"]
544 if ent:
545 insp = inspect(ent, raiseerr=False)
546 if insp and insp.mapper and insp.mapper not in seen:
547 seen.add(insp.mapper)
548 result.append(insp.mapper)
549 return result
550 elif self.statement.is_dml and self.bind_mapper:
551 return [self.bind_mapper]
552 else:
553 return []
554
555 @property
556 def is_orm_statement(self) -> bool:
557 """return True if the operation is an ORM statement.
558
559 This indicates that the select(), insert(), update(), or delete()
560 being invoked contains ORM entities as subjects. For a statement
561 that does not have ORM entities and instead refers only to
562 :class:`.Table` metadata, it is invoked as a Core SQL statement
563 and no ORM-level automation takes place.
564
565 """
566 return self._compile_state_cls is not None
567
568 @property
569 def is_executemany(self) -> bool:
570 """return True if the parameters are a multi-element list of
571 dictionaries with more than one dictionary.
572
573 .. versionadded:: 2.0
574
575 """
576 return isinstance(self.parameters, list)
577
578 @property
579 def is_select(self) -> bool:
580 """return True if this is a SELECT operation.
581
582 .. versionchanged:: 2.0.30 - the attribute is also True for a
583 :meth:`_sql.Select.from_statement` construct that is itself against
584 a :class:`_sql.Select` construct, such as
585 ``select(Entity).from_statement(select(..))``
586
587 """
588 return self.statement.is_select
589
590 @property
591 def is_from_statement(self) -> bool:
592 """return True if this operation is a
593 :meth:`_sql.Select.from_statement` operation.
594
595 This is independent from :attr:`_orm.ORMExecuteState.is_select`, as a
596 ``select().from_statement()`` construct can be used with
597 INSERT/UPDATE/DELETE RETURNING types of statements as well.
598 :attr:`_orm.ORMExecuteState.is_select` will only be set if the
599 :meth:`_sql.Select.from_statement` is itself against a
600 :class:`_sql.Select` construct.
601
602 .. versionadded:: 2.0.30
603
604 """
605 return self.statement.is_from_statement
606
607 @property
608 def is_insert(self) -> bool:
609 """return True if this is an INSERT operation.
610
611 .. versionchanged:: 2.0.30 - the attribute is also True for a
612 :meth:`_sql.Select.from_statement` construct that is itself against
613 a :class:`_sql.Insert` construct, such as
614 ``select(Entity).from_statement(insert(..))``
615
616 """
617 return self.statement.is_dml and self.statement.is_insert
618
619 @property
620 def is_update(self) -> bool:
621 """return True if this is an UPDATE operation.
622
623 .. versionchanged:: 2.0.30 - the attribute is also True for a
624 :meth:`_sql.Select.from_statement` construct that is itself against
625 a :class:`_sql.Update` construct, such as
626 ``select(Entity).from_statement(update(..))``
627
628 """
629 return self.statement.is_dml and self.statement.is_update
630
631 @property
632 def is_delete(self) -> bool:
633 """return True if this is a DELETE operation.
634
635 .. versionchanged:: 2.0.30 - the attribute is also True for a
636 :meth:`_sql.Select.from_statement` construct that is itself against
637 a :class:`_sql.Delete` construct, such as
638 ``select(Entity).from_statement(delete(..))``
639
640 """
641 return self.statement.is_dml and self.statement.is_delete
642
643 @property
644 def _is_crud(self) -> bool:
645 return isinstance(self.statement, (dml.Update, dml.Delete))
646
647 def update_execution_options(self, **opts: Any) -> None:
648 """Update the local execution options with new values."""
649 self.local_execution_options = self.local_execution_options.union(opts)
650 self._update_execution_options = self._update_execution_options.union(
651 opts
652 )
653
654 def _orm_compile_options(
655 self,
656 ) -> Optional[
657 Union[
658 context._ORMCompileState.default_compile_options,
659 Type[context._ORMCompileState.default_compile_options],
660 ]
661 ]:
662 if not self.is_select:
663 return None
664 try:
665 opts = self.statement._compile_options
666 except AttributeError:
667 return None
668
669 if opts is not None and opts.isinstance(
670 context._ORMCompileState.default_compile_options
671 ):
672 return opts # type: ignore[return-value]
673 else:
674 return None
675
676 @property
677 def lazy_loaded_from(self) -> Optional[InstanceState[Any]]:
678 """An :class:`.InstanceState` that is using this statement execution
679 for a lazy load operation.
680
681 The primary rationale for this attribute is to support the horizontal
682 sharding extension, where it is available within specific query
683 execution time hooks created by this extension. To that end, the
684 attribute is only intended to be meaningful at **query execution
685 time**, and importantly not any time prior to that, including query
686 compilation time.
687
688 """
689 return self.load_options._lazy_loaded_from
690
691 @property
692 def loader_strategy_path(self) -> Optional[PathRegistry]:
693 """Return the :class:`.PathRegistry` for the current load path.
694
695 This object represents the "path" in a query along relationships
696 when a particular object or collection is being loaded.
697
698 """
699 opts = self._orm_compile_options()
700 if opts is not None:
701 return opts._current_path
702 else:
703 return None
704
705 @property
706 def is_column_load(self) -> bool:
707 """Return True if the operation is refreshing column-oriented
708 attributes on an existing ORM object.
709
710 This occurs during operations such as :meth:`_orm.Session.refresh`,
711 as well as when an attribute deferred by :func:`_orm.defer` is
712 being loaded, or an attribute that was expired either directly
713 by :meth:`_orm.Session.expire` or via a commit operation is being
714 loaded.
715
716 Handlers will very likely not want to add any options to queries
717 when such an operation is occurring as the query should be a straight
718 primary key fetch which should not have any additional WHERE criteria,
719 and loader options travelling with the instance
720 will have already been added to the query.
721
722 .. versionadded:: 1.4.0b2
723
724 .. seealso::
725
726 :attr:`_orm.ORMExecuteState.is_relationship_load`
727
728 """
729 opts = self._orm_compile_options()
730 return opts is not None and opts._for_refresh_state
731
732 @property
733 def is_relationship_load(self) -> bool:
734 """Return True if this load is loading objects on behalf of a
735 relationship.
736
737 This means, the loader in effect is either a LazyLoader,
738 SelectInLoader, SubqueryLoader, or similar, and the entire
739 SELECT statement being emitted is on behalf of a relationship
740 load.
741
742 Handlers will very likely not want to add any options to queries
743 when such an operation is occurring, as loader options are already
744 capable of being propagated to relationship loaders and should
745 be already present.
746
747 .. seealso::
748
749 :attr:`_orm.ORMExecuteState.is_column_load`
750
751 """
752 opts = self._orm_compile_options()
753 if opts is None:
754 return False
755 path = self.loader_strategy_path
756 return path is not None and not path.is_root
757
758 @property
759 def load_options(
760 self,
761 ) -> Union[
762 context.QueryContext.default_load_options,
763 Type[context.QueryContext.default_load_options],
764 ]:
765 """Return the load_options that will be used for this execution."""
766
767 if not self.is_select:
768 raise sa_exc.InvalidRequestError(
769 "This ORM execution is not against a SELECT statement "
770 "so there are no load options."
771 )
772
773 lo: Union[
774 context.QueryContext.default_load_options,
775 Type[context.QueryContext.default_load_options],
776 ] = self.execution_options.get(
777 "_sa_orm_load_options", context.QueryContext.default_load_options
778 )
779 return lo
780
781 @property
782 def update_delete_options(
783 self,
784 ) -> Union[
785 bulk_persistence._BulkUDCompileState.default_update_options,
786 Type[bulk_persistence._BulkUDCompileState.default_update_options],
787 ]:
788 """Return the update_delete_options that will be used for this
789 execution."""
790
791 if not self._is_crud:
792 raise sa_exc.InvalidRequestError(
793 "This ORM execution is not against an UPDATE or DELETE "
794 "statement so there are no update options."
795 )
796 uo: Union[
797 bulk_persistence._BulkUDCompileState.default_update_options,
798 Type[bulk_persistence._BulkUDCompileState.default_update_options],
799 ] = self.execution_options.get(
800 "_sa_orm_update_options",
801 bulk_persistence._BulkUDCompileState.default_update_options,
802 )
803 return uo
804
805 @property
806 def _non_compile_orm_options(self) -> Sequence[ORMOption]:
807 return [
808 opt
809 for opt in self.statement._with_options
810 if is_orm_option(opt) and not opt._is_compile_state
811 ]
812
813 @property
814 def user_defined_options(self) -> Sequence[UserDefinedOption]:
815 """The sequence of :class:`.UserDefinedOptions` that have been
816 associated with the statement being invoked.
817
818 .. versionchanged:: 2.1 - the returned option take into
819 consideration any options added before calling
820 :meth:`_sql.Select.with_only_columns` or
821 :meth:`_orm.Query.with_entities`.
822
823 """
824 items = [
825 self.statement,
826 *getattr(self.statement, "_memoized_select_entities", ()),
827 ]
828 return [
829 opt
830 for item in items
831 for opt in item._with_options
832 if is_user_defined_option(opt)
833 ]
834
835
836class SessionTransactionOrigin(Enum):
837 """indicates the origin of a :class:`.SessionTransaction`.
838
839 This enumeration is present on the
840 :attr:`.SessionTransaction.origin` attribute of any
841 :class:`.SessionTransaction` object.
842
843 .. versionadded:: 2.0
844
845 """
846
847 AUTOBEGIN = 0
848 """transaction were started by autobegin"""
849
850 BEGIN = 1
851 """transaction were started by calling :meth:`_orm.Session.begin`"""
852
853 BEGIN_NESTED = 2
854 """transaction were started by :meth:`_orm.Session.begin_nested`"""
855
856 SUBTRANSACTION = 3
857 """transaction is an internal "subtransaction" """
858
859
860class SessionTransaction(_StateChange, TransactionalContext):
861 """A :class:`.Session`-level transaction.
862
863 :class:`.SessionTransaction` is produced from the
864 :meth:`_orm.Session.begin`
865 and :meth:`_orm.Session.begin_nested` methods. It's largely an internal
866 object that in modern use provides a context manager for session
867 transactions.
868
869 Documentation on interacting with :class:`_orm.SessionTransaction` is
870 at: :ref:`unitofwork_transaction`.
871
872
873 .. versionchanged:: 1.4 The scoping and API methods to work with the
874 :class:`_orm.SessionTransaction` object directly have been simplified.
875
876 .. seealso::
877
878 :ref:`unitofwork_transaction`
879
880 :meth:`.Session.begin`
881
882 :meth:`.Session.begin_nested`
883
884 :meth:`.Session.rollback`
885
886 :meth:`.Session.commit`
887
888 :meth:`.Session.in_transaction`
889
890 :meth:`.Session.in_nested_transaction`
891
892 :meth:`.Session.get_transaction`
893
894 :meth:`.Session.get_nested_transaction`
895
896
897 """
898
899 _rollback_exception: Optional[BaseException] = None
900
901 _connections: Dict[
902 Union[Engine, Connection], Tuple[Connection, Transaction, bool, bool]
903 ]
904 session: Session
905 _parent: Optional[SessionTransaction]
906
907 _state: SessionTransactionState
908
909 _new: weakref.WeakKeyDictionary[InstanceState[Any], object]
910 _deleted: weakref.WeakKeyDictionary[InstanceState[Any], object]
911 _dirty: weakref.WeakKeyDictionary[InstanceState[Any], object]
912 _key_switches: weakref.WeakKeyDictionary[
913 InstanceState[Any], Tuple[Any, Any]
914 ]
915
916 origin: SessionTransactionOrigin
917 """Origin of this :class:`_orm.SessionTransaction`.
918
919 Refers to a :class:`.SessionTransactionOrigin` instance which is an
920 enumeration indicating the source event that led to constructing
921 this :class:`_orm.SessionTransaction`.
922
923 .. versionadded:: 2.0
924
925 """
926
927 nested: bool = False
928 """Indicates if this is a nested, or SAVEPOINT, transaction.
929
930 When :attr:`.SessionTransaction.nested` is True, it is expected
931 that :attr:`.SessionTransaction.parent` will be present as well,
932 linking to the enclosing :class:`.SessionTransaction`.
933
934 .. seealso::
935
936 :attr:`.SessionTransaction.origin`
937
938 """
939
940 def __init__(
941 self,
942 session: Session,
943 origin: SessionTransactionOrigin,
944 parent: Optional[SessionTransaction] = None,
945 ):
946 TransactionalContext._trans_ctx_check(session)
947
948 self.session = session
949 self._connections = {}
950 self._parent = parent
951 self.nested = nested = origin is SessionTransactionOrigin.BEGIN_NESTED
952 self.origin = origin
953
954 if session._close_state is _SessionCloseState.CLOSED:
955 raise sa_exc.InvalidRequestError(
956 "This Session has been permanently closed and is unable "
957 "to handle any more transaction requests."
958 )
959
960 if nested:
961 if not parent:
962 raise sa_exc.InvalidRequestError(
963 "Can't start a SAVEPOINT transaction when no existing "
964 "transaction is in progress"
965 )
966
967 self._previous_nested_transaction = session._nested_transaction
968 elif origin is SessionTransactionOrigin.SUBTRANSACTION:
969 assert parent is not None
970 else:
971 assert parent is None
972
973 self._state = SessionTransactionState.ACTIVE
974
975 self._take_snapshot()
976
977 # make sure transaction is assigned before we call the
978 # dispatch
979 self.session._transaction = self
980
981 self.session.dispatch.after_transaction_create(self.session, self)
982
983 def _raise_for_prerequisite_state(
984 self, operation_name: str, state: _StateChangeState
985 ) -> NoReturn:
986 if state is SessionTransactionState.DEACTIVE:
987 if self._rollback_exception:
988 raise sa_exc.PendingRollbackError(
989 "This Session's transaction has been rolled back "
990 "due to a previous exception during flush."
991 " To begin a new transaction with this Session, "
992 "first issue Session.rollback()."
993 f" Original exception was: {self._rollback_exception}",
994 code="7s2a",
995 )
996 else:
997 raise sa_exc.InvalidRequestError(
998 "This session is in 'inactive' state, due to the "
999 "SQL transaction being rolled back; no further SQL "
1000 "can be emitted within this transaction."
1001 )
1002 elif state is SessionTransactionState.CLOSED:
1003 raise sa_exc.ResourceClosedError("This transaction is closed")
1004 elif state is SessionTransactionState.PROVISIONING_CONNECTION:
1005 raise sa_exc.InvalidRequestError(
1006 "This session is provisioning a new connection; concurrent "
1007 "operations are not permitted",
1008 code="isce",
1009 )
1010 else:
1011 raise sa_exc.InvalidRequestError(
1012 f"This session is in '{state.name.lower()}' state; no "
1013 "further SQL can be emitted within this transaction."
1014 )
1015
1016 @property
1017 def parent(self) -> Optional[SessionTransaction]:
1018 """The parent :class:`.SessionTransaction` of this
1019 :class:`.SessionTransaction`.
1020
1021 If this attribute is ``None``, indicates this
1022 :class:`.SessionTransaction` is at the top of the stack, and
1023 corresponds to a real "COMMIT"/"ROLLBACK"
1024 block. If non-``None``, then this is either a "subtransaction"
1025 (an internal marker object used by the flush process) or a
1026 "nested" / SAVEPOINT transaction. If the
1027 :attr:`.SessionTransaction.nested` attribute is ``True``, then
1028 this is a SAVEPOINT, and if ``False``, indicates this a subtransaction.
1029
1030 """
1031 return self._parent
1032
1033 @property
1034 def is_active(self) -> bool:
1035 return (
1036 self.session is not None
1037 and self._state is SessionTransactionState.ACTIVE
1038 )
1039
1040 @property
1041 def _is_transaction_boundary(self) -> bool:
1042 return self.nested or not self._parent
1043
1044 @_StateChange.declare_states(
1045 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE
1046 )
1047 def connection(
1048 self,
1049 bindkey: Optional[Mapper[Any]],
1050 execution_options: Optional[_ExecuteOptions] = None,
1051 **kwargs: Any,
1052 ) -> Connection:
1053 bind = self.session.get_bind(bindkey, **kwargs)
1054 return self._connection_for_bind(bind, execution_options)
1055
1056 @property
1057 def _connections_boundary(self) -> SessionTransaction:
1058 trans = self
1059 while not trans._is_transaction_boundary:
1060 assert trans._parent is not None
1061 trans = trans._parent
1062 return trans
1063
1064 @property
1065 def connections(self) -> FrozenSet[Connection]:
1066 """The :class:`_engine.Connection` objects this
1067 :class:`.SessionTransaction` has acquired so far.
1068
1069 Reading this attribute never acquires a connection, and works in any
1070 state of the transaction. After the transaction ends, it still holds
1071 the connections that were used.
1072
1073 .. note::
1074
1075 A savepoint holds only the connections it has used itself.
1076 To get all connections in the outer transaction, even while a
1077 savepoint is active, use :meth:`.Session.get_transaction`
1078 rather than :meth:`.Session.get_nested_transaction`::
1079
1080 root = session.get_transaction()
1081 connections = root.connections
1082
1083 .. versionadded:: 2.1.7
1084
1085 .. seealso::
1086
1087 :meth:`.SessionTransaction.bound_connection`
1088
1089 """
1090 connections = self._connections_boundary._connections
1091 return frozenset(conn for conn, _, _, _ in connections.values())
1092
1093 def bound_connection(
1094 self, bindkey: Optional[_EntityBindKey[Any]] = None, **kwargs: Any
1095 ) -> Connection:
1096 """Return the :class:`_engine.Connection` this
1097 :class:`.SessionTransaction` has acquired for the given bind key.
1098
1099 The bind key is resolved as in :meth:`_orm.Session.connection`, but
1100 no connection is acquired; if the transaction holds none for that
1101 bind, :class:`~sqlalchemy.exc.InvalidRequestError` is raised.
1102 Additional keyword arguments such as ``clause`` and ``bind`` are
1103 passed to :meth:`_orm.Session.get_bind`.
1104
1105 .. note::
1106
1107 A savepoint can return only a connection it has used itself.
1108 To look up a connection in the outer transaction, even while a
1109 savepoint is active, use :meth:`.Session.get_transaction`
1110 rather than :meth:`.Session.get_nested_transaction`::
1111
1112 root = session.get_transaction()
1113 connection = root.bound_connection(bindkey)
1114
1115 .. versionadded:: 2.1.7
1116
1117 .. seealso::
1118
1119 :attr:`.SessionTransaction.connections`
1120
1121 """
1122 bind = self.session.get_bind(bindkey, **kwargs)
1123 entry = self._connections_boundary._connections.get(bind)
1124 if entry is None:
1125 raise sa_exc.InvalidRequestError(
1126 "This transaction has not acquired a connection for "
1127 f"bind {bind!r}"
1128 )
1129 return entry[0]
1130
1131 @_StateChange.declare_states(
1132 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE
1133 )
1134 def _begin(self, nested: bool = False) -> SessionTransaction:
1135 return SessionTransaction(
1136 self.session,
1137 (
1138 SessionTransactionOrigin.BEGIN_NESTED
1139 if nested
1140 else SessionTransactionOrigin.SUBTRANSACTION
1141 ),
1142 self,
1143 )
1144
1145 def _iterate_self_and_parents(
1146 self, upto: Optional[SessionTransaction] = None
1147 ) -> Iterable[SessionTransaction]:
1148 current = self
1149 result: Tuple[SessionTransaction, ...] = ()
1150 while current:
1151 result += (current,)
1152 if current._parent is upto:
1153 break
1154 elif current._parent is None:
1155 raise sa_exc.InvalidRequestError(
1156 "Transaction %s is not on the active transaction list"
1157 % (upto)
1158 )
1159 else:
1160 current = current._parent
1161
1162 return result
1163
1164 def _take_snapshot(self) -> None:
1165 if not self._is_transaction_boundary:
1166 parent = self._parent
1167 assert parent is not None
1168 self._new = parent._new
1169 self._deleted = parent._deleted
1170 self._dirty = parent._dirty
1171 self._key_switches = parent._key_switches
1172 return
1173
1174 is_begin = self.origin in (
1175 SessionTransactionOrigin.BEGIN,
1176 SessionTransactionOrigin.AUTOBEGIN,
1177 )
1178 if not is_begin and not self.session._flushing:
1179 self.session.flush()
1180
1181 self._new = weakref.WeakKeyDictionary()
1182 self._deleted = weakref.WeakKeyDictionary()
1183 self._dirty = weakref.WeakKeyDictionary()
1184 self._key_switches = weakref.WeakKeyDictionary()
1185
1186 def _restore_snapshot(self, dirty_only: bool = False) -> None:
1187 """Restore the restoration state taken before a transaction began.
1188
1189 Corresponds to a rollback.
1190
1191 """
1192 assert self._is_transaction_boundary
1193
1194 to_expunge = set(self._new).union(self.session._new)
1195 self.session._expunge_states(to_expunge, to_transient=True)
1196
1197 for s, (oldkey, newkey) in self._key_switches.items():
1198 # we probably can do this conditionally based on
1199 # if we expunged or not, but safe_discard does that anyway
1200 self.session.identity_map.safe_discard(s)
1201
1202 # restore the old key
1203 s.key = oldkey
1204
1205 # now restore the object, but only if we didn't expunge
1206 if s not in to_expunge:
1207 self.session.identity_map.replace(s)
1208
1209 for s in set(self._deleted).union(self.session._deleted):
1210 self.session._update_impl(s, revert_deletion=True)
1211
1212 assert not self.session._deleted
1213
1214 for s in self.session.identity_map.all_states():
1215 if not dirty_only or s.modified or s in self._dirty:
1216 s._expire(s.dict, self.session.identity_map._modified)
1217
1218 def _remove_snapshot(self) -> None:
1219 """Remove the restoration state taken before a transaction began.
1220
1221 Corresponds to a commit.
1222
1223 """
1224 assert self._is_transaction_boundary
1225
1226 if not self.nested and self.session.expire_on_commit:
1227 for s in self.session.identity_map.all_states():
1228 s._expire(s.dict, self.session.identity_map._modified)
1229
1230 statelib.InstanceState._detach_states(
1231 list(self._deleted), self.session
1232 )
1233 self._deleted.clear()
1234 elif self.nested:
1235 parent = self._parent
1236 assert parent is not None
1237 parent._new.update(self._new)
1238 parent._dirty.update(self._dirty)
1239 parent._deleted.update(self._deleted)
1240 parent._key_switches.update(self._key_switches)
1241
1242 @_StateChange.declare_states(
1243 (SessionTransactionState.ACTIVE,), _StateChangeStates.NO_CHANGE
1244 )
1245 def _connection_for_bind(
1246 self,
1247 bind: _SessionBind,
1248 execution_options: Optional[CoreExecuteOptionsParameter],
1249 ) -> Connection:
1250 if bind in self._connections:
1251 if execution_options:
1252 util.warn(
1253 "Connection is already established for the "
1254 "given bind; execution_options ignored"
1255 )
1256 return self._connections[bind][0]
1257
1258 self._state = SessionTransactionState.PROVISIONING_CONNECTION
1259
1260 local_connect = False
1261 should_commit = True
1262
1263 try:
1264 if self._parent:
1265 # the root transaction applies execution options if it
1266 # acquires the connection here, else warns that they are
1267 # ignored
1268 conn = self._parent._connection_for_bind(
1269 bind, execution_options
1270 )
1271 if not self.nested:
1272 return conn
1273 else:
1274 if isinstance(bind, engine.Connection):
1275 conn = bind
1276 if conn.engine in self._connections:
1277 raise sa_exc.InvalidRequestError(
1278 "Session already has a Connection associated "
1279 "for the given Connection's Engine"
1280 )
1281 else:
1282 conn = bind.connect()
1283 local_connect = True
1284
1285 try:
1286 # a savepoint receives the connection from the root
1287 # transaction with execution options already applied
1288 if self._parent is None:
1289 conn_exec_opts: Dict[str, Any] = {}
1290 if self.session.execution_options:
1291 conn_exec_opts.update(self.session.execution_options)
1292 if execution_options:
1293 conn_exec_opts.update(execution_options)
1294 if conn_exec_opts:
1295 conn = conn.execution_options(**conn_exec_opts)
1296
1297 transaction: Transaction
1298 if self.session.twophase and self._parent is None:
1299 # TODO: shouldn't we only be here if not
1300 # conn.in_transaction() ?
1301 # if twophase is set and conn.in_transaction(), validate
1302 # that it is in fact twophase.
1303 transaction = conn.begin_twophase()
1304 elif self.nested:
1305 transaction = conn.begin_nested()
1306 elif conn.in_transaction():
1307
1308 if local_connect:
1309 _trans = conn.get_transaction()
1310 assert _trans is not None
1311 transaction = _trans
1312 else:
1313 join_transaction_mode = (
1314 self.session.join_transaction_mode
1315 )
1316
1317 if join_transaction_mode == "conditional_savepoint":
1318 if conn.in_nested_transaction():
1319 join_transaction_mode = "create_savepoint"
1320 else:
1321 join_transaction_mode = "rollback_only"
1322
1323 if join_transaction_mode in (
1324 "control_fully",
1325 "rollback_only",
1326 ):
1327 if conn.in_nested_transaction():
1328 transaction = (
1329 conn._get_required_nested_transaction()
1330 )
1331 else:
1332 transaction = conn._get_required_transaction()
1333 if join_transaction_mode == "rollback_only":
1334 should_commit = False
1335 elif join_transaction_mode == "create_savepoint":
1336 transaction = conn.begin_nested()
1337 else:
1338 assert False, join_transaction_mode
1339 else:
1340 transaction = conn.begin()
1341 except:
1342 # connection will not not be associated with this Session;
1343 # close it immediately so that it isn't closed under GC
1344 if local_connect:
1345 conn.close()
1346 raise
1347 else:
1348 bind_is_connection = isinstance(bind, engine.Connection)
1349
1350 self._connections[conn] = self._connections[conn.engine] = (
1351 conn,
1352 transaction,
1353 should_commit,
1354 not bind_is_connection,
1355 )
1356 self.session.dispatch.after_begin(self.session, self, conn)
1357 return conn
1358 finally:
1359 self._state = SessionTransactionState.ACTIVE
1360
1361 def prepare(self) -> None:
1362 if self._parent is not None or not self.session.twophase:
1363 raise sa_exc.InvalidRequestError(
1364 "'twophase' mode not enabled, or not root transaction; "
1365 "can't prepare."
1366 )
1367 self._prepare_impl()
1368
1369 @_StateChange.declare_states(
1370 (SessionTransactionState.ACTIVE,), SessionTransactionState.PREPARED
1371 )
1372 def _prepare_impl(self) -> None:
1373 if self._parent is None or self.nested:
1374 self.session.dispatch.before_commit(self.session)
1375
1376 stx = self.session._transaction
1377 assert stx is not None
1378 if stx is not self:
1379 for subtransaction in stx._iterate_self_and_parents(upto=self):
1380 subtransaction.commit()
1381
1382 if not self.session._flushing:
1383 for _flush_guard in range(100):
1384 if self.session._is_clean():
1385 break
1386 self.session.flush()
1387 else:
1388 raise exc.FlushError(
1389 "Over 100 subsequent flushes have occurred within "
1390 "session.commit() - is an after_flush() hook "
1391 "creating new objects?"
1392 )
1393
1394 if self._parent is None and self.session.twophase:
1395 try:
1396 for t in set(self._connections.values()):
1397 cast("TwoPhaseTransaction", t[1]).prepare()
1398 except:
1399 with util.safe_reraise():
1400 with self._expect_state(SessionTransactionState.CLOSED):
1401 self.rollback()
1402
1403 self._state = SessionTransactionState.PREPARED
1404
1405 @_StateChange.declare_states(
1406 (SessionTransactionState.ACTIVE, SessionTransactionState.PREPARED),
1407 SessionTransactionState.CLOSED,
1408 )
1409 def commit(self, _to_root: bool = False) -> None:
1410 if self._state is not SessionTransactionState.PREPARED:
1411 with self._expect_state(SessionTransactionState.PREPARED):
1412 self._prepare_impl()
1413
1414 if self._parent is None or self.nested:
1415 for conn, trans, should_commit, autoclose in set(
1416 self._connections.values()
1417 ):
1418 if should_commit:
1419 trans.commit()
1420
1421 self._state = SessionTransactionState.COMMITTED
1422 self.session.dispatch.after_commit(self.session)
1423
1424 self._remove_snapshot()
1425
1426 with self._expect_state(SessionTransactionState.CLOSED):
1427 self.close()
1428
1429 if _to_root and self._parent:
1430 self._parent.commit(_to_root=True)
1431
1432 @_StateChange.declare_states(
1433 (
1434 SessionTransactionState.ACTIVE,
1435 SessionTransactionState.DEACTIVE,
1436 SessionTransactionState.PREPARED,
1437 ),
1438 SessionTransactionState.CLOSED,
1439 )
1440 def rollback(
1441 self, _capture_exception: bool = False, _to_root: bool = False
1442 ) -> None:
1443 stx = self.session._transaction
1444 assert stx is not None
1445 if stx is not self:
1446 for subtransaction in stx._iterate_self_and_parents(upto=self):
1447 subtransaction.close()
1448
1449 boundary = self
1450 rollback_err = None
1451 if self._state in (
1452 SessionTransactionState.ACTIVE,
1453 SessionTransactionState.PREPARED,
1454 ):
1455 for transaction in self._iterate_self_and_parents():
1456 if transaction._parent is None or transaction.nested:
1457 try:
1458 for t in set(transaction._connections.values()):
1459 t[1].rollback()
1460
1461 transaction._state = SessionTransactionState.DEACTIVE
1462 self.session.dispatch.after_rollback(self.session)
1463 except:
1464 rollback_err = sys.exc_info()
1465 finally:
1466 transaction._state = SessionTransactionState.DEACTIVE
1467 transaction._restore_snapshot(
1468 dirty_only=transaction.nested
1469 )
1470 boundary = transaction
1471 break
1472 else:
1473 transaction._state = SessionTransactionState.DEACTIVE
1474
1475 sess = self.session
1476
1477 if not rollback_err and not sess._is_clean():
1478 # if items were added, deleted, or mutated
1479 # here, we need to re-restore the snapshot
1480 util.warn(
1481 "Session's state has been changed on "
1482 "a non-active transaction - this state "
1483 "will be discarded."
1484 )
1485 boundary._restore_snapshot(dirty_only=boundary.nested)
1486
1487 with self._expect_state(SessionTransactionState.CLOSED):
1488 self.close()
1489
1490 if self._parent and _capture_exception:
1491 self._parent._rollback_exception = sys.exc_info()[1]
1492
1493 if rollback_err and rollback_err[1]:
1494 raise rollback_err[1].with_traceback(rollback_err[2])
1495
1496 sess.dispatch.after_soft_rollback(sess, self)
1497
1498 if _to_root and self._parent:
1499 self._parent.rollback(_to_root=True)
1500
1501 @_StateChange.declare_states(
1502 _StateChangeStates.ANY, SessionTransactionState.CLOSED
1503 )
1504 def close(self, invalidate: bool = False) -> None:
1505 if self.nested:
1506 self.session._nested_transaction = (
1507 self._previous_nested_transaction
1508 )
1509
1510 self.session._transaction = self._parent
1511
1512 for connection, transaction, should_commit, autoclose in set(
1513 self._connections.values()
1514 ):
1515 if invalidate and self._parent is None:
1516 connection.invalidate()
1517 if should_commit and transaction.is_active:
1518 transaction.close()
1519 if autoclose and self._parent is None:
1520 connection.close()
1521
1522 self._state = SessionTransactionState.CLOSED
1523 sess = self.session
1524
1525 # TODO: these two None sets were historically after the
1526 # event hook below, and in 2.0 I changed it this way for some reason,
1527 # and I remember there being a reason, but not what it was.
1528 # Why do we need to get rid of them at all? test_memusage::CycleTest
1529 # passes with these commented out.
1530 # self.session = None # type: ignore
1531 # self._connections = None # type: ignore
1532
1533 sess.dispatch.after_transaction_end(sess, self)
1534
1535 def _get_subject(self) -> Session:
1536 return self.session
1537
1538 def _transaction_is_active(self) -> bool:
1539 return self._state is SessionTransactionState.ACTIVE
1540
1541 def _transaction_is_closed(self) -> bool:
1542 return self._state is SessionTransactionState.CLOSED
1543
1544 def _rollback_can_be_called(self) -> bool:
1545 return self._state not in (COMMITTED, CLOSED)
1546
1547
1548class _SessionCloseState(Enum):
1549 ACTIVE = 1
1550 CLOSED = 2
1551 CLOSE_IS_RESET = 3
1552
1553
1554class Session(_SessionClassMethods, EventTarget):
1555 """Manages persistence operations for ORM-mapped objects.
1556
1557 The :class:`_orm.Session` is **not safe for use in concurrent threads.**.
1558 See :ref:`session_faq_threadsafe` for background.
1559
1560 The Session's usage paradigm is described at :doc:`/orm/session`.
1561
1562
1563 """
1564
1565 _is_asyncio = False
1566
1567 dispatch: dispatcher[Session]
1568
1569 identity_map: IdentityMap
1570 """A mapping of object identities to objects themselves.
1571
1572 Iterating through ``Session.identity_map.values()`` provides
1573 access to the full set of persistent objects (i.e., those
1574 that have row identity) currently in the session.
1575
1576 .. seealso::
1577
1578 :func:`.identity_key` - helper function to produce the keys used
1579 in this dictionary.
1580
1581 """
1582
1583 binds: Mapping[_SessionBindKey, _SessionBind]
1584 """An immutable mapping of bind targets to :class:`_engine.Engine` or
1585 :class:`_engine.Connection` objects.
1586
1587 This collection is established from the
1588 :paramref:`_orm.Session.binds` parameter as well as the
1589 :meth:`_orm.Session.bind_mapper` and :meth:`_orm.Session.bind_table`
1590 methods, and is consulted by :meth:`_orm.Session.get_bind`. The keys are
1591 normalized from what was originally passed; a mapper or mapped class is
1592 entered both under the mapped class and under each of its selectables.
1593
1594 The collection is replaced, rather than mutated, whenever a new bind is
1595 added, so a reference to it will not observe subsequent changes.
1596
1597 .. versionadded:: 2.1 Previously this collection was stored
1598 privately.
1599
1600 """
1601
1602 _new: Dict[InstanceState[Any], Any]
1603 _deleted: Dict[InstanceState[Any], Any]
1604 bind: Optional[Union[Engine, Connection]]
1605 _flushing: bool
1606 _warn_on_events: bool
1607 _transaction: Optional[SessionTransaction]
1608 _nested_transaction: Optional[SessionTransaction]
1609 hash_key: int
1610 autoflush: bool
1611 expire_on_commit: bool
1612 enable_baked_queries: bool
1613 twophase: bool
1614 join_transaction_mode: JoinTransactionMode
1615 execution_options: _ExecuteOptions = util.EMPTY_DICT
1616 _query_cls: Type[Query[Any]]
1617 _close_state: _SessionCloseState
1618
1619 def __init__(
1620 self,
1621 bind: Optional[_SessionBind] = None,
1622 *,
1623 autoflush: bool = True,
1624 future: Literal[True] = True,
1625 expire_on_commit: bool = True,
1626 autobegin: bool = True,
1627 twophase: bool = False,
1628 binds: Optional[Dict[_SessionBindKey, _SessionBind]] = None,
1629 enable_baked_queries: bool = True,
1630 info: Optional[_InfoType] = None,
1631 query_cls: Optional[Type[Query[Any]]] = None,
1632 autocommit: Literal[False] = False,
1633 join_transaction_mode: JoinTransactionMode = "conditional_savepoint",
1634 close_resets_only: Union[bool, _NoArg] = _NoArg.NO_ARG,
1635 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
1636 ):
1637 r"""Construct a new :class:`_orm.Session`.
1638
1639 See also the :class:`.sessionmaker` function which is used to
1640 generate a :class:`.Session`-producing callable with a given
1641 set of arguments.
1642
1643 :param autoflush: When ``True``, all query operations will issue a
1644 :meth:`~.Session.flush` call to this ``Session`` before proceeding.
1645 This is a convenience feature so that :meth:`~.Session.flush` need
1646 not be called repeatedly in order for database queries to retrieve
1647 results.
1648
1649 .. seealso::
1650
1651 :ref:`session_flushing` - additional background on autoflush
1652
1653 :param autobegin: Automatically start transactions (i.e. equivalent to
1654 invoking :meth:`_orm.Session.begin`) when database access is
1655 requested by an operation. Defaults to ``True``. Set to
1656 ``False`` to prevent a :class:`_orm.Session` from implicitly
1657 beginning transactions after construction, as well as after any of
1658 the :meth:`_orm.Session.rollback`, :meth:`_orm.Session.commit`,
1659 or :meth:`_orm.Session.close` methods are called.
1660
1661 .. versionadded:: 2.0
1662
1663 .. seealso::
1664
1665 :ref:`session_autobegin_disable`
1666
1667 :param bind: An optional :class:`_engine.Engine` or
1668 :class:`_engine.Connection` to
1669 which this ``Session`` should be bound. When specified, all SQL
1670 operations performed by this session will execute via this
1671 connectable.
1672
1673 :param binds: A dictionary which may specify any number of
1674 :class:`_engine.Engine` or :class:`_engine.Connection`
1675 objects as the source of
1676 connectivity for SQL operations on a per-entity basis. The keys
1677 of the dictionary consist of any series of mapped classes,
1678 arbitrary Python classes that are bases for mapped classes,
1679 :class:`_schema.Table` objects and :class:`_orm.Mapper` objects.
1680 The
1681 values of the dictionary are then instances of
1682 :class:`_engine.Engine`
1683 or less commonly :class:`_engine.Connection` objects.
1684 Operations which
1685 proceed relative to a particular mapped class will consult this
1686 dictionary for the closest matching entity in order to determine
1687 which :class:`_engine.Engine` should be used for a particular SQL
1688 operation. The complete heuristics for resolution are
1689 described at :meth:`.Session.get_bind`. Usage looks like::
1690
1691 Session = sessionmaker(
1692 binds={
1693 SomeMappedClass: create_engine("postgresql+psycopg2://engine1"),
1694 SomeDeclarativeBase: create_engine(
1695 "postgresql+psycopg2://engine2"
1696 ),
1697 some_mapper: create_engine("postgresql+psycopg2://engine3"),
1698 some_table: create_engine("postgresql+psycopg2://engine4"),
1699 }
1700 )
1701
1702 .. seealso::
1703
1704 :ref:`session_partitioning`
1705
1706 :meth:`.Session.bind_mapper`
1707
1708 :meth:`.Session.bind_table`
1709
1710 :meth:`.Session.get_bind`
1711
1712
1713 :param \class_: Specify an alternate class other than
1714 ``sqlalchemy.orm.session.Session`` which should be used by the
1715 returned class. This is the only argument that is local to the
1716 :class:`.sessionmaker` function, and is not sent directly to the
1717 constructor for ``Session``.
1718
1719 :param enable_baked_queries: legacy; defaults to ``True``.
1720 A parameter consumed
1721 by the :mod:`sqlalchemy.ext.baked` extension to determine if
1722 "baked queries" should be cached, as is the normal operation
1723 of this extension. When set to ``False``, caching as used by
1724 this particular extension is disabled.
1725
1726 .. versionchanged:: 1.4 The ``sqlalchemy.ext.baked`` extension is
1727 legacy and is not used by any of SQLAlchemy's internals. This
1728 flag therefore only affects applications that are making explicit
1729 use of this extension within their own code.
1730
1731 :param execution_options: optional dictionary of execution options
1732 that will be applied to the :class:`_engine.Connection` when first
1733 procured for a transaction, as well as to all explicit query
1734 executions such as :meth:`_orm.Session.execute`,
1735 :meth:`_orm.Session.scalars`, and similar. This includes
1736 flush (INSERT/UPDATE/DELETE) operations and is visible within
1737 event hooks such as
1738 :meth:`_events.ConnectionEvents.before_cursor_execute`.
1739
1740 Execution options present in statements as well as options passed
1741 to methods like :meth:`_orm.Session.execute` explicitly take
1742 precedence over the session-wide options.
1743
1744 .. versionadded:: 2.1
1745
1746 .. versionchanged:: 2.1.0b3
1747 Session-level execution options are now applied to the
1748 :class:`_engine.Connection` at procurement time, so that
1749 they take effect for flush operations as well as explicit
1750 query executions. Previously, options were only applied to
1751 explicit calls such as :meth:`_orm.Session.execute`.
1752
1753 :param expire_on_commit: Defaults to ``True``. When ``True``, all
1754 instances will be fully expired after each :meth:`~.commit`,
1755 so that all attribute/object access subsequent to a completed
1756 transaction will load from the most recent database state.
1757
1758 .. seealso::
1759
1760 :ref:`session_committing`
1761
1762 :param future: Deprecated; this flag is always True.
1763
1764 .. seealso::
1765
1766 :ref:`migration_20_toplevel`
1767
1768 :param info: optional dictionary of arbitrary data to be associated
1769 with this :class:`.Session`. Is available via the
1770 :attr:`.Session.info` attribute. Note the dictionary is copied at
1771 construction time so that modifications to the per-
1772 :class:`.Session` dictionary will be local to that
1773 :class:`.Session`.
1774
1775 :param query_cls: Class which should be used to create new Query
1776 objects, as returned by the :meth:`~.Session.query` method.
1777 Defaults to :class:`_query.Query`.
1778
1779 :param twophase: When ``True``, all transactions will be started as
1780 a "two phase" transaction, i.e. using the "two phase" semantics
1781 of the database in use along with an XID. During a
1782 :meth:`~.commit`, after :meth:`~.flush` has been issued for all
1783 attached databases, the :meth:`~.TwoPhaseTransaction.prepare`
1784 method on each database's :class:`.TwoPhaseTransaction` will be
1785 called. This allows each database to roll back the entire
1786 transaction, before each transaction is committed.
1787
1788 :param autocommit: the "autocommit" keyword is present for backwards
1789 compatibility but must remain at its default value of ``False``.
1790
1791 :param join_transaction_mode: Describes the transactional behavior to
1792 take when a given bind is a :class:`_engine.Connection` that
1793 has already begun a transaction outside the scope of this
1794 :class:`_orm.Session`; in other words the
1795 :meth:`_engine.Connection.in_transaction()` method returns True.
1796
1797 The following behaviors only take effect when the :class:`_orm.Session`
1798 **actually makes use of the connection given**; that is, a method
1799 such as :meth:`_orm.Session.execute`, :meth:`_orm.Session.connection`,
1800 etc. are actually invoked:
1801
1802 * ``"conditional_savepoint"`` - this is the default. if the given
1803 :class:`_engine.Connection` is begun within a transaction but
1804 does not have a SAVEPOINT, then ``"rollback_only"`` is used.
1805 If the :class:`_engine.Connection` is additionally within
1806 a SAVEPOINT, in other words
1807 :meth:`_engine.Connection.in_nested_transaction()` method returns
1808 True, then ``"create_savepoint"`` is used.
1809
1810 ``"conditional_savepoint"`` behavior attempts to make use of
1811 savepoints in order to keep the state of the existing transaction
1812 unchanged, but only if there is already a savepoint in progress;
1813 otherwise, it is not assumed that the backend in use has adequate
1814 support for SAVEPOINT, as availability of this feature varies.
1815 ``"conditional_savepoint"`` also seeks to establish approximate
1816 backwards compatibility with previous :class:`_orm.Session`
1817 behavior, for applications that are not setting a specific mode. It
1818 is recommended that one of the explicit settings be used.
1819
1820 * ``"create_savepoint"`` - the :class:`_orm.Session` will use
1821 :meth:`_engine.Connection.begin_nested()` in all cases to create
1822 its own transaction. This transaction by its nature rides
1823 "on top" of any existing transaction that's opened on the given
1824 :class:`_engine.Connection`; if the underlying database and
1825 the driver in use has full, non-broken support for SAVEPOINT, the
1826 external transaction will remain unaffected throughout the
1827 lifespan of the :class:`_orm.Session`.
1828
1829 The ``"create_savepoint"`` mode is the most useful for integrating
1830 a :class:`_orm.Session` into a test suite where an externally
1831 initiated transaction should remain unaffected; however, it relies
1832 on proper SAVEPOINT support from the underlying driver and
1833 database.
1834
1835 .. tip:: When using SQLite, the SQLite driver included through
1836 Python 3.11 does not handle SAVEPOINTs correctly in all cases
1837 without workarounds. See the sections
1838 :ref:`pysqlite_serializable` and :ref:`aiosqlite_serializable`
1839 for details on current workarounds.
1840
1841 * ``"control_fully"`` - the :class:`_orm.Session` will take
1842 control of the given transaction as its own;
1843 :meth:`_orm.Session.commit` will call ``.commit()`` on the
1844 transaction, :meth:`_orm.Session.rollback` will call
1845 ``.rollback()`` on the transaction, :meth:`_orm.Session.close` will
1846 call ``.rollback`` on the transaction.
1847
1848 .. tip:: This mode of use is equivalent to how SQLAlchemy 1.4 would
1849 handle a :class:`_engine.Connection` given with an existing
1850 SAVEPOINT (i.e. :meth:`_engine.Connection.begin_nested`); the
1851 :class:`_orm.Session` would take full control of the existing
1852 SAVEPOINT.
1853
1854 * ``"rollback_only"`` - the :class:`_orm.Session` will take control
1855 of the given transaction for ``.rollback()`` calls only;
1856 ``.commit()`` calls will not be propagated to the given
1857 transaction. ``.close()`` calls will have no effect on the
1858 given transaction.
1859
1860 .. tip:: This mode of use is equivalent to how SQLAlchemy 1.4 would
1861 handle a :class:`_engine.Connection` given with an existing
1862 regular database transaction (i.e.
1863 :meth:`_engine.Connection.begin`); the :class:`_orm.Session`
1864 would propagate :meth:`_orm.Session.rollback` calls to the
1865 underlying transaction, but not :meth:`_orm.Session.commit` or
1866 :meth:`_orm.Session.close` calls.
1867
1868 .. versionadded:: 2.0.0rc1
1869
1870 :param close_resets_only: Defaults to ``True``. Determines if
1871 the session should reset itself after calling ``.close()``
1872 or should pass in a no longer usable state, disabling reuse.
1873
1874 .. versionadded:: 2.0.22 added flag ``close_resets_only``.
1875 A future SQLAlchemy version may change the default value of
1876 this flag to ``False``.
1877
1878 .. seealso::
1879
1880 :ref:`session_closing` - Detail on the semantics of
1881 :meth:`_orm.Session.close` and :meth:`_orm.Session.reset`.
1882
1883 """ # noqa
1884
1885 # considering allowing the "autocommit" keyword to still be accepted
1886 # as long as it's False, so that external test suites, oslo.db etc
1887 # continue to function as the argument appears to be passed in lots
1888 # of cases including in our own test suite
1889 if autocommit:
1890 raise sa_exc.ArgumentError(
1891 "autocommit=True is no longer supported"
1892 )
1893 self.identity_map = identity._WeakInstanceDict()
1894
1895 if not future:
1896 raise sa_exc.ArgumentError(
1897 "The 'future' parameter passed to "
1898 "Session() may only be set to True."
1899 )
1900
1901 self._new = {} # InstanceState->object, strong refs object
1902 self._deleted = {} # same
1903 self.bind = bind
1904 self.binds = util.EMPTY_DICT
1905 self._flushing = False
1906 self._warn_on_events = False
1907 self._transaction = None
1908 self._nested_transaction = None
1909 self.hash_key = _new_sessionid()
1910 self.autobegin = autobegin
1911 self.autoflush = autoflush
1912 self.expire_on_commit = expire_on_commit
1913 self.enable_baked_queries = enable_baked_queries
1914 if execution_options:
1915 self.execution_options = self.execution_options.union(
1916 execution_options
1917 )
1918
1919 # the idea is that at some point NO_ARG will warn that in the future
1920 # the default will switch to close_resets_only=False.
1921 if close_resets_only in (True, _NoArg.NO_ARG):
1922 self._close_state = _SessionCloseState.CLOSE_IS_RESET
1923 else:
1924 self._close_state = _SessionCloseState.ACTIVE
1925 if (
1926 join_transaction_mode
1927 and join_transaction_mode
1928 not in JoinTransactionMode.__args__ # type: ignore[attr-defined]
1929 ):
1930 raise sa_exc.ArgumentError(
1931 f"invalid selection for join_transaction_mode: "
1932 f'"{join_transaction_mode}"'
1933 )
1934 self.join_transaction_mode = join_transaction_mode
1935
1936 self.twophase = twophase
1937 self._query_cls = query_cls if query_cls else query.Query
1938 if info:
1939 self.info.update(info)
1940
1941 if binds is not None:
1942 for key, bind in binds.items():
1943 self._add_bind(key, bind)
1944
1945 _sessions[self.hash_key] = self
1946
1947 # used by sqlalchemy.engine.util.TransactionalContext
1948 _trans_context_manager: Optional[TransactionalContext] = None
1949
1950 connection_callable: Optional[_ConnectionCallableProto] = None
1951
1952 def __enter__(self: _S) -> _S:
1953 return self
1954
1955 def __exit__(self, type_: Any, value: Any, traceback: Any) -> None:
1956 self.close()
1957
1958 @contextlib.contextmanager
1959 def _maker_context_manager(self: _S) -> Iterator[_S]:
1960 with self:
1961 with self.begin():
1962 yield self
1963
1964 def in_transaction(self) -> bool:
1965 """Return True if this :class:`_orm.Session` has begun a transaction.
1966
1967 .. versionadded:: 1.4
1968
1969 .. seealso::
1970
1971 :attr:`_orm.Session.is_active`
1972
1973
1974 """
1975 return self._transaction is not None
1976
1977 def in_nested_transaction(self) -> bool:
1978 """Return True if this :class:`_orm.Session` has begun a nested
1979 transaction, e.g. SAVEPOINT.
1980
1981 .. versionadded:: 1.4
1982
1983 """
1984 return self._nested_transaction is not None
1985
1986 def get_transaction(self) -> Optional[SessionTransaction]:
1987 """Return the current root transaction in progress, if any.
1988
1989 .. versionadded:: 1.4
1990
1991 """
1992 trans = self._transaction
1993 while trans is not None and trans._parent is not None:
1994 trans = trans._parent
1995 return trans
1996
1997 def get_nested_transaction(self) -> Optional[SessionTransaction]:
1998 """Return the current nested transaction in progress, if any.
1999
2000 .. versionadded:: 1.4
2001
2002 """
2003
2004 return self._nested_transaction
2005
2006 @util.memoized_property
2007 def info(self) -> _InfoType:
2008 """A user-modifiable dictionary.
2009
2010 The initial value of this dictionary can be populated using the
2011 ``info`` argument to the :class:`.Session` constructor or
2012 :class:`.sessionmaker` constructor or factory methods. The dictionary
2013 here is always local to this :class:`.Session` and can be modified
2014 independently of all other :class:`.Session` objects.
2015
2016 """
2017 return {}
2018
2019 def _autobegin_t(self, begin: bool = False) -> SessionTransaction:
2020 if self._transaction is None:
2021 if not begin and not self.autobegin:
2022 raise sa_exc.InvalidRequestError(
2023 "Autobegin is disabled on this Session; please call "
2024 "session.begin() to start a new transaction"
2025 )
2026 trans = SessionTransaction(
2027 self,
2028 (
2029 SessionTransactionOrigin.BEGIN
2030 if begin
2031 else SessionTransactionOrigin.AUTOBEGIN
2032 ),
2033 )
2034 assert self._transaction is trans
2035 return trans
2036
2037 return self._transaction
2038
2039 def begin(self, nested: bool = False) -> SessionTransaction:
2040 """Begin a transaction, or nested transaction,
2041 on this :class:`.Session`, if one is not already begun.
2042
2043 The :class:`_orm.Session` object features **autobegin** behavior,
2044 so that normally it is not necessary to call the
2045 :meth:`_orm.Session.begin`
2046 method explicitly. However, it may be used in order to control
2047 the scope of when the transactional state is begun.
2048
2049 When used to begin the outermost transaction, an error is raised
2050 if this :class:`.Session` is already inside of a transaction.
2051
2052 :param nested: if True, begins a SAVEPOINT transaction and is
2053 equivalent to calling :meth:`~.Session.begin_nested`. For
2054 documentation on SAVEPOINT transactions, please see
2055 :ref:`session_begin_nested`.
2056
2057 :return: the :class:`.SessionTransaction` object. Note that
2058 :class:`.SessionTransaction`
2059 acts as a Python context manager, allowing :meth:`.Session.begin`
2060 to be used in a "with" block. See :ref:`session_explicit_begin` for
2061 an example.
2062
2063 .. seealso::
2064
2065 :ref:`session_autobegin`
2066
2067 :ref:`unitofwork_transaction`
2068
2069 :meth:`.Session.begin_nested`
2070
2071
2072 """
2073
2074 trans = self._transaction
2075 if trans is None:
2076 trans = self._autobegin_t(begin=True)
2077
2078 if not nested:
2079 return trans
2080
2081 assert trans is not None
2082
2083 if nested:
2084 trans = trans._begin(nested=nested)
2085 assert self._transaction is trans
2086 self._nested_transaction = trans
2087 else:
2088 raise sa_exc.InvalidRequestError(
2089 "A transaction is already begun on this Session."
2090 )
2091
2092 return trans # needed for __enter__/__exit__ hook
2093
2094 def begin_nested(self) -> SessionTransaction:
2095 """Begin a "nested" transaction on this Session, e.g. SAVEPOINT.
2096
2097 The target database(s) and associated drivers must support SQL
2098 SAVEPOINT for this method to function correctly.
2099
2100 For documentation on SAVEPOINT
2101 transactions, please see :ref:`session_begin_nested`.
2102
2103 :return: the :class:`.SessionTransaction` object. Note that
2104 :class:`.SessionTransaction` acts as a context manager, allowing
2105 :meth:`.Session.begin_nested` to be used in a "with" block.
2106 See :ref:`session_begin_nested` for a usage example.
2107
2108 .. seealso::
2109
2110 :ref:`session_begin_nested`
2111
2112 :ref:`pysqlite_serializable` - special workarounds required
2113 with the SQLite driver in order for SAVEPOINT to work
2114 correctly. For asyncio use cases, see the section
2115 :ref:`aiosqlite_serializable`.
2116
2117 """
2118 return self.begin(nested=True)
2119
2120 def rollback(self) -> None:
2121 """Rollback the current transaction in progress.
2122
2123 If no transaction is in progress, this method is a pass-through.
2124
2125 The method always rolls back
2126 the topmost database transaction, discarding any nested
2127 transactions that may be in progress.
2128
2129 .. seealso::
2130
2131 :ref:`session_rollback`
2132
2133 :ref:`unitofwork_transaction`
2134
2135 """
2136 if self._transaction is None:
2137 pass
2138 else:
2139 self._transaction.rollback(_to_root=True)
2140
2141 def commit(self) -> None:
2142 """Flush pending changes and commit the current transaction.
2143
2144 When the COMMIT operation is complete, all objects are fully
2145 :term:`expired`, erasing their internal contents, which will be
2146 automatically re-loaded when the objects are next accessed. In the
2147 interim, these objects are in an expired state and will not function if
2148 they are :term:`detached` from the :class:`.Session`. Additionally,
2149 this re-load operation is not supported when using asyncio-oriented
2150 APIs. The :paramref:`.Session.expire_on_commit` parameter may be used
2151 to disable this behavior.
2152
2153 When there is no transaction in place for the :class:`.Session`,
2154 indicating that no operations were invoked on this :class:`.Session`
2155 since the previous call to :meth:`.Session.commit`, the method will
2156 begin and commit an internal-only "logical" transaction, that does not
2157 normally affect the database unless pending flush changes were
2158 detected, but will still invoke event handlers and object expiration
2159 rules.
2160
2161 The outermost database transaction is committed unconditionally,
2162 automatically releasing any SAVEPOINTs in effect.
2163
2164 .. seealso::
2165
2166 :ref:`session_committing`
2167
2168 :ref:`unitofwork_transaction`
2169
2170 :ref:`asyncio_orm_avoid_lazyloads`
2171
2172 """
2173 trans = self._transaction
2174 if trans is None:
2175 trans = self._autobegin_t()
2176
2177 trans.commit(_to_root=True)
2178
2179 def prepare(self) -> None:
2180 """Prepare the current transaction in progress for two phase commit.
2181
2182 If no transaction is in progress, this method raises an
2183 :exc:`~sqlalchemy.exc.InvalidRequestError`.
2184
2185 Only root transactions of two phase sessions can be prepared. If the
2186 current transaction is not such, an
2187 :exc:`~sqlalchemy.exc.InvalidRequestError` is raised.
2188
2189 """
2190 trans = self._transaction
2191 if trans is None:
2192 trans = self._autobegin_t()
2193
2194 trans.prepare()
2195
2196 def connection(
2197 self,
2198 bind_arguments: Optional[_BindArguments] = None,
2199 execution_options: Optional[CoreExecuteOptionsParameter] = None,
2200 ) -> Connection:
2201 r"""Return a :class:`_engine.Connection` object corresponding to this
2202 :class:`.Session` object's transactional state.
2203
2204 Either the :class:`_engine.Connection` corresponding to the current
2205 transaction is returned, or if no transaction is in progress, a new
2206 one is begun and the :class:`_engine.Connection`
2207 returned (note that no
2208 transactional state is established with the DBAPI until the first
2209 SQL statement is emitted).
2210
2211 Ambiguity in multi-bind or unbound :class:`.Session` objects can be
2212 resolved through any of the optional keyword arguments. This
2213 ultimately makes usage of the :meth:`.get_bind` method for resolution.
2214
2215 :param bind_arguments: dictionary of bind arguments. May include
2216 "mapper", "bind", "clause", other custom arguments that are passed
2217 to :meth:`.Session.get_bind`.
2218
2219 :param execution_options: a dictionary of execution options that will
2220 be passed to :meth:`_engine.Connection.execution_options`, **when the
2221 connection is first procured only**. If the connection is already
2222 present within the :class:`.Session`, a warning is emitted and
2223 the arguments are ignored.
2224
2225 .. seealso::
2226
2227 :ref:`session_transaction_isolation`
2228
2229 """
2230
2231 if bind_arguments:
2232 bind = bind_arguments.pop("bind", None)
2233
2234 if bind is None:
2235 bind = self.get_bind(**bind_arguments)
2236 else:
2237 bind = self.get_bind()
2238
2239 return self._connection_for_bind(
2240 bind,
2241 execution_options=execution_options,
2242 )
2243
2244 def _connection_for_bind(
2245 self,
2246 engine: _SessionBind,
2247 execution_options: Optional[CoreExecuteOptionsParameter] = None,
2248 **kw: Any,
2249 ) -> Connection:
2250 TransactionalContext._trans_ctx_check(self)
2251
2252 trans = self._transaction
2253 if trans is None:
2254 trans = self._autobegin_t()
2255 return trans._connection_for_bind(engine, execution_options)
2256
2257 @overload
2258 def _execute_internal(
2259 self,
2260 statement: Executable,
2261 params: Optional[_CoreSingleExecuteParams] = None,
2262 *,
2263 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2264 bind_arguments: Optional[_BindArguments] = None,
2265 _parent_execute_state: Optional[Any] = None,
2266 _add_event: Optional[Any] = None,
2267 _scalar_result: Literal[True] = ...,
2268 ) -> Any: ...
2269
2270 @overload
2271 def _execute_internal(
2272 self,
2273 statement: Executable,
2274 params: Optional[_CoreAnyExecuteParams] = None,
2275 *,
2276 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2277 bind_arguments: Optional[_BindArguments] = None,
2278 _parent_execute_state: Optional[Any] = None,
2279 _add_event: Optional[Any] = None,
2280 _scalar_result: bool = ...,
2281 ) -> Result[Unpack[TupleAny]]: ...
2282
2283 def _execute_internal(
2284 self,
2285 statement: Executable,
2286 params: Optional[_CoreAnyExecuteParams] = None,
2287 *,
2288 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2289 bind_arguments: Optional[_BindArguments] = None,
2290 _parent_execute_state: Optional[Any] = None,
2291 _add_event: Optional[Any] = None,
2292 _scalar_result: bool = False,
2293 ) -> Any:
2294 statement = coercions.expect(roles.StatementRole, statement)
2295
2296 if not bind_arguments:
2297 bind_arguments = {}
2298 else:
2299 bind_arguments = dict(bind_arguments)
2300
2301 if (
2302 statement._propagate_attrs.get("compile_state_plugin", None)
2303 == "orm"
2304 ):
2305 compile_state_cls = CompileState._get_plugin_class_for_plugin(
2306 statement, "orm"
2307 )
2308 if TYPE_CHECKING:
2309 assert isinstance(
2310 compile_state_cls, context._AbstractORMCompileState
2311 )
2312 else:
2313 compile_state_cls = None
2314 bind_arguments.setdefault("clause", statement)
2315
2316 combined_execution_options: util.immutabledict[str, Any] = (
2317 util.coerce_to_immutabledict(execution_options)
2318 )
2319 if self.execution_options:
2320 # merge given execution options with session-wide execution
2321 # options. if the statement also has execution_options,
2322 # maintain priority of session.execution_options ->
2323 # statement.execution_options -> method passed execution_options
2324 # by omitting from the base execution options those keys that
2325 # will come from the statement
2326 if statement._execution_options:
2327 combined_execution_options = util.immutabledict(
2328 {
2329 k: v
2330 for k, v in self.execution_options.items()
2331 if k not in statement._execution_options
2332 }
2333 ).union(combined_execution_options)
2334 else:
2335 combined_execution_options = self.execution_options.union(
2336 combined_execution_options
2337 )
2338
2339 if _parent_execute_state:
2340 events_todo = _parent_execute_state._remaining_events()
2341 else:
2342 events_todo = self.dispatch.do_orm_execute
2343 if _add_event:
2344 events_todo = list(events_todo) + [_add_event]
2345
2346 if events_todo:
2347 # save the original execution options before
2348 # orm_pre_session_exec processes them, so that we can pass
2349 # the unprocessed options (plus any explicit updates from event
2350 # hooks) to the second orm_pre_session_exec call. This
2351 # prevents internal state like _sa_orm_load_options and
2352 # yield_per from the first call leaking into the second call,
2353 # which would otherwise cause issues like yield_per incorrectly
2354 # propagating into post-load (selectinload etc.) queries.
2355 # part of #13301.
2356 original_execution_options = combined_execution_options
2357
2358 if compile_state_cls is not None:
2359 # for event handlers, do the orm_pre_session_exec
2360 # pass ahead of the event handlers, so that things like
2361 # .load_options, .update_delete_options etc. are populated.
2362 # is_pre_event=True allows the hook to hold off on things
2363 # it doesn't want to do twice, including autoflush as well
2364 # as "pre fetch" for DML, etc.
2365 (
2366 statement,
2367 combined_execution_options,
2368 params,
2369 ) = compile_state_cls.orm_pre_session_exec(
2370 self,
2371 statement,
2372 params,
2373 combined_execution_options,
2374 bind_arguments,
2375 True,
2376 )
2377
2378 orm_exec_state = ORMExecuteState(
2379 self,
2380 statement,
2381 params,
2382 combined_execution_options,
2383 bind_arguments,
2384 compile_state_cls,
2385 events_todo,
2386 )
2387 for idx, fn in enumerate(events_todo):
2388 orm_exec_state._starting_event_idx = idx
2389 fn_result: Optional[Result[Unpack[TupleAny]]] = fn(
2390 orm_exec_state
2391 )
2392 if fn_result:
2393 if _scalar_result:
2394 return fn_result.scalar()
2395 else:
2396 return fn_result
2397
2398 statement = orm_exec_state.statement
2399 params = orm_exec_state.parameters
2400
2401 # use the original execution options plus only the explicit
2402 # updates from event hooks, not the processed options from
2403 # the first orm_pre_session_exec call
2404 combined_execution_options = original_execution_options.union(
2405 orm_exec_state._update_execution_options
2406 )
2407
2408 if compile_state_cls is not None:
2409 # now run orm_pre_session_exec() "for real". if there were
2410 # event hooks, this will re-run the steps that interpret
2411 # new execution_options into load_options / update_delete_options,
2412 # which we assume the event hook might have updated.
2413 # autoflush will also be invoked in this step if enabled.
2414 (
2415 statement,
2416 combined_execution_options,
2417 params,
2418 ) = compile_state_cls.orm_pre_session_exec(
2419 self,
2420 statement,
2421 params,
2422 combined_execution_options,
2423 bind_arguments,
2424 False,
2425 )
2426 else:
2427 # Issue #9809: unconditionally autoflush for Core statements
2428 self._autoflush()
2429
2430 bind = self.get_bind(**bind_arguments)
2431
2432 conn = self._connection_for_bind(bind)
2433
2434 if _scalar_result and not compile_state_cls:
2435 if TYPE_CHECKING:
2436 params = cast(_CoreSingleExecuteParams, params)
2437 return conn.scalar(
2438 statement,
2439 params or {},
2440 execution_options=combined_execution_options,
2441 )
2442
2443 if compile_state_cls:
2444 result: Result[Unpack[TupleAny]] = (
2445 compile_state_cls.orm_execute_statement(
2446 self,
2447 statement,
2448 params or {},
2449 combined_execution_options,
2450 bind_arguments,
2451 conn,
2452 )
2453 )
2454 else:
2455 result = conn.execute(
2456 statement, params, execution_options=combined_execution_options
2457 )
2458
2459 if _scalar_result:
2460 return result.scalar()
2461 else:
2462 return result
2463
2464 @overload
2465 def execute(
2466 self,
2467 statement: TypedReturnsRows[Unpack[_Ts]],
2468 params: Optional[_CoreAnyExecuteParams] = None,
2469 *,
2470 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2471 bind_arguments: Optional[_BindArguments] = None,
2472 _parent_execute_state: Optional[Any] = None,
2473 _add_event: Optional[Any] = None,
2474 ) -> Result[Unpack[_Ts]]: ...
2475
2476 @overload
2477 def execute(
2478 self,
2479 statement: Executable,
2480 params: Optional[_CoreAnyExecuteParams] = None,
2481 *,
2482 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2483 bind_arguments: Optional[_BindArguments] = None,
2484 _parent_execute_state: Optional[Any] = None,
2485 _add_event: Optional[Any] = None,
2486 ) -> Result[Unpack[TupleAny]]: ...
2487
2488 def execute(
2489 self,
2490 statement: Executable,
2491 params: Optional[_CoreAnyExecuteParams] = None,
2492 *,
2493 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2494 bind_arguments: Optional[_BindArguments] = None,
2495 _parent_execute_state: Optional[Any] = None,
2496 _add_event: Optional[Any] = None,
2497 ) -> Result[Unpack[TupleAny]]:
2498 r"""Execute a SQL expression construct.
2499
2500 Returns a :class:`_engine.Result` object representing
2501 results of the statement execution.
2502
2503 E.g.::
2504
2505 from sqlalchemy import select
2506
2507 result = session.execute(select(User).where(User.id == 5))
2508
2509 The API contract of :meth:`_orm.Session.execute` is similar to that
2510 of :meth:`_engine.Connection.execute`, the :term:`2.0 style` version
2511 of :class:`_engine.Connection`.
2512
2513 .. versionchanged:: 1.4 the :meth:`_orm.Session.execute` method is
2514 now the primary point of ORM statement execution when using
2515 :term:`2.0 style` ORM usage.
2516
2517 :param statement:
2518 An executable statement (i.e. an :class:`.Executable` expression
2519 such as :func:`_expression.select`).
2520
2521 :param params:
2522 Optional dictionary, or list of dictionaries, containing
2523 bound parameter values. If a single dictionary, single-row
2524 execution occurs; if a list of dictionaries, an
2525 "executemany" will be invoked. The keys in each dictionary
2526 must correspond to parameter names present in the statement.
2527
2528 :param execution_options: optional dictionary of execution options,
2529 which will be associated with the statement execution. This
2530 dictionary can provide a subset of the options that are accepted
2531 by :meth:`_engine.Connection.execution_options`, and may also
2532 provide additional options understood only in an ORM context.
2533
2534 The execution_options are passed along to methods like
2535 :meth:`.Connection.execute` on :class:`.Connection` giving the
2536 highest priority to execution_options that are passed to this
2537 method explicitly, then the options that are present on the
2538 statement object if any, and finally those options present
2539 session-wide.
2540
2541 .. seealso::
2542
2543 :ref:`orm_queryguide_execution_options` - ORM-specific execution
2544 options
2545
2546 :param bind_arguments: dictionary of additional arguments to determine
2547 the bind. May include "mapper", "bind", or other custom arguments.
2548 Contents of this dictionary are passed to the
2549 :meth:`.Session.get_bind` method.
2550
2551 :return: a :class:`_engine.Result` object.
2552
2553
2554 """
2555 return self._execute_internal(
2556 statement,
2557 params,
2558 execution_options=execution_options,
2559 bind_arguments=bind_arguments,
2560 _parent_execute_state=_parent_execute_state,
2561 _add_event=_add_event,
2562 )
2563
2564 # special case to handle mypy issue:
2565 # https://github.com/python/mypy/issues/20651
2566 @overload
2567 def scalar(
2568 self,
2569 statement: TypedReturnsRows[Never],
2570 params: Optional[_CoreSingleExecuteParams] = None,
2571 *,
2572 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2573 bind_arguments: Optional[_BindArguments] = None,
2574 **kw: Any,
2575 ) -> Optional[Any]: ...
2576
2577 @overload
2578 def scalar(
2579 self,
2580 statement: TypedReturnsRows[_T],
2581 params: Optional[_CoreSingleExecuteParams] = None,
2582 *,
2583 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2584 bind_arguments: Optional[_BindArguments] = None,
2585 **kw: Any,
2586 ) -> Optional[_T]: ...
2587
2588 @overload
2589 def scalar(
2590 self,
2591 statement: Executable,
2592 params: Optional[_CoreSingleExecuteParams] = None,
2593 *,
2594 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2595 bind_arguments: Optional[_BindArguments] = None,
2596 **kw: Any,
2597 ) -> Any: ...
2598
2599 def scalar(
2600 self,
2601 statement: Executable,
2602 params: Optional[_CoreSingleExecuteParams] = None,
2603 *,
2604 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2605 bind_arguments: Optional[_BindArguments] = None,
2606 **kw: Any,
2607 ) -> Any:
2608 """Execute a statement and return a scalar result.
2609
2610 Usage and parameters are the same as that of
2611 :meth:`_orm.Session.execute`; the return result is a scalar Python
2612 value.
2613
2614 """
2615
2616 return self._execute_internal(
2617 statement,
2618 params,
2619 execution_options=execution_options,
2620 bind_arguments=bind_arguments,
2621 _scalar_result=True,
2622 **kw,
2623 )
2624
2625 @overload
2626 def scalars(
2627 self,
2628 statement: TypedReturnsRows[_T],
2629 params: Optional[_CoreAnyExecuteParams] = None,
2630 *,
2631 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2632 bind_arguments: Optional[_BindArguments] = None,
2633 **kw: Any,
2634 ) -> ScalarResult[_T]: ...
2635
2636 @overload
2637 def scalars(
2638 self,
2639 statement: Executable,
2640 params: Optional[_CoreAnyExecuteParams] = None,
2641 *,
2642 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2643 bind_arguments: Optional[_BindArguments] = None,
2644 **kw: Any,
2645 ) -> ScalarResult[Any]: ...
2646
2647 def scalars(
2648 self,
2649 statement: Executable,
2650 params: Optional[_CoreAnyExecuteParams] = None,
2651 *,
2652 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2653 bind_arguments: Optional[_BindArguments] = None,
2654 **kw: Any,
2655 ) -> ScalarResult[Any]:
2656 """Execute a statement and return the results as scalars.
2657
2658 Usage and parameters are the same as that of
2659 :meth:`_orm.Session.execute`; the return result is a
2660 :class:`_result.ScalarResult` filtering object which
2661 will return single elements rather than :class:`_row.Row` objects.
2662
2663 :return: a :class:`_result.ScalarResult` object
2664
2665 .. versionadded:: 1.4.24 Added :meth:`_orm.Session.scalars`
2666
2667 .. versionadded:: 1.4.26 Added :meth:`_orm.scoped_session.scalars`
2668
2669 .. seealso::
2670
2671 :ref:`orm_queryguide_select_orm_entities` - contrasts the behavior
2672 of :meth:`_orm.Session.execute` to :meth:`_orm.Session.scalars`
2673
2674 """
2675
2676 return self._execute_internal(
2677 statement,
2678 params=params,
2679 execution_options=execution_options,
2680 bind_arguments=bind_arguments,
2681 _scalar_result=False, # mypy appreciates this
2682 **kw,
2683 ).scalars()
2684
2685 def close(self) -> None:
2686 """Close out the transactional resources and ORM objects used by this
2687 :class:`_orm.Session`.
2688
2689 This expunges all ORM objects associated with this
2690 :class:`_orm.Session`, ends any transaction in progress and
2691 :term:`releases` any :class:`_engine.Connection` objects which this
2692 :class:`_orm.Session` itself has checked out from associated
2693 :class:`_engine.Engine` objects. The operation then leaves the
2694 :class:`_orm.Session` in a state which it may be used again.
2695
2696 .. tip::
2697
2698 In the default running mode the :meth:`_orm.Session.close`
2699 method **does not prevent the Session from being used again**.
2700 The :class:`_orm.Session` itself does not actually have a
2701 distinct "closed" state; it merely means
2702 the :class:`_orm.Session` will release all database connections
2703 and ORM objects.
2704
2705 Setting the parameter :paramref:`_orm.Session.close_resets_only`
2706 to ``False`` will instead make the ``close`` final, meaning that
2707 any further action on the session will be forbidden.
2708
2709 .. versionchanged:: 1.4 The :meth:`.Session.close` method does not
2710 immediately create a new :class:`.SessionTransaction` object;
2711 instead, the new :class:`.SessionTransaction` is created only if
2712 the :class:`.Session` is used again for a database operation.
2713
2714 .. seealso::
2715
2716 :ref:`session_closing` - detail on the semantics of
2717 :meth:`_orm.Session.close` and :meth:`_orm.Session.reset`.
2718
2719 :meth:`_orm.Session.reset` - a similar method that behaves like
2720 ``close()`` with the parameter
2721 :paramref:`_orm.Session.close_resets_only` set to ``True``.
2722
2723 """
2724 self._close_impl(invalidate=False)
2725
2726 def reset(self) -> None:
2727 """Close out the transactional resources and ORM objects used by this
2728 :class:`_orm.Session`, resetting the session to its initial state.
2729
2730 This method provides for same "reset-only" behavior that the
2731 :meth:`_orm.Session.close` method has provided historically, where the
2732 state of the :class:`_orm.Session` is reset as though the object were
2733 brand new, and ready to be used again.
2734 This method may then be useful for :class:`_orm.Session` objects
2735 which set :paramref:`_orm.Session.close_resets_only` to ``False``,
2736 so that "reset only" behavior is still available.
2737
2738 .. versionadded:: 2.0.22
2739
2740 .. seealso::
2741
2742 :ref:`session_closing` - detail on the semantics of
2743 :meth:`_orm.Session.close` and :meth:`_orm.Session.reset`.
2744
2745 :meth:`_orm.Session.close` - a similar method will additionally
2746 prevent reuse of the Session when the parameter
2747 :paramref:`_orm.Session.close_resets_only` is set to ``False``.
2748 """
2749 self._close_impl(invalidate=False, is_reset=True)
2750
2751 def invalidate(self) -> None:
2752 """Close this Session, using connection invalidation.
2753
2754 This is a variant of :meth:`.Session.close` that will additionally
2755 ensure that the :meth:`_engine.Connection.invalidate`
2756 method will be called on each :class:`_engine.Connection` object
2757 that is currently in use for a transaction (typically there is only
2758 one connection unless the :class:`_orm.Session` is used with
2759 multiple engines).
2760
2761 This can be called when the database is known to be in a state where
2762 the connections are no longer safe to be used.
2763
2764 Below illustrates a scenario when using `gevent
2765 <https://www.gevent.org/>`_, which can produce ``Timeout`` exceptions
2766 that may mean the underlying connection should be discarded::
2767
2768 import gevent
2769
2770 try:
2771 sess = Session()
2772 sess.add(User())
2773 sess.commit()
2774 except gevent.Timeout:
2775 sess.invalidate()
2776 raise
2777 except:
2778 sess.rollback()
2779 raise
2780
2781 The method additionally does everything that :meth:`_orm.Session.close`
2782 does, including that all ORM objects are expunged.
2783
2784 """
2785 self._close_impl(invalidate=True)
2786
2787 def _close_impl(self, invalidate: bool, is_reset: bool = False) -> None:
2788 if not is_reset and self._close_state is _SessionCloseState.ACTIVE:
2789 self._close_state = _SessionCloseState.CLOSED
2790 self.expunge_all()
2791 if self._transaction is not None:
2792 for transaction in self._transaction._iterate_self_and_parents():
2793 transaction.close(invalidate)
2794
2795 def expunge_all(self) -> None:
2796 """Remove all object instances from this ``Session``.
2797
2798 This is equivalent to calling ``expunge(obj)`` on all objects in this
2799 ``Session``.
2800
2801 """
2802
2803 all_states = self.identity_map.all_states() + list(self._new)
2804 self.identity_map._kill()
2805 self.identity_map = identity._WeakInstanceDict()
2806 self._new = {}
2807 self._deleted = {}
2808
2809 statelib.InstanceState._detach_states(all_states, self)
2810
2811 def _add_bind(self, key: _SessionBindKey, bind: _SessionBind) -> None:
2812 new_binds: Dict[_SessionBindKey, _SessionBind] = {}
2813
2814 try:
2815 insp = inspect(key)
2816 except sa_exc.NoInspectionAvailable as err:
2817 if not isinstance(key, type):
2818 raise sa_exc.ArgumentError(
2819 "Not an acceptable bind target: %s" % key
2820 ) from err
2821 else:
2822 new_binds[key] = bind
2823 else:
2824 if TYPE_CHECKING:
2825 assert isinstance(insp, Inspectable)
2826
2827 if isinstance(insp, TableClause):
2828 new_binds[insp] = bind
2829 elif insp_is_mapper(insp):
2830 new_binds[insp.class_] = bind
2831 for _selectable in insp._all_tables:
2832 new_binds[_selectable] = bind
2833 else:
2834 raise sa_exc.ArgumentError(
2835 "Not an acceptable bind target: %s" % key
2836 )
2837
2838 self.binds = self.binds.merge_with( # type: ignore[attr-defined]
2839 new_binds
2840 )
2841
2842 def bind_mapper(
2843 self, mapper: _EntityBindKey[_O], bind: _SessionBind
2844 ) -> None:
2845 """Associate a :class:`_orm.Mapper` or arbitrary Python class with a
2846 "bind", e.g. an :class:`_engine.Engine` or
2847 :class:`_engine.Connection`.
2848
2849 The given entity is added to a lookup used by the
2850 :meth:`.Session.get_bind` method.
2851
2852 :param mapper: a :class:`_orm.Mapper` object,
2853 or an instance of a mapped
2854 class, or any Python class that is the base of a set of mapped
2855 classes.
2856
2857 :param bind: an :class:`_engine.Engine` or :class:`_engine.Connection`
2858 object.
2859
2860 .. seealso::
2861
2862 :ref:`session_partitioning`
2863
2864 :paramref:`.Session.binds`
2865
2866 :meth:`.Session.bind_table`
2867
2868
2869 """
2870 self._add_bind(mapper, bind)
2871
2872 def bind_table(self, table: TableClause, bind: _SessionBind) -> None:
2873 """Associate a :class:`_schema.Table` with a "bind", e.g. an
2874 :class:`_engine.Engine`
2875 or :class:`_engine.Connection`.
2876
2877 The given :class:`_schema.Table` is added to a lookup used by the
2878 :meth:`.Session.get_bind` method.
2879
2880 :param table: a :class:`_schema.Table` object,
2881 which is typically the target
2882 of an ORM mapping, or is present within a selectable that is
2883 mapped.
2884
2885 :param bind: an :class:`_engine.Engine` or :class:`_engine.Connection`
2886 object.
2887
2888 .. seealso::
2889
2890 :ref:`session_partitioning`
2891
2892 :paramref:`.Session.binds`
2893
2894 :meth:`.Session.bind_mapper`
2895
2896
2897 """
2898 self._add_bind(table, bind)
2899
2900 def get_bind(
2901 self,
2902 mapper: Optional[_EntityBindKey[_O]] = None,
2903 *,
2904 clause: Optional[ClauseElement] = None,
2905 bind: Optional[_SessionBind] = None,
2906 _sa_skip_events: Optional[bool] = None,
2907 _sa_skip_for_implicit_returning: bool = False,
2908 **kw: Any,
2909 ) -> Union[Engine, Connection]:
2910 """Return a "bind" to which this :class:`.Session` is bound.
2911
2912 The "bind" is usually an instance of :class:`_engine.Engine`,
2913 except in the case where the :class:`.Session` has been
2914 explicitly bound directly to a :class:`_engine.Connection`.
2915
2916 For a multiply-bound or unbound :class:`.Session`, the
2917 ``mapper`` or ``clause`` arguments are used to determine the
2918 appropriate bind to return.
2919
2920 Note that the "mapper" argument is usually present
2921 when :meth:`.Session.get_bind` is called via an ORM
2922 operation such as a :meth:`.Session.query`, each
2923 individual INSERT/UPDATE/DELETE operation within a
2924 :meth:`.Session.flush`, call, etc.
2925
2926 The order of resolution is:
2927
2928 1. if mapper given and :paramref:`.Session.binds` is present,
2929 locate a bind based first on the mapper in use, then
2930 on the mapped class in use, then on any base classes that are
2931 present in the ``__mro__`` of the mapped class, from more specific
2932 superclasses to more general.
2933 2. if clause given and ``Session.binds`` is present,
2934 locate a bind based on :class:`_schema.Table` objects
2935 found in the given clause present in ``Session.binds``.
2936 3. if ``Session.binds`` is present, return that.
2937 4. if clause given, attempt to return a bind
2938 linked to the :class:`_schema.MetaData` ultimately
2939 associated with the clause.
2940 5. if mapper given, attempt to return a bind
2941 linked to the :class:`_schema.MetaData` ultimately
2942 associated with the :class:`_schema.Table` or other
2943 selectable to which the mapper is mapped.
2944 6. No bind can be found, :exc:`~sqlalchemy.exc.UnboundExecutionError`
2945 is raised.
2946
2947 Note that the :meth:`.Session.get_bind` method can be overridden on
2948 a user-defined subclass of :class:`.Session` to provide any kind
2949 of bind resolution scheme. See the example at
2950 :ref:`session_custom_partitioning`.
2951
2952 :param mapper:
2953 Optional mapped class or corresponding :class:`_orm.Mapper` instance.
2954 The bind can be derived from a :class:`_orm.Mapper` first by
2955 consulting the "binds" map associated with this :class:`.Session`,
2956 and secondly by consulting the :class:`_schema.MetaData` associated
2957 with the :class:`_schema.Table` to which the :class:`_orm.Mapper` is
2958 mapped for a bind.
2959
2960 :param clause:
2961 A :class:`_expression.ClauseElement` (i.e.
2962 :func:`_expression.select`,
2963 :func:`_expression.text`,
2964 etc.). If the ``mapper`` argument is not present or could not
2965 produce a bind, the given expression construct will be searched
2966 for a bound element, typically a :class:`_schema.Table`
2967 associated with
2968 bound :class:`_schema.MetaData`.
2969
2970 .. seealso::
2971
2972 :ref:`session_partitioning`
2973
2974 :paramref:`.Session.binds`
2975
2976 :meth:`.Session.bind_mapper`
2977
2978 :meth:`.Session.bind_table`
2979
2980 """
2981
2982 # this function is documented as a subclassing hook, so we have
2983 # to call this method even if the return is simple
2984 if bind:
2985 return bind
2986 elif not self.binds and self.bind:
2987 # simplest and most common case, we have a bind and no
2988 # per-mapper/table binds, we're done
2989 return self.bind
2990
2991 # we don't have self.bind and either have self.binds
2992 # or we don't have self.binds (which is legacy). Look at the
2993 # mapper and the clause
2994 if mapper is None and clause is None:
2995 if self.bind:
2996 return self.bind
2997 else:
2998 raise sa_exc.UnboundExecutionError(
2999 "This session is not bound to a single Engine or "
3000 "Connection, and no context was provided to locate "
3001 "a binding."
3002 )
3003
3004 # look more closely at the mapper.
3005 if mapper is not None:
3006 try:
3007 inspected_mapper = inspect(mapper)
3008 except sa_exc.NoInspectionAvailable as err:
3009 if isinstance(mapper, type):
3010 raise exc.UnmappedClassError(mapper) from err
3011 else:
3012 raise
3013 else:
3014 inspected_mapper = None
3015
3016 # match up the mapper or clause in the binds
3017 if self.binds:
3018 # matching mappers and selectables to entries in the
3019 # binds dictionary; supported use case.
3020 if inspected_mapper:
3021 for cls in inspected_mapper.class_.__mro__:
3022 if cls in self.binds:
3023 return self.binds[cls]
3024 if clause is None:
3025 clause = inspected_mapper.persist_selectable
3026
3027 if clause is not None:
3028 plugin_subject = clause._propagate_attrs.get(
3029 "plugin_subject", None
3030 )
3031
3032 if plugin_subject is not None:
3033 for cls in plugin_subject.mapper.class_.__mro__:
3034 if cls in self.binds:
3035 return self.binds[cls]
3036
3037 for obj in visitors.iterate(clause):
3038 if obj in self.binds:
3039 if TYPE_CHECKING:
3040 assert isinstance(obj, Table)
3041 return self.binds[obj]
3042
3043 # none of the binds matched, but we have a fallback bind.
3044 # return that
3045 if self.bind:
3046 return self.bind
3047
3048 context = []
3049 if inspected_mapper is not None:
3050 context.append(f"mapper {inspected_mapper}")
3051 if clause is not None:
3052 context.append("SQL expression")
3053
3054 raise sa_exc.UnboundExecutionError(
3055 f"Could not locate a bind configured on "
3056 f'{", ".join(context)} or this Session.'
3057 )
3058
3059 @overload
3060 def query(self, _entity: _EntityType[_O]) -> Query[_O]: ...
3061
3062 @overload
3063 def query(
3064 self, _colexpr: TypedColumnsClauseRole[_T]
3065 ) -> RowReturningQuery[_T]: ...
3066
3067 # START OVERLOADED FUNCTIONS self.query RowReturningQuery 2-8
3068
3069 # code within this block is **programmatically,
3070 # statically generated** by tools/generate_tuple_map_overloads.py
3071
3072 @overload
3073 def query(
3074 self, __ent0: _TCCA[_T0], __ent1: _TCCA[_T1], /
3075 ) -> RowReturningQuery[_T0, _T1]: ...
3076
3077 @overload
3078 def query(
3079 self, __ent0: _TCCA[_T0], __ent1: _TCCA[_T1], __ent2: _TCCA[_T2], /
3080 ) -> RowReturningQuery[_T0, _T1, _T2]: ...
3081
3082 @overload
3083 def query(
3084 self,
3085 __ent0: _TCCA[_T0],
3086 __ent1: _TCCA[_T1],
3087 __ent2: _TCCA[_T2],
3088 __ent3: _TCCA[_T3],
3089 /,
3090 ) -> RowReturningQuery[_T0, _T1, _T2, _T3]: ...
3091
3092 @overload
3093 def query(
3094 self,
3095 __ent0: _TCCA[_T0],
3096 __ent1: _TCCA[_T1],
3097 __ent2: _TCCA[_T2],
3098 __ent3: _TCCA[_T3],
3099 __ent4: _TCCA[_T4],
3100 /,
3101 ) -> RowReturningQuery[_T0, _T1, _T2, _T3, _T4]: ...
3102
3103 @overload
3104 def query(
3105 self,
3106 __ent0: _TCCA[_T0],
3107 __ent1: _TCCA[_T1],
3108 __ent2: _TCCA[_T2],
3109 __ent3: _TCCA[_T3],
3110 __ent4: _TCCA[_T4],
3111 __ent5: _TCCA[_T5],
3112 /,
3113 ) -> RowReturningQuery[_T0, _T1, _T2, _T3, _T4, _T5]: ...
3114
3115 @overload
3116 def query(
3117 self,
3118 __ent0: _TCCA[_T0],
3119 __ent1: _TCCA[_T1],
3120 __ent2: _TCCA[_T2],
3121 __ent3: _TCCA[_T3],
3122 __ent4: _TCCA[_T4],
3123 __ent5: _TCCA[_T5],
3124 __ent6: _TCCA[_T6],
3125 /,
3126 ) -> RowReturningQuery[_T0, _T1, _T2, _T3, _T4, _T5, _T6]: ...
3127
3128 @overload
3129 def query(
3130 self,
3131 __ent0: _TCCA[_T0],
3132 __ent1: _TCCA[_T1],
3133 __ent2: _TCCA[_T2],
3134 __ent3: _TCCA[_T3],
3135 __ent4: _TCCA[_T4],
3136 __ent5: _TCCA[_T5],
3137 __ent6: _TCCA[_T6],
3138 __ent7: _TCCA[_T7],
3139 /,
3140 *entities: _ColumnsClauseArgument[Any],
3141 ) -> RowReturningQuery[
3142 _T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7, Unpack[TupleAny]
3143 ]: ...
3144
3145 # END OVERLOADED FUNCTIONS self.query
3146
3147 @overload
3148 def query(
3149 self, *entities: _ColumnsClauseArgument[Any], **kwargs: Any
3150 ) -> Query[Any]: ...
3151
3152 def query(
3153 self, *entities: _ColumnsClauseArgument[Any], **kwargs: Any
3154 ) -> Query[Any]:
3155 """Return a new :class:`_query.Query` object corresponding to this
3156 :class:`_orm.Session`.
3157
3158 Note that the :class:`_query.Query` object is legacy as of
3159 SQLAlchemy 2.0; the :func:`_sql.select` construct is now used
3160 to construct ORM queries.
3161
3162 .. seealso::
3163
3164 :ref:`unified_tutorial`
3165
3166 :ref:`queryguide_toplevel`
3167
3168 :ref:`query_api_toplevel` - legacy API doc
3169
3170 """
3171
3172 return self._query_cls(entities, self, **kwargs)
3173
3174 def _identity_lookup(
3175 self,
3176 mapper: Mapper[_O],
3177 primary_key_identity: Union[Any, Tuple[Any, ...]],
3178 identity_token: Any = None,
3179 passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
3180 lazy_loaded_from: Optional[InstanceState[Any]] = None,
3181 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
3182 bind_arguments: Optional[_BindArguments] = None,
3183 ) -> Union[Optional[_O], LoaderCallableStatus]:
3184 """Locate an object in the identity map.
3185
3186 Given a primary key identity, constructs an identity key and then
3187 looks in the session's identity map. If present, the object may
3188 be run through unexpiration rules (e.g. load unloaded attributes,
3189 check if was deleted).
3190
3191 e.g.::
3192
3193 obj = session._identity_lookup(inspect(SomeClass), (1,))
3194
3195 :param mapper: mapper in use
3196 :param primary_key_identity: the primary key we are searching for, as
3197 a tuple.
3198 :param identity_token: identity token that should be used to create
3199 the identity key. Used as is, however overriding subclasses can
3200 repurpose this in order to interpret the value in a special way,
3201 such as if None then look among multiple target tokens.
3202 :param passive: passive load flag passed to
3203 :func:`.loading.get_from_identity`, which impacts the behavior if
3204 the object is found; the object may be validated and/or unexpired
3205 if the flag allows for SQL to be emitted.
3206 :param lazy_loaded_from: an :class:`.InstanceState` that is
3207 specifically asking for this identity as a related identity. Used
3208 for sharding schemes where there is a correspondence between an object
3209 and a related object being lazy-loaded (or otherwise
3210 relationship-loaded).
3211
3212 :return: None if the object is not found in the identity map, *or*
3213 if the object was unexpired and found to have been deleted.
3214 if passive flags disallow SQL and the object is expired, returns
3215 PASSIVE_NO_RESULT. In all other cases the instance is returned.
3216
3217 .. versionchanged:: 1.4.0 - the :meth:`.Session._identity_lookup`
3218 method was moved from :class:`_query.Query` to
3219 :class:`.Session`, to avoid having to instantiate the
3220 :class:`_query.Query` object.
3221
3222
3223 """
3224
3225 key = mapper.identity_key_from_primary_key(
3226 primary_key_identity, identity_token=identity_token
3227 )
3228
3229 # work around: https://github.com/python/typing/discussions/1143
3230 return_value = loading.get_from_identity(self, mapper, key, passive)
3231 return return_value
3232
3233 @util.non_memoized_property
3234 @contextlib.contextmanager
3235 def no_autoflush(self) -> Iterator[Session]:
3236 """Return a context manager that disables autoflush.
3237
3238 e.g.::
3239
3240 with session.no_autoflush:
3241
3242 some_object = SomeClass()
3243 session.add(some_object)
3244 # won't autoflush
3245 some_object.related_thing = session.query(SomeRelated).first()
3246
3247 Operations that proceed within the ``with:`` block
3248 will not be subject to flushes occurring upon query
3249 access. This is useful when initializing a series
3250 of objects which involve existing database queries,
3251 where the uncompleted object should not yet be flushed.
3252
3253 """
3254 autoflush = self.autoflush
3255 self.autoflush = False
3256 try:
3257 yield self
3258 finally:
3259 self.autoflush = autoflush
3260
3261 @util.langhelpers.tag_method_for_warnings(
3262 "This warning originated from the Session 'autoflush' process, "
3263 "which was invoked automatically in response to a user-initiated "
3264 "operation. Consider using ``no_autoflush`` context manager if this "
3265 "warning happened while initializing objects.",
3266 sa_exc.SAWarning,
3267 )
3268 def _autoflush(self) -> None:
3269 if self.autoflush and not self._flushing:
3270 try:
3271 self.flush()
3272 except sa_exc.StatementError as e:
3273 # note we are reraising StatementError as opposed to
3274 # raising FlushError with "chaining" to remain compatible
3275 # with code that catches StatementError, IntegrityError,
3276 # etc.
3277 e.add_detail(
3278 "raised as a result of Query-invoked autoflush; "
3279 "consider using a session.no_autoflush block if this "
3280 "flush is occurring prematurely"
3281 )
3282 raise e.with_traceback(sys.exc_info()[2])
3283
3284 def refresh(
3285 self,
3286 instance: object,
3287 attribute_names: Optional[Iterable[str]] = None,
3288 with_for_update: ForUpdateParameter = None,
3289 ) -> None:
3290 """Expire and refresh attributes on the given instance.
3291
3292 The selected attributes will first be expired as they would when using
3293 :meth:`_orm.Session.expire`; then a SELECT statement will be issued to
3294 the database to refresh column-oriented attributes with the current
3295 value available in the current transaction.
3296
3297 :func:`_orm.relationship` oriented attributes will also be immediately
3298 loaded if they were already eagerly loaded on the object, using the
3299 same eager loading strategy that they were loaded with originally.
3300
3301 .. versionadded:: 1.4 - the :meth:`_orm.Session.refresh` method
3302 can also refresh eagerly loaded attributes.
3303
3304 :func:`_orm.relationship` oriented attributes that would normally
3305 load using the ``select`` (or "lazy") loader strategy will also
3306 load **if they are named explicitly in the attribute_names
3307 collection**, emitting a SELECT statement for the attribute using the
3308 ``immediate`` loader strategy. If lazy-loaded relationships are not
3309 named in :paramref:`_orm.Session.refresh.attribute_names`, then
3310 they remain as "lazy loaded" attributes and are not implicitly
3311 refreshed.
3312
3313 .. versionchanged:: 2.0.4 The :meth:`_orm.Session.refresh` method
3314 will now refresh lazy-loaded :func:`_orm.relationship` oriented
3315 attributes for those which are named explicitly in the
3316 :paramref:`_orm.Session.refresh.attribute_names` collection.
3317
3318 .. tip::
3319
3320 While the :meth:`_orm.Session.refresh` method is capable of
3321 refreshing both column and relationship oriented attributes, its
3322 primary focus is on refreshing of local column-oriented attributes
3323 on a single instance. For more open ended "refresh" functionality,
3324 including the ability to refresh the attributes on many objects at
3325 once while having explicit control over relationship loader
3326 strategies, use the
3327 :ref:`populate existing <orm_queryguide_populate_existing>` feature
3328 instead.
3329
3330 Note that a highly isolated transaction will return the same values as
3331 were previously read in that same transaction, regardless of changes
3332 in database state outside of that transaction. Refreshing
3333 attributes usually only makes sense at the start of a transaction
3334 where database rows have not yet been accessed.
3335
3336 :param attribute_names: optional. An iterable collection of
3337 string attribute names indicating a subset of attributes to
3338 be refreshed.
3339
3340 :param with_for_update: optional boolean ``True`` indicating FOR UPDATE
3341 should be used, or may be a dictionary containing flags to
3342 indicate a more specific set of FOR UPDATE flags for the SELECT;
3343 flags should match the parameters of
3344 :meth:`_query.Query.with_for_update`.
3345 Supersedes the :paramref:`.Session.refresh.lockmode` parameter.
3346
3347 .. seealso::
3348
3349 :ref:`session_expire` - introductory material
3350
3351 :meth:`.Session.expire`
3352
3353 :meth:`.Session.expire_all`
3354
3355 :ref:`orm_queryguide_populate_existing` - allows any ORM query
3356 to refresh objects as they would be loaded normally.
3357
3358 """
3359 try:
3360 state = attributes.instance_state(instance)
3361 except exc.NO_STATE as err:
3362 raise exc.UnmappedInstanceError(instance) from err
3363
3364 self._expire_state(state, attribute_names)
3365
3366 # this autoflush previously used to occur as a secondary effect
3367 # of the load_on_ident below. Meaning we'd organize the SELECT
3368 # based on current DB pks, then flush, then if pks changed in that
3369 # flush, crash. this was unticketed but discovered as part of
3370 # #8703. So here, autoflush up front, dont autoflush inside
3371 # load_on_ident.
3372 self._autoflush()
3373
3374 if with_for_update == {}:
3375 raise sa_exc.ArgumentError(
3376 "with_for_update should be the boolean value "
3377 "True, or a dictionary with options. "
3378 "A blank dictionary is ambiguous."
3379 )
3380
3381 with_for_update = ForUpdateArg._from_argument(with_for_update)
3382
3383 stmt: Select[Unpack[TupleAny]] = sql.select(object_mapper(instance))
3384 if (
3385 loading._load_on_ident(
3386 self,
3387 stmt,
3388 state.key,
3389 refresh_state=state,
3390 with_for_update=with_for_update,
3391 only_load_props=attribute_names,
3392 require_pk_cols=True,
3393 # technically unnecessary as we just did autoflush
3394 # above, however removes the additional unnecessary
3395 # call to _autoflush()
3396 no_autoflush=True,
3397 is_user_refresh=True,
3398 )
3399 is None
3400 ):
3401 raise sa_exc.InvalidRequestError(
3402 "Could not refresh instance '%s'" % instance_str(instance)
3403 )
3404
3405 def expire_all(self) -> None:
3406 """Expires all persistent instances within this Session.
3407
3408 When any attributes on a persistent instance is next accessed,
3409 a query will be issued using the
3410 :class:`.Session` object's current transactional context in order to
3411 load all expired attributes for the given instance. Note that
3412 a highly isolated transaction will return the same values as were
3413 previously read in that same transaction, regardless of changes
3414 in database state outside of that transaction.
3415
3416 To expire individual objects and individual attributes
3417 on those objects, use :meth:`Session.expire`.
3418
3419 The :class:`.Session` object's default behavior is to
3420 expire all state whenever the :meth:`Session.rollback`
3421 or :meth:`Session.commit` methods are called, so that new
3422 state can be loaded for the new transaction. For this reason,
3423 calling :meth:`Session.expire_all` is not usually needed,
3424 assuming the transaction is isolated.
3425
3426 .. seealso::
3427
3428 :ref:`session_expire` - introductory material
3429
3430 :meth:`.Session.expire`
3431
3432 :meth:`.Session.refresh`
3433
3434 :meth:`_orm.Query.populate_existing`
3435
3436 """
3437 for state in self.identity_map.all_states():
3438 state._expire(state.dict, self.identity_map._modified)
3439
3440 def expire(
3441 self, instance: object, attribute_names: Optional[Iterable[str]] = None
3442 ) -> None:
3443 """Expire the attributes on an instance.
3444
3445 Marks the attributes of an instance as out of date. When an expired
3446 attribute is next accessed, a query will be issued to the
3447 :class:`.Session` object's current transactional context in order to
3448 load all expired attributes for the given instance. Note that
3449 a highly isolated transaction will return the same values as were
3450 previously read in that same transaction, regardless of changes
3451 in database state outside of that transaction.
3452
3453 To expire all objects in the :class:`.Session` simultaneously,
3454 use :meth:`Session.expire_all`.
3455
3456 The :class:`.Session` object's default behavior is to
3457 expire all state whenever the :meth:`Session.rollback`
3458 or :meth:`Session.commit` methods are called, so that new
3459 state can be loaded for the new transaction. For this reason,
3460 calling :meth:`Session.expire` only makes sense for the specific
3461 case that a non-ORM SQL statement was emitted in the current
3462 transaction.
3463
3464 :param instance: The instance to be refreshed.
3465 :param attribute_names: optional list of string attribute names
3466 indicating a subset of attributes to be expired.
3467
3468 .. seealso::
3469
3470 :ref:`session_expire` - introductory material
3471
3472 :meth:`.Session.expire`
3473
3474 :meth:`.Session.refresh`
3475
3476 :meth:`_orm.Query.populate_existing`
3477
3478 """
3479 try:
3480 state = attributes.instance_state(instance)
3481 except exc.NO_STATE as err:
3482 raise exc.UnmappedInstanceError(instance) from err
3483 self._expire_state(state, attribute_names)
3484
3485 def _expire_state(
3486 self,
3487 state: InstanceState[Any],
3488 attribute_names: Optional[Iterable[str]],
3489 ) -> None:
3490 self._validate_persistent(state)
3491 if attribute_names:
3492 state._expire_attributes(state.dict, attribute_names)
3493 else:
3494 # pre-fetch the full cascade since the expire is going to
3495 # remove associations
3496 cascaded = list(
3497 state.manager.mapper.cascade_iterator("refresh-expire", state)
3498 )
3499 self._conditional_expire(state)
3500 for o, m, st_, dct_ in cascaded:
3501 self._conditional_expire(st_)
3502
3503 def _conditional_expire(
3504 self, state: InstanceState[Any], autoflush: Optional[bool] = None
3505 ) -> None:
3506 """Expire a state if persistent, else expunge if pending"""
3507
3508 if state.key:
3509 state._expire(state.dict, self.identity_map._modified)
3510 elif state in self._new:
3511 self._new.pop(state)
3512 state._detach(self)
3513
3514 def expunge(self, instance: object) -> None:
3515 """Remove the `instance` from this ``Session``.
3516
3517 This will free all internal references to the instance. Cascading
3518 will be applied according to the *expunge* cascade rule.
3519
3520 """
3521 try:
3522 state = attributes.instance_state(instance)
3523 except exc.NO_STATE as err:
3524 raise exc.UnmappedInstanceError(instance) from err
3525 if state.session_id is not self.hash_key:
3526 raise sa_exc.InvalidRequestError(
3527 "Instance %s is not present in this Session" % state_str(state)
3528 )
3529
3530 cascaded = list(
3531 state.manager.mapper.cascade_iterator("expunge", state)
3532 )
3533 self._expunge_states([state] + [st_ for o, m, st_, dct_ in cascaded])
3534
3535 def _expunge_states(
3536 self, states: Iterable[InstanceState[Any]], to_transient: bool = False
3537 ) -> None:
3538 for state in states:
3539 if state in self._new:
3540 self._new.pop(state)
3541 elif self.identity_map.contains_state(state):
3542 self.identity_map.safe_discard(state)
3543 self._deleted.pop(state, None)
3544 elif self._transaction:
3545 # state is "detached" from being deleted, but still present
3546 # in the transaction snapshot
3547 self._transaction._deleted.pop(state, None)
3548 statelib.InstanceState._detach_states(
3549 states, self, to_transient=to_transient
3550 )
3551
3552 def _register_persistent(self, states: Set[InstanceState[Any]]) -> None:
3553 """Register all persistent objects from a flush.
3554
3555 This is used both for pending objects moving to the persistent
3556 state as well as already persistent objects.
3557
3558 """
3559
3560 pending_to_persistent = self.dispatch.pending_to_persistent or None
3561 for state in states:
3562 mapper = _state_mapper(state)
3563
3564 # prevent against last minute dereferences of the object
3565 obj = state.obj()
3566 if obj is not None:
3567 instance_key = mapper._identity_key_from_state(state)
3568
3569 if (
3570 _none_set.intersection(instance_key[1])
3571 and not mapper.allow_partial_pks
3572 or _none_set.issuperset(instance_key[1])
3573 ):
3574 raise exc.FlushError(
3575 "Instance %s has a NULL identity key. If this is an "
3576 "auto-generated value, check that the database table "
3577 "allows generation of new primary key values, and "
3578 "that the mapped Column object is configured to "
3579 "expect these generated values. Ensure also that "
3580 "this flush() is not occurring at an inappropriate "
3581 "time, such as within a load() event."
3582 % state_str(state)
3583 )
3584
3585 if state.key is None:
3586 state.key = instance_key
3587 elif state.key != instance_key:
3588 # primary key switch. use safe_discard() in case another
3589 # state has already replaced this one in the identity
3590 # map (see test/orm/test_naturalpks.py ReversePKsTest)
3591 self.identity_map.safe_discard(state)
3592 trans = self._transaction
3593 assert trans is not None
3594 if state in trans._key_switches:
3595 orig_key = trans._key_switches[state][0]
3596 else:
3597 orig_key = state.key
3598 trans._key_switches[state] = (
3599 orig_key,
3600 instance_key,
3601 )
3602 state.key = instance_key
3603
3604 # there can be an existing state in the identity map
3605 # that is replaced when the primary keys of two instances
3606 # are swapped; see test/orm/test_naturalpks.py -> test_reverse
3607 old = self.identity_map.replace(state)
3608
3609 # check the replaced state's identity without emitting
3610 # any SQL; an expired state such as one left over from
3611 # a rollback will not match and does not warn
3612 if (
3613 old is not None
3614 and old.obj() is not None
3615 and mapper._identity_key_from_state(
3616 old, PassiveFlag.PASSIVE_NO_FETCH
3617 )
3618 == instance_key
3619 ):
3620 util.warn(
3621 "Identity map already had an identity for %s, "
3622 "replacing it with newly flushed object. Are there "
3623 "load operations occurring inside of an event handler "
3624 "within the flush?" % (instance_key,)
3625 )
3626 state._orphaned_outside_of_session = False
3627
3628 statelib.InstanceState._commit_all_states(
3629 ((state, state.dict) for state in states), self.identity_map
3630 )
3631
3632 self._register_altered(states)
3633
3634 if pending_to_persistent is not None:
3635 for state in states.intersection(self._new):
3636 pending_to_persistent(self, state)
3637
3638 # remove from new last, might be the last strong ref
3639 for state in set(states).intersection(self._new):
3640 self._new.pop(state)
3641
3642 def _register_altered(self, states: Iterable[InstanceState[Any]]) -> None:
3643 if self._transaction:
3644 for state in states:
3645 if state in self._new:
3646 self._transaction._new[state] = True
3647 else:
3648 self._transaction._dirty[state] = True
3649
3650 def _remove_newly_deleted(
3651 self, states: Iterable[InstanceState[Any]]
3652 ) -> None:
3653 persistent_to_deleted = self.dispatch.persistent_to_deleted or None
3654 for state in states:
3655 if self._transaction:
3656 self._transaction._deleted[state] = True
3657
3658 if persistent_to_deleted is not None:
3659 # get a strong reference before we pop out of
3660 # self._deleted
3661 obj = state.obj() # noqa
3662
3663 self.identity_map.safe_discard(state)
3664 self._deleted.pop(state, None)
3665 state._deleted = True
3666 # can't call state._detach() here, because this state
3667 # is still in the transaction snapshot and needs to be
3668 # tracked as part of that
3669 if persistent_to_deleted is not None:
3670 persistent_to_deleted(self, state)
3671
3672 def add(self, instance: object, *, _warn: bool = True) -> None:
3673 """Place an object into this :class:`_orm.Session`.
3674
3675 Objects that are in the :term:`transient` state when passed to the
3676 :meth:`_orm.Session.add` method will move to the
3677 :term:`pending` state, until the next flush, at which point they
3678 will move to the :term:`persistent` state.
3679
3680 Objects that are in the :term:`detached` state when passed to the
3681 :meth:`_orm.Session.add` method will move to the :term:`persistent`
3682 state directly.
3683
3684 If the transaction used by the :class:`_orm.Session` is rolled back,
3685 objects which were transient when they were passed to
3686 :meth:`_orm.Session.add` will be moved back to the
3687 :term:`transient` state, and will no longer be present within this
3688 :class:`_orm.Session`.
3689
3690 .. seealso::
3691
3692 :meth:`_orm.Session.add_all`
3693
3694 :ref:`session_adding` - at :ref:`session_basics`
3695
3696 """
3697 if _warn and self._warn_on_events:
3698 self._flush_warning("Session.add()")
3699
3700 try:
3701 state = attributes.instance_state(instance)
3702 except exc.NO_STATE as err:
3703 raise exc.UnmappedInstanceError(instance) from err
3704
3705 self._save_or_update_state(state)
3706
3707 def add_all(self, instances: Iterable[object]) -> None:
3708 """Add the given collection of instances to this :class:`_orm.Session`.
3709
3710 See the documentation for :meth:`_orm.Session.add` for a general
3711 behavioral description.
3712
3713 .. seealso::
3714
3715 :meth:`_orm.Session.add`
3716
3717 :ref:`session_adding` - at :ref:`session_basics`
3718
3719 """
3720
3721 if self._warn_on_events:
3722 self._flush_warning("Session.add_all()")
3723
3724 for instance in instances:
3725 self.add(instance, _warn=False)
3726
3727 def _save_or_update_state(self, state: InstanceState[Any]) -> None:
3728 state._orphaned_outside_of_session = False
3729 self._save_or_update_impl(state)
3730
3731 mapper = _state_mapper(state)
3732 for o, m, st_, dct_ in mapper.cascade_iterator(
3733 "save-update", state, halt_on=self._contains_state
3734 ):
3735 self._save_or_update_impl(st_)
3736
3737 def delete(self, instance: object) -> None:
3738 """Mark an instance as deleted.
3739
3740 The object is assumed to be either :term:`persistent` or
3741 :term:`detached` when passed; after the method is called, the
3742 object will remain in the :term:`persistent` state until the next
3743 flush proceeds. During this time, the object will also be a member
3744 of the :attr:`_orm.Session.deleted` collection.
3745
3746 When the next flush proceeds, the object will move to the
3747 :term:`deleted` state, indicating a ``DELETE`` statement was emitted
3748 for its row within the current transaction. When the transaction
3749 is successfully committed,
3750 the deleted object is moved to the :term:`detached` state and is
3751 no longer present within this :class:`_orm.Session`.
3752
3753 .. seealso::
3754
3755 :ref:`session_deleting` - at :ref:`session_basics`
3756
3757 :meth:`.Session.delete_all` - multiple instance version
3758
3759 """
3760 if self._warn_on_events:
3761 self._flush_warning("Session.delete()")
3762
3763 self._delete_impl(object_state(instance), instance, head=True)
3764
3765 def delete_all(self, instances: Iterable[object]) -> None:
3766 """Calls :meth:`.Session.delete` on multiple instances.
3767
3768 .. seealso::
3769
3770 :meth:`.Session.delete` - main documentation on delete
3771
3772 .. versionadded:: 2.1
3773
3774 """
3775
3776 if self._warn_on_events:
3777 self._flush_warning("Session.delete_all()")
3778
3779 for instance in instances:
3780 self._delete_impl(object_state(instance), instance, head=True)
3781
3782 def _delete_impl(
3783 self, state: InstanceState[Any], obj: object, head: bool
3784 ) -> None:
3785 if state.key is None:
3786 if head:
3787 raise sa_exc.InvalidRequestError(
3788 "Instance '%s' is not persisted" % state_str(state)
3789 )
3790 else:
3791 return
3792
3793 to_attach = self._before_attach(state, obj)
3794
3795 if state in self._deleted:
3796 return
3797
3798 self.identity_map.add(state)
3799
3800 if to_attach:
3801 self._after_attach(state, obj)
3802
3803 if head:
3804 # grab the cascades before adding the item to the deleted list
3805 # so that autoflush does not delete the item
3806 # the strong reference to the instance itself is significant here
3807 cascade_states = list(
3808 state.manager.mapper.cascade_iterator("delete", state)
3809 )
3810 else:
3811 cascade_states = None
3812
3813 self._deleted[state] = obj
3814
3815 if head:
3816 if TYPE_CHECKING:
3817 assert cascade_states is not None
3818 for o, m, st_, dct_ in cascade_states:
3819 self._delete_impl(st_, o, False)
3820
3821 def get(
3822 self,
3823 entity: _EntityBindKey[_O],
3824 ident: _PKIdentityArgument,
3825 *,
3826 options: Optional[Sequence[ORMOption]] = None,
3827 populate_existing: bool | None = None,
3828 with_for_update: ForUpdateParameter = None,
3829 identity_token: Optional[Any] = None,
3830 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
3831 bind_arguments: Optional[_BindArguments] = None,
3832 ) -> Optional[_O]:
3833 """Return an instance based on the given primary key identifier,
3834 or ``None`` if not found.
3835
3836 E.g.::
3837
3838 my_user = session.get(User, 5)
3839
3840 some_object = session.get(VersionedFoo, (5, 10))
3841
3842 some_object = session.get(VersionedFoo, {"id": 5, "version_id": 10})
3843
3844 .. versionadded:: 1.4 Added :meth:`_orm.Session.get`, which is moved
3845 from the now legacy :meth:`_orm.Query.get` method.
3846
3847 :meth:`_orm.Session.get` is special in that it provides direct
3848 access to the identity map of the :class:`.Session`.
3849 If the given primary key identifier is present
3850 in the local identity map, the object is returned
3851 directly from this collection and no SQL is emitted,
3852 unless the object has been marked fully expired.
3853 If not present,
3854 a SELECT is performed in order to locate the object.
3855
3856 :meth:`_orm.Session.get` also will perform a check if
3857 the object is present in the identity map and
3858 marked as expired - a SELECT
3859 is emitted to refresh the object as well as to
3860 ensure that the row is still present.
3861 If not, :class:`~sqlalchemy.orm.exc.ObjectDeletedError` is raised.
3862
3863 :param entity: a mapped class or :class:`.Mapper` indicating the
3864 type of entity to be loaded.
3865
3866 :param ident: A scalar, tuple, or dictionary representing the
3867 primary key. For a composite (e.g. multiple column) primary key,
3868 a tuple or dictionary should be passed.
3869
3870 For a single-column primary key, the scalar calling form is typically
3871 the most expedient. If the primary key of a row is the value "5",
3872 the call looks like::
3873
3874 my_object = session.get(SomeClass, 5)
3875
3876 The tuple form contains primary key values typically in
3877 the order in which they correspond to the mapped
3878 :class:`_schema.Table`
3879 object's primary key columns, or if the
3880 :paramref:`_orm.Mapper.primary_key` configuration parameter were
3881 used, in
3882 the order used for that parameter. For example, if the primary key
3883 of a row is represented by the integer
3884 digits "5, 10" the call would look like::
3885
3886 my_object = session.get(SomeClass, (5, 10))
3887
3888 The dictionary form should include as keys the mapped attribute names
3889 corresponding to each element of the primary key. If the mapped class
3890 has the attributes ``id``, ``version_id`` as the attributes which
3891 store the object's primary key value, the call would look like::
3892
3893 my_object = session.get(SomeClass, {"id": 5, "version_id": 10})
3894
3895 :param options: optional sequence of loader options which will be
3896 applied to the query, if one is emitted.
3897
3898 :param populate_existing: causes the method to unconditionally emit
3899 a SQL query and refresh the object with the newly loaded data,
3900 regardless of whether or not the object is already present.
3901 Setting this flag takes precedence over passing it as an
3902 execution option.
3903
3904 :param with_for_update: optional boolean ``True`` indicating FOR UPDATE
3905 should be used, or may be a dictionary containing flags to
3906 indicate a more specific set of FOR UPDATE flags for the SELECT;
3907 flags should match the parameters of
3908 :meth:`_query.Query.with_for_update`.
3909 Supersedes the :paramref:`.Session.refresh.lockmode` parameter.
3910
3911 :param execution_options: optional dictionary of execution options,
3912 which will be associated with the query execution if one is emitted.
3913 This dictionary can provide a subset of the options that are
3914 accepted by :meth:`_engine.Connection.execution_options`, and may
3915 also provide additional options understood only in an ORM context.
3916
3917 .. versionadded:: 1.4.29
3918
3919 .. seealso::
3920
3921 :ref:`orm_queryguide_execution_options` - ORM-specific execution
3922 options
3923
3924 :param bind_arguments: dictionary of additional arguments to determine
3925 the bind. May include "mapper", "bind", or other custom arguments.
3926 Contents of this dictionary are passed to the
3927 :meth:`.Session.get_bind` method.
3928
3929 .. versionadded:: 2.0.0rc1
3930
3931 :return: The object instance, or ``None``.
3932
3933 """ # noqa: E501
3934 return self._get_impl(
3935 entity,
3936 ident,
3937 loading._load_on_pk_identity,
3938 options=options,
3939 populate_existing=populate_existing,
3940 with_for_update=with_for_update,
3941 identity_token=identity_token,
3942 execution_options=execution_options,
3943 bind_arguments=bind_arguments,
3944 )
3945
3946 def get_one(
3947 self,
3948 entity: _EntityBindKey[_O],
3949 ident: _PKIdentityArgument,
3950 *,
3951 options: Optional[Sequence[ORMOption]] = None,
3952 populate_existing: bool | None = None,
3953 with_for_update: ForUpdateParameter = None,
3954 identity_token: Optional[Any] = None,
3955 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
3956 bind_arguments: Optional[_BindArguments] = None,
3957 ) -> _O:
3958 """Return exactly one instance based on the given primary key
3959 identifier, or raise an exception if not found.
3960
3961 Raises :class:`_exc.NoResultFound` if the query selects no rows.
3962
3963 For a detailed documentation of the arguments see the
3964 method :meth:`.Session.get`.
3965
3966 .. versionadded:: 2.0.22
3967
3968 :return: The object instance.
3969
3970 .. seealso::
3971
3972 :meth:`.Session.get` - equivalent method that instead
3973 returns ``None`` if no row was found with the provided primary
3974 key
3975
3976 """
3977
3978 instance = self.get(
3979 entity,
3980 ident,
3981 options=options,
3982 populate_existing=populate_existing,
3983 with_for_update=with_for_update,
3984 identity_token=identity_token,
3985 execution_options=execution_options,
3986 bind_arguments=bind_arguments,
3987 )
3988
3989 if instance is None:
3990 raise sa_exc.NoResultFound(
3991 "No row was found when one was required"
3992 )
3993
3994 return instance
3995
3996 def _get_impl(
3997 self,
3998 entity: _EntityBindKey[_O],
3999 primary_key_identity: _PKIdentityArgument,
4000 db_load_fn: Callable[..., _O],
4001 *,
4002 options: Optional[Sequence[ExecutableOption]] = None,
4003 populate_existing: bool | None = None,
4004 with_for_update: ForUpdateParameter = None,
4005 identity_token: Optional[Any] = None,
4006 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
4007 bind_arguments: Optional[_BindArguments] = None,
4008 ) -> Optional[_O]:
4009 # set populate_existing value; direct parameter
4010 # takes precedence over execution_options
4011 if populate_existing is not None:
4012 execution_options = {
4013 **execution_options,
4014 "populate_existing": populate_existing,
4015 }
4016 else:
4017 populate_existing = execution_options.get(
4018 "populate_existing", False
4019 )
4020
4021 # convert composite types to individual args
4022 if (
4023 is_composite_class(primary_key_identity)
4024 and type(primary_key_identity)
4025 in descriptor_props._composite_getters
4026 ):
4027 getter = descriptor_props._composite_getters[
4028 type(primary_key_identity)
4029 ]
4030 primary_key_identity = getter(primary_key_identity)
4031
4032 mapper: Optional[Mapper[_O]] = inspect(entity)
4033
4034 if mapper is None or not mapper.is_mapper:
4035 raise sa_exc.ArgumentError(
4036 "Expected mapped class or mapper, got: %r" % entity
4037 )
4038
4039 is_dict = isinstance(primary_key_identity, dict)
4040 if not is_dict:
4041 primary_key_identity = util.to_list(
4042 primary_key_identity, default=[None]
4043 )
4044
4045 if len(primary_key_identity) != len(mapper.primary_key):
4046 raise sa_exc.InvalidRequestError(
4047 "Incorrect number of values in identifier to formulate "
4048 "primary key for session.get(); primary key columns "
4049 "are %s" % ",".join("'%s'" % c for c in mapper.primary_key)
4050 )
4051
4052 if is_dict:
4053 pk_synonyms = mapper._pk_synonyms
4054
4055 if pk_synonyms:
4056 correct_keys = set(pk_synonyms).intersection(
4057 primary_key_identity
4058 )
4059
4060 if correct_keys:
4061 primary_key_identity = dict(primary_key_identity)
4062 for k in correct_keys:
4063 primary_key_identity[pk_synonyms[k]] = (
4064 primary_key_identity[k]
4065 )
4066
4067 try:
4068 primary_key_identity = list(
4069 primary_key_identity[prop.key]
4070 for prop in mapper._identity_key_props
4071 )
4072
4073 except KeyError as err:
4074 raise sa_exc.InvalidRequestError(
4075 "Incorrect names of values in identifier to formulate "
4076 "primary key for session.get(); primary key attribute "
4077 "names are %s (synonym names are also accepted)"
4078 % ",".join(
4079 "'%s'" % prop.key
4080 for prop in mapper._identity_key_props
4081 )
4082 ) from err
4083
4084 for_update_arg = ForUpdateArg._from_argument(with_for_update)
4085
4086 if (
4087 not populate_existing
4088 and not mapper.always_refresh
4089 and for_update_arg is None
4090 ):
4091 instance = self._identity_lookup(
4092 mapper,
4093 primary_key_identity,
4094 identity_token=identity_token,
4095 execution_options=execution_options,
4096 bind_arguments=bind_arguments,
4097 )
4098
4099 if instance is not None:
4100 # reject calls for id in identity map but class
4101 # mismatch.
4102 if not isinstance(instance, mapper.class_):
4103 return None
4104 return instance
4105
4106 # TODO: this was being tested before, but this is not possible
4107 assert instance is not LoaderCallableStatus.PASSIVE_CLASS_MISMATCH
4108
4109 load_options = context.QueryContext.default_load_options
4110
4111 if populate_existing:
4112 load_options += {"_populate_existing": populate_existing}
4113 statement = sql.select(mapper)
4114 if for_update_arg is not None:
4115 statement._for_update_arg = for_update_arg
4116
4117 if options:
4118 statement = statement.options(*options)
4119 if self.execution_options:
4120 execution_options = self.execution_options.union(execution_options)
4121 return db_load_fn(
4122 self,
4123 statement,
4124 primary_key_identity,
4125 load_options=load_options,
4126 identity_token=identity_token,
4127 execution_options=execution_options,
4128 bind_arguments=bind_arguments,
4129 )
4130
4131 def merge(
4132 self,
4133 instance: _O,
4134 *,
4135 load: bool = True,
4136 options: Optional[Sequence[ORMOption]] = None,
4137 ) -> _O:
4138 """Copy the state of a given instance into a corresponding instance
4139 within this :class:`.Session`.
4140
4141 :meth:`.Session.merge` examines the primary key attributes of the
4142 source instance, and attempts to reconcile it with an instance of the
4143 same primary key in the session. If not found locally, it attempts
4144 to load the object from the database based on primary key, and if
4145 none can be located, creates a new instance. The state of each
4146 attribute on the source instance is then copied to the target
4147 instance. The resulting target instance is then returned by the
4148 method; the original source instance is left unmodified, and
4149 un-associated with the :class:`.Session` if not already.
4150
4151 This operation cascades to associated instances if the association is
4152 mapped with ``cascade="merge"``.
4153
4154 See :ref:`unitofwork_merging` for a detailed discussion of merging.
4155
4156 :param instance: Instance to be merged.
4157 :param load: Boolean, when False, :meth:`.merge` switches into
4158 a "high performance" mode which causes it to forego emitting history
4159 events as well as all database access. This flag is used for
4160 cases such as transferring graphs of objects into a :class:`.Session`
4161 from a second level cache, or to transfer just-loaded objects
4162 into the :class:`.Session` owned by a worker thread or process
4163 without re-querying the database.
4164
4165 The ``load=False`` use case adds the caveat that the given
4166 object has to be in a "clean" state, that is, has no pending changes
4167 to be flushed - even if the incoming object is detached from any
4168 :class:`.Session`. This is so that when
4169 the merge operation populates local attributes and
4170 cascades to related objects and
4171 collections, the values can be "stamped" onto the
4172 target object as is, without generating any history or attribute
4173 events, and without the need to reconcile the incoming data with
4174 any existing related objects or collections that might not
4175 be loaded. The resulting objects from ``load=False`` are always
4176 produced as "clean", so it is only appropriate that the given objects
4177 should be "clean" as well, else this suggests a mis-use of the
4178 method.
4179 :param options: optional sequence of loader options which will be
4180 applied to the :meth:`_orm.Session.get` method when the merge
4181 operation loads the existing version of the object from the database.
4182
4183 .. versionadded:: 1.4.24
4184
4185
4186 .. seealso::
4187
4188 :func:`.make_transient_to_detached` - provides for an alternative
4189 means of "merging" a single object into the :class:`.Session`
4190
4191 :meth:`.Session.merge_all` - multiple instance version
4192
4193 """
4194
4195 if self._warn_on_events:
4196 self._flush_warning("Session.merge()")
4197
4198 if load:
4199 # flush current contents if we expect to load data
4200 self._autoflush()
4201
4202 with self.no_autoflush:
4203 return self._merge(
4204 object_state(instance),
4205 attributes.instance_dict(instance),
4206 load=load,
4207 options=options,
4208 _recursive={},
4209 _resolve_conflict_map={},
4210 )
4211
4212 def merge_all(
4213 self,
4214 instances: Iterable[_O],
4215 *,
4216 load: bool = True,
4217 options: Optional[Sequence[ORMOption]] = None,
4218 ) -> Sequence[_O]:
4219 """Calls :meth:`.Session.merge` on multiple instances.
4220
4221 .. seealso::
4222
4223 :meth:`.Session.merge` - main documentation on merge
4224
4225 .. versionadded:: 2.1
4226
4227 """
4228
4229 if self._warn_on_events:
4230 self._flush_warning("Session.merge_all()")
4231
4232 if load:
4233 # flush current contents if we expect to load data
4234 self._autoflush()
4235
4236 with self.no_autoflush:
4237 return [
4238 self._merge(
4239 object_state(instance),
4240 attributes.instance_dict(instance),
4241 load=load,
4242 options=options,
4243 _recursive={},
4244 _resolve_conflict_map={},
4245 )
4246 for instance in instances
4247 ]
4248
4249 def _merge(
4250 self,
4251 state: InstanceState[_O],
4252 state_dict: _InstanceDict,
4253 *,
4254 options: Optional[Sequence[ORMOption]] = None,
4255 load: bool,
4256 _recursive: Dict[Any, object],
4257 _resolve_conflict_map: Dict[_IdentityKeyType[Any], object],
4258 ) -> _O:
4259 mapper: Mapper[_O] = _state_mapper(state)
4260 if state in _recursive:
4261 return cast(_O, _recursive[state])
4262
4263 new_instance = False
4264 key = state.key
4265
4266 merged: Optional[_O]
4267
4268 if key is None:
4269 if state in self._new:
4270 util.warn(
4271 "Instance %s is already pending in this Session yet is "
4272 "being merged again; this is probably not what you want "
4273 "to do" % state_str(state)
4274 )
4275
4276 if not load:
4277 raise sa_exc.InvalidRequestError(
4278 "merge() with load=False option does not support "
4279 "objects transient (i.e. unpersisted) objects. flush() "
4280 "all changes on mapped instances before merging with "
4281 "load=False."
4282 )
4283 key = mapper._identity_key_from_state(state)
4284 key_is_persistent = LoaderCallableStatus.NEVER_SET not in key[
4285 1
4286 ] and (
4287 not _none_set.intersection(key[1])
4288 or (
4289 mapper.allow_partial_pks
4290 and not _none_set.issuperset(key[1])
4291 )
4292 )
4293 else:
4294 key_is_persistent = True
4295
4296 merged = self.identity_map.get(key)
4297
4298 if merged is None:
4299 if key_is_persistent and key in _resolve_conflict_map:
4300 merged = cast(_O, _resolve_conflict_map[key])
4301
4302 elif not load:
4303 if state.modified:
4304 raise sa_exc.InvalidRequestError(
4305 "merge() with load=False option does not support "
4306 "objects marked as 'dirty'. flush() all changes on "
4307 "mapped instances before merging with load=False."
4308 )
4309 merged = mapper.class_manager.new_instance()
4310 merged_state = attributes.instance_state(merged)
4311 merged_state.key = key
4312 self._update_impl(merged_state)
4313 new_instance = True
4314
4315 elif key_is_persistent:
4316 merged = self.get(
4317 mapper.class_,
4318 key[1],
4319 identity_token=key[2],
4320 options=options,
4321 )
4322
4323 if merged is None:
4324 merged = mapper.class_manager.new_instance()
4325 merged_state = attributes.instance_state(merged)
4326 merged_dict = attributes.instance_dict(merged)
4327 new_instance = True
4328 self._save_or_update_state(merged_state)
4329 else:
4330 merged_state = attributes.instance_state(merged)
4331 merged_dict = attributes.instance_dict(merged)
4332
4333 _recursive[state] = merged
4334 _resolve_conflict_map[key] = merged
4335
4336 # check that we didn't just pull the exact same
4337 # state out.
4338 if state is not merged_state:
4339 # version check if applicable
4340 if mapper.version_id_col is not None:
4341 existing_version = mapper._get_state_attr_by_column(
4342 state,
4343 state_dict,
4344 mapper.version_id_col,
4345 passive=PassiveFlag.PASSIVE_NO_INITIALIZE,
4346 )
4347
4348 merged_version = mapper._get_state_attr_by_column(
4349 merged_state,
4350 merged_dict,
4351 mapper.version_id_col,
4352 passive=PassiveFlag.PASSIVE_NO_INITIALIZE,
4353 )
4354
4355 if (
4356 existing_version
4357 is not LoaderCallableStatus.PASSIVE_NO_RESULT
4358 and merged_version
4359 is not LoaderCallableStatus.PASSIVE_NO_RESULT
4360 and existing_version != merged_version
4361 ):
4362 raise exc.StaleDataError(
4363 "Version id '%s' on merged state %s "
4364 "does not match existing version '%s'. "
4365 "Leave the version attribute unset when "
4366 "merging to update the most recent version."
4367 % (
4368 existing_version,
4369 state_str(merged_state),
4370 merged_version,
4371 )
4372 )
4373
4374 merged_state.load_path = state.load_path
4375 merged_state.load_options = state.load_options
4376
4377 # since we are copying load_options, we need to copy
4378 # the callables_ that would have been generated by those
4379 # load_options.
4380 # assumes that the callables we put in state.callables_
4381 # are not instance-specific (which they should not be)
4382 merged_state._copy_callables(state)
4383
4384 for prop in mapper.iterate_properties:
4385 prop.merge(
4386 self,
4387 state,
4388 state_dict,
4389 merged_state,
4390 merged_dict,
4391 load,
4392 _recursive,
4393 _resolve_conflict_map,
4394 )
4395
4396 if not load:
4397 # remove any history
4398 merged_state._commit_all(merged_dict, self.identity_map)
4399 merged_state.manager.dispatch._sa_event_merge_wo_load(
4400 merged_state, None
4401 )
4402
4403 if new_instance:
4404 merged_state.manager.dispatch.load(merged_state, None)
4405
4406 return merged
4407
4408 def _validate_persistent(self, state: InstanceState[Any]) -> None:
4409 if not self.identity_map.contains_state(state):
4410 raise sa_exc.InvalidRequestError(
4411 "Instance '%s' is not persistent within this Session"
4412 % state_str(state)
4413 )
4414
4415 def _save_impl(self, state: InstanceState[Any]) -> None:
4416 if state.key is not None:
4417 raise sa_exc.InvalidRequestError(
4418 "Object '%s' already has an identity - "
4419 "it can't be registered as pending" % state_str(state)
4420 )
4421
4422 obj = state.obj()
4423 to_attach = self._before_attach(state, obj)
4424 if state not in self._new:
4425 self._new[state] = obj
4426 state.insert_order = len(self._new)
4427 if to_attach:
4428 self._after_attach(state, obj)
4429
4430 def _update_impl(
4431 self, state: InstanceState[Any], revert_deletion: bool = False
4432 ) -> None:
4433 if state.key is None:
4434 raise sa_exc.InvalidRequestError(
4435 "Instance '%s' is not persisted" % state_str(state)
4436 )
4437
4438 if state._deleted:
4439 if revert_deletion:
4440 if not state._attached:
4441 return
4442 del state._deleted
4443 else:
4444 raise sa_exc.InvalidRequestError(
4445 "Instance '%s' has been deleted. "
4446 "Use the make_transient() "
4447 "function to send this object back "
4448 "to the transient state." % state_str(state)
4449 )
4450
4451 obj = state.obj()
4452
4453 # check for late gc
4454 if obj is None:
4455 return
4456
4457 to_attach = self._before_attach(state, obj)
4458
4459 self._deleted.pop(state, None)
4460 if revert_deletion:
4461 self.identity_map.replace(state)
4462 else:
4463 self.identity_map.add(state)
4464
4465 if to_attach:
4466 self._after_attach(state, obj)
4467 elif revert_deletion:
4468 self.dispatch.deleted_to_persistent(self, state)
4469
4470 def _save_or_update_impl(self, state: InstanceState[Any]) -> None:
4471 if state.key is None:
4472 self._save_impl(state)
4473 else:
4474 self._update_impl(state)
4475
4476 def enable_relationship_loading(self, obj: object) -> None:
4477 """Associate an object with this :class:`.Session` for related
4478 object loading.
4479
4480 .. warning::
4481
4482 :meth:`.enable_relationship_loading` exists to serve special
4483 use cases and is not recommended for general use.
4484
4485 Accesses of attributes mapped with :func:`_orm.relationship`
4486 will attempt to load a value from the database using this
4487 :class:`.Session` as the source of connectivity. The values
4488 will be loaded based on foreign key and primary key values
4489 present on this object - if not present, then those relationships
4490 will be unavailable.
4491
4492 The object will be attached to this session, but will
4493 **not** participate in any persistence operations; its state
4494 for almost all purposes will remain either "transient" or
4495 "detached", except for the case of relationship loading.
4496
4497 Also note that backrefs will often not work as expected.
4498 Altering a relationship-bound attribute on the target object
4499 may not fire off a backref event, if the effective value
4500 is what was already loaded from a foreign-key-holding value.
4501
4502 The :meth:`.Session.enable_relationship_loading` method is
4503 similar to the ``load_on_pending`` flag on :func:`_orm.relationship`.
4504 Unlike that flag, :meth:`.Session.enable_relationship_loading` allows
4505 an object to remain transient while still being able to load
4506 related items.
4507
4508 To make a transient object associated with a :class:`.Session`
4509 via :meth:`.Session.enable_relationship_loading` pending, add
4510 it to the :class:`.Session` using :meth:`.Session.add` normally.
4511 If the object instead represents an existing identity in the database,
4512 it should be merged using :meth:`.Session.merge`.
4513
4514 :meth:`.Session.enable_relationship_loading` does not improve
4515 behavior when the ORM is used normally - object references should be
4516 constructed at the object level, not at the foreign key level, so
4517 that they are present in an ordinary way before flush()
4518 proceeds. This method is not intended for general use.
4519
4520 .. seealso::
4521
4522 :paramref:`_orm.relationship.load_on_pending` - this flag
4523 allows per-relationship loading of many-to-ones on items that
4524 are pending.
4525
4526 :func:`.make_transient_to_detached` - allows for an object to
4527 be added to a :class:`.Session` without SQL emitted, which then
4528 will unexpire attributes on access.
4529
4530 """
4531 try:
4532 state = attributes.instance_state(obj)
4533 except exc.NO_STATE as err:
4534 raise exc.UnmappedInstanceError(obj) from err
4535
4536 to_attach = self._before_attach(state, obj)
4537 state._load_pending = True
4538 if to_attach:
4539 self._after_attach(state, obj)
4540
4541 def _before_attach(self, state: InstanceState[Any], obj: object) -> bool:
4542 self._autobegin_t()
4543
4544 if state.session_id == self.hash_key:
4545 return False
4546
4547 if state.session_id and state.session_id in _sessions:
4548 raise sa_exc.InvalidRequestError(
4549 "Object '%s' is already attached to session '%s' "
4550 "(this is '%s')"
4551 % (state_str(state), state.session_id, self.hash_key)
4552 )
4553
4554 self.dispatch.before_attach(self, state)
4555
4556 return True
4557
4558 def _after_attach(self, state: InstanceState[Any], obj: object) -> None:
4559 state.session_id = self.hash_key
4560 if state.modified and state._strong_obj is None:
4561 state._strong_obj = obj
4562 self.dispatch.after_attach(self, state)
4563
4564 if state.key:
4565 self.dispatch.detached_to_persistent(self, state)
4566 else:
4567 self.dispatch.transient_to_pending(self, state)
4568
4569 def __contains__(self, instance: object) -> bool:
4570 """Return True if the instance is associated with this session.
4571
4572 The instance may be pending or persistent within the Session for a
4573 result of True.
4574
4575 """
4576 try:
4577 state = attributes.instance_state(instance)
4578 except exc.NO_STATE as err:
4579 raise exc.UnmappedInstanceError(instance) from err
4580 return self._contains_state(state)
4581
4582 def __iter__(self) -> Iterator[object]:
4583 """Iterate over all pending or persistent instances within this
4584 Session.
4585
4586 """
4587 return iter(
4588 list(self._new.values()) + list(self.identity_map.values())
4589 )
4590
4591 def _contains_state(self, state: InstanceState[Any]) -> bool:
4592 return state in self._new or self.identity_map.contains_state(state)
4593
4594 def flush(self, objects: Optional[Sequence[Any]] = None) -> None:
4595 """Flush all the object changes to the database.
4596
4597 Writes out all pending object creations, deletions and modifications
4598 to the database as INSERTs, DELETEs, UPDATEs, etc. Operations are
4599 automatically ordered by the Session's unit of work dependency
4600 solver.
4601
4602 Database operations will be issued in the current transactional
4603 context and do not affect the state of the transaction, unless an
4604 error occurs, in which case the entire transaction is rolled back.
4605 You may flush() as often as you like within a transaction to move
4606 changes from Python to the database's transaction buffer.
4607
4608 :param objects: Optional; restricts the flush operation to operate
4609 only on elements that are in the given collection.
4610
4611 This feature is for an extremely narrow set of use cases where
4612 particular objects may need to be operated upon before the
4613 full flush() occurs. It is not intended for general use.
4614
4615 .. deprecated:: 2.1
4616
4617 """
4618
4619 if self._flushing:
4620 raise sa_exc.InvalidRequestError("Session is already flushing")
4621
4622 if self._is_clean():
4623 return
4624 try:
4625 self._flushing = True
4626 self._flush(objects)
4627 finally:
4628 self._flushing = False
4629
4630 def _flush_warning(self, method: Any) -> None:
4631 util.warn(
4632 "Usage of the '%s' operation is not currently supported "
4633 "within the execution stage of the flush process. "
4634 "Results may not be consistent. Consider using alternative "
4635 "event listeners or connection-level operations instead." % method
4636 )
4637
4638 def _is_clean(self) -> bool:
4639 return (
4640 not self.identity_map.check_modified()
4641 and not self._deleted
4642 and not self._new
4643 )
4644
4645 # have this here since it otherwise causes issues with the proxy
4646 # method generation
4647 @deprecated_params(
4648 objects=(
4649 "2.1",
4650 "The `objects` parameter of `Session.flush` is deprecated",
4651 )
4652 )
4653 def _flush(self, objects: Optional[Sequence[object]] = None) -> None:
4654 dirty = self._dirty_states
4655 if not dirty and not self._deleted and not self._new:
4656 self.identity_map._modified.clear()
4657 return
4658
4659 flush_context = UOWTransaction(self)
4660
4661 if self.dispatch.before_flush:
4662 self.dispatch.before_flush(self, flush_context, objects)
4663 # re-establish "dirty states" in case the listeners
4664 # added
4665 dirty = self._dirty_states
4666
4667 deleted = set(self._deleted)
4668 new = set(self._new)
4669
4670 dirty = set(dirty).difference(deleted)
4671
4672 # create the set of all objects we want to operate upon
4673 if objects:
4674 # specific list passed in
4675 objset = set()
4676 for o in objects:
4677 try:
4678 state = attributes.instance_state(o)
4679
4680 except exc.NO_STATE as err:
4681 raise exc.UnmappedInstanceError(o) from err
4682 objset.add(state)
4683 else:
4684 objset = None
4685
4686 # store objects whose fate has been decided
4687 processed = set()
4688
4689 # put all saves/updates into the flush context. detect top-level
4690 # orphans and throw them into deleted.
4691 if objset:
4692 proc = new.union(dirty).intersection(objset).difference(deleted)
4693 else:
4694 proc = new.union(dirty).difference(deleted)
4695
4696 for state in proc:
4697 is_orphan = _state_mapper(state)._is_orphan(state)
4698
4699 is_persistent_orphan = is_orphan and state.has_identity
4700
4701 if (
4702 is_orphan
4703 and not is_persistent_orphan
4704 and state._orphaned_outside_of_session
4705 ):
4706 self._expunge_states([state])
4707 else:
4708 _reg = flush_context.register_object(
4709 state, isdelete=is_persistent_orphan
4710 )
4711 assert _reg, "Failed to add object to the flush context!"
4712 processed.add(state)
4713
4714 # put all remaining deletes into the flush context.
4715 if objset:
4716 proc = deleted.intersection(objset).difference(processed)
4717 else:
4718 proc = deleted.difference(processed)
4719 for state in proc:
4720 _reg = flush_context.register_object(state, isdelete=True)
4721 assert _reg, "Failed to add object to the flush context!"
4722
4723 if not flush_context.has_work:
4724 return
4725
4726 flush_context.transaction = transaction = self._autobegin_t()._begin()
4727 try:
4728 self._warn_on_events = True
4729 try:
4730 flush_context.execute()
4731 finally:
4732 self._warn_on_events = False
4733
4734 self.dispatch.after_flush(self, flush_context)
4735
4736 flush_context.finalize_flush_changes()
4737
4738 if not objects and self.identity_map._modified:
4739 len_ = len(self.identity_map._modified)
4740
4741 statelib.InstanceState._commit_all_states(
4742 [
4743 (state, state.dict)
4744 for state in self.identity_map._modified
4745 ],
4746 instance_dict=self.identity_map,
4747 )
4748 util.warn(
4749 "Attribute history events accumulated on %d "
4750 "previously clean instances "
4751 "within inner-flush event handlers have been "
4752 "reset, and will not result in database updates. "
4753 "Consider using set_committed_value() within "
4754 "inner-flush event handlers to avoid this warning." % len_
4755 )
4756
4757 # useful assertions:
4758 # if not objects:
4759 # assert not self.identity_map._modified
4760 # else:
4761 # assert self.identity_map._modified == \
4762 # self.identity_map._modified.difference(objects)
4763
4764 self.dispatch.after_flush_postexec(self, flush_context)
4765
4766 transaction.commit()
4767
4768 except:
4769 with util.safe_reraise():
4770 transaction.rollback(_capture_exception=True)
4771
4772 def bulk_save_objects(
4773 self,
4774 objects: Iterable[object],
4775 return_defaults: bool = False,
4776 update_changed_only: bool = True,
4777 preserve_order: bool = True,
4778 ) -> None:
4779 """Perform a bulk save of the given list of objects.
4780
4781 .. legacy::
4782
4783 This method is a legacy feature as of the 2.0 series of
4784 SQLAlchemy. For modern bulk INSERT and UPDATE, see
4785 the sections :ref:`orm_queryguide_bulk_insert` and
4786 :ref:`orm_queryguide_bulk_update`.
4787
4788 For general INSERT and UPDATE of existing ORM mapped objects,
4789 prefer standard :term:`unit of work` data management patterns,
4790 introduced in the :ref:`unified_tutorial` at
4791 :ref:`tutorial_orm_data_manipulation`. SQLAlchemy 2.0
4792 now uses :ref:`engine_insertmanyvalues` with modern dialects
4793 which solves previous issues of bulk INSERT slowness.
4794
4795 :param objects: a sequence of mapped object instances. The mapped
4796 objects are persisted as is, and are **not** associated with the
4797 :class:`.Session` afterwards.
4798
4799 For each object, whether the object is sent as an INSERT or an
4800 UPDATE is dependent on the same rules used by the :class:`.Session`
4801 in traditional operation; if the object has the
4802 :attr:`.InstanceState.key`
4803 attribute set, then the object is assumed to be "detached" and
4804 will result in an UPDATE. Otherwise, an INSERT is used.
4805
4806 In the case of an UPDATE, statements are grouped based on which
4807 attributes have changed, and are thus to be the subject of each
4808 SET clause. If ``update_changed_only`` is False, then all
4809 attributes present within each object are applied to the UPDATE
4810 statement, which may help in allowing the statements to be grouped
4811 together into a larger executemany(), and will also reduce the
4812 overhead of checking history on attributes.
4813
4814 :param return_defaults: when True, rows that are missing values which
4815 generate defaults, namely integer primary key defaults and sequences,
4816 will be inserted **one at a time**, so that the primary key value
4817 is available. In particular this will allow joined-inheritance
4818 and other multi-table mappings to insert correctly without the need
4819 to provide primary key values ahead of time; however,
4820 :paramref:`.Session.bulk_save_objects.return_defaults` **greatly
4821 reduces the performance gains** of the method overall. It is strongly
4822 advised to please use the standard :meth:`_orm.Session.add_all`
4823 approach.
4824
4825 :param update_changed_only: when True, UPDATE statements are rendered
4826 based on those attributes in each state that have logged changes.
4827 When False, all attributes present are rendered into the SET clause
4828 with the exception of primary key attributes.
4829
4830 :param preserve_order: when True, the order of inserts and updates
4831 matches exactly the order in which the objects are given. When
4832 False, common types of objects are grouped into inserts
4833 and updates, to allow for more batching opportunities.
4834
4835 .. seealso::
4836
4837 :doc:`queryguide/dml`
4838
4839 :meth:`.Session.bulk_insert_mappings`
4840
4841 :meth:`.Session.bulk_update_mappings`
4842
4843 """
4844
4845 obj_states: Iterable[InstanceState[Any]]
4846
4847 obj_states = (attributes.instance_state(obj) for obj in objects)
4848
4849 if not preserve_order:
4850 # the purpose of this sort is just so that common mappers
4851 # and persistence states are grouped together, so that groupby
4852 # will return a single group for a particular type of mapper.
4853 # it's not trying to be deterministic beyond that.
4854 obj_states = sorted(
4855 obj_states,
4856 key=lambda state: (id(state.mapper), state.key is not None),
4857 )
4858
4859 def grouping_key(
4860 state: InstanceState[_O],
4861 ) -> Tuple[Mapper[_O], bool]:
4862 return (state.mapper, state.key is not None)
4863
4864 for (mapper, isupdate), states in itertools.groupby(
4865 obj_states, grouping_key
4866 ):
4867 self._bulk_save_mappings(
4868 mapper,
4869 states,
4870 isupdate=isupdate,
4871 isstates=True,
4872 return_defaults=return_defaults,
4873 update_changed_only=update_changed_only,
4874 render_nulls=False,
4875 )
4876
4877 def bulk_insert_mappings(
4878 self,
4879 mapper: _EntityBindKey[Any],
4880 mappings: Iterable[Dict[str, Any]],
4881 return_defaults: bool = False,
4882 render_nulls: bool = False,
4883 ) -> None:
4884 """Perform a bulk insert of the given list of mapping dictionaries.
4885
4886 .. legacy::
4887
4888 This method is a legacy feature as of the 2.0 series of
4889 SQLAlchemy. For modern bulk INSERT and UPDATE, see
4890 the sections :ref:`orm_queryguide_bulk_insert` and
4891 :ref:`orm_queryguide_bulk_update`. The 2.0 API shares
4892 implementation details with this method and adds new features
4893 as well.
4894
4895 :param mapper: a mapped class, or the actual :class:`_orm.Mapper`
4896 object,
4897 representing the single kind of object represented within the mapping
4898 list.
4899
4900 :param mappings: a sequence of dictionaries, each one containing the
4901 state of the mapped row to be inserted, in terms of the attribute
4902 names on the mapped class. If the mapping refers to multiple tables,
4903 such as a joined-inheritance mapping, each dictionary must contain all
4904 keys to be populated into all tables.
4905
4906 :param return_defaults: when True, the INSERT process will be altered
4907 to ensure that newly generated primary key values will be fetched.
4908 The rationale for this parameter is typically to enable
4909 :ref:`Joined Table Inheritance <joined_inheritance>` mappings to
4910 be bulk inserted.
4911
4912 .. note:: for backends that don't support RETURNING, the
4913 :paramref:`_orm.Session.bulk_insert_mappings.return_defaults`
4914 parameter can significantly decrease performance as INSERT
4915 statements can no longer be batched. See
4916 :ref:`engine_insertmanyvalues`
4917 for background on which backends are affected.
4918
4919 :param render_nulls: When True, a value of ``None`` will result
4920 in a NULL value being included in the INSERT statement, rather
4921 than the column being omitted from the INSERT. This allows all
4922 the rows being INSERTed to have the identical set of columns which
4923 allows the full set of rows to be batched to the DBAPI. Normally,
4924 each column-set that contains a different combination of NULL values
4925 than the previous row must omit a different series of columns from
4926 the rendered INSERT statement, which means it must be emitted as a
4927 separate statement. By passing this flag, the full set of rows
4928 are guaranteed to be batchable into one batch; the cost however is
4929 that server-side defaults which are invoked by an omitted column will
4930 be skipped, so care must be taken to ensure that these are not
4931 necessary.
4932
4933 .. warning::
4934
4935 When this flag is set, **server side default SQL values will
4936 not be invoked** for those columns that are inserted as NULL;
4937 the NULL value will be sent explicitly. Care must be taken
4938 to ensure that no server-side default functions need to be
4939 invoked for the operation as a whole.
4940
4941 .. seealso::
4942
4943 :doc:`queryguide/dml`
4944
4945 :meth:`.Session.bulk_save_objects`
4946
4947 :meth:`.Session.bulk_update_mappings`
4948
4949 """
4950 self._bulk_save_mappings(
4951 mapper,
4952 mappings,
4953 isupdate=False,
4954 isstates=False,
4955 return_defaults=return_defaults,
4956 update_changed_only=False,
4957 render_nulls=render_nulls,
4958 )
4959
4960 def bulk_update_mappings(
4961 self, mapper: _EntityBindKey[Any], mappings: Iterable[Dict[str, Any]]
4962 ) -> None:
4963 """Perform a bulk update of the given list of mapping dictionaries.
4964
4965 .. legacy::
4966
4967 This method is a legacy feature as of the 2.0 series of
4968 SQLAlchemy. For modern bulk INSERT and UPDATE, see
4969 the sections :ref:`orm_queryguide_bulk_insert` and
4970 :ref:`orm_queryguide_bulk_update`. The 2.0 API shares
4971 implementation details with this method and adds new features
4972 as well.
4973
4974 :param mapper: a mapped class, or the actual :class:`_orm.Mapper`
4975 object,
4976 representing the single kind of object represented within the mapping
4977 list.
4978
4979 :param mappings: a sequence of dictionaries, each one containing the
4980 state of the mapped row to be updated, in terms of the attribute names
4981 on the mapped class. If the mapping refers to multiple tables, such
4982 as a joined-inheritance mapping, each dictionary may contain keys
4983 corresponding to all tables. All those keys which are present and
4984 are not part of the primary key are applied to the SET clause of the
4985 UPDATE statement; the primary key values, which are required, are
4986 applied to the WHERE clause.
4987
4988
4989 .. seealso::
4990
4991 :doc:`queryguide/dml`
4992
4993 :meth:`.Session.bulk_insert_mappings`
4994
4995 :meth:`.Session.bulk_save_objects`
4996
4997 """
4998 self._bulk_save_mappings(
4999 mapper,
5000 mappings,
5001 isupdate=True,
5002 isstates=False,
5003 return_defaults=False,
5004 update_changed_only=False,
5005 render_nulls=False,
5006 )
5007
5008 def _bulk_save_mappings(
5009 self,
5010 mapper: _EntityBindKey[_O],
5011 mappings: Union[Iterable[InstanceState[_O]], Iterable[Dict[str, Any]]],
5012 *,
5013 isupdate: bool,
5014 isstates: bool,
5015 return_defaults: bool,
5016 update_changed_only: bool,
5017 render_nulls: bool,
5018 ) -> None:
5019 mapper = _class_to_mapper(mapper)
5020
5021 try:
5022 self._flushing = True
5023
5024 transaction = self._autobegin_t()._begin()
5025 try:
5026 if isupdate:
5027 bulk_persistence._bulk_update(
5028 mapper,
5029 mappings,
5030 transaction,
5031 isstates=isstates,
5032 update_changed_only=update_changed_only,
5033 )
5034 else:
5035 bulk_persistence._bulk_insert(
5036 mapper,
5037 mappings,
5038 transaction,
5039 isstates=isstates,
5040 return_defaults=return_defaults,
5041 render_nulls=render_nulls,
5042 )
5043 transaction.commit()
5044
5045 except:
5046 with util.safe_reraise():
5047 transaction.rollback(_capture_exception=True)
5048 finally:
5049 self._flushing = False
5050
5051 def is_modified(
5052 self, instance: object, include_collections: bool = True
5053 ) -> bool:
5054 r"""Return ``True`` if the given instance has locally
5055 modified attributes.
5056
5057 This method retrieves the history for each instrumented
5058 attribute on the instance and performs a comparison of the current
5059 value to its previously flushed or committed value, if any.
5060
5061 It is in effect a more expensive and accurate
5062 version of checking for the given instance in the
5063 :attr:`.Session.dirty` collection; a full test for
5064 each attribute's net "dirty" status is performed.
5065
5066 E.g.::
5067
5068 return session.is_modified(someobject)
5069
5070 A few caveats to this method apply:
5071
5072 * Instances present in the :attr:`.Session.dirty` collection may
5073 report ``False`` when tested with this method. This is because
5074 the object may have received change events via attribute mutation,
5075 thus placing it in :attr:`.Session.dirty`, but ultimately the state
5076 is the same as that loaded from the database, resulting in no net
5077 change here.
5078 * Scalar attributes may not have recorded the previously set
5079 value when a new value was applied, if the attribute was not loaded,
5080 or was expired, at the time the new value was received - in these
5081 cases, the attribute is assumed to have a change, even if there is
5082 ultimately no net change against its database value. SQLAlchemy in
5083 most cases does not need the "old" value when a set event occurs, so
5084 it skips the expense of a SQL call if the old value isn't present,
5085 based on the assumption that an UPDATE of the scalar value is
5086 usually needed, and in those few cases where it isn't, is less
5087 expensive on average than issuing a defensive SELECT.
5088
5089 The "old" value is fetched unconditionally upon set only if the
5090 attribute container has the ``active_history`` flag set to ``True``.
5091 This flag is set typically for primary key attributes and scalar
5092 object references that are not a simple many-to-one. To set this
5093 flag for any arbitrary mapped column, use the ``active_history``
5094 argument with :func:`.column_property`.
5095
5096 :param instance: mapped instance to be tested for pending changes.
5097 :param include_collections: Indicates if multivalued collections
5098 should be included in the operation. Setting this to ``False`` is a
5099 way to detect only local-column based properties (i.e. scalar columns
5100 or many-to-one foreign keys) that would result in an UPDATE for this
5101 instance upon flush.
5102
5103 """
5104 state = object_state(instance)
5105
5106 if not state.modified:
5107 return False
5108
5109 dict_ = state.dict
5110
5111 for attr in state.manager.attributes:
5112 if (
5113 not include_collections
5114 and hasattr(attr.impl, "get_collection")
5115 ) or not hasattr(attr.impl, "get_history"):
5116 continue
5117
5118 added, unchanged, deleted = attr.impl.get_history(
5119 state, dict_, passive=PassiveFlag.NO_CHANGE
5120 )
5121
5122 if added or deleted:
5123 return True
5124 else:
5125 return False
5126
5127 @property
5128 def is_active(self) -> bool:
5129 """True if this :class:`.Session` not in "partial rollback" state.
5130
5131 .. versionchanged:: 1.4 The :class:`_orm.Session` no longer begins
5132 a new transaction immediately, so this attribute will be False
5133 when the :class:`_orm.Session` is first instantiated.
5134
5135 "partial rollback" state typically indicates that the flush process
5136 of the :class:`_orm.Session` has failed, and that the
5137 :meth:`_orm.Session.rollback` method must be emitted in order to
5138 fully roll back the transaction.
5139
5140 If this :class:`_orm.Session` is not in a transaction at all, the
5141 :class:`_orm.Session` will autobegin when it is first used, so in this
5142 case :attr:`_orm.Session.is_active` will return True.
5143
5144 Otherwise, if this :class:`_orm.Session` is within a transaction,
5145 and that transaction has not been rolled back internally, the
5146 :attr:`_orm.Session.is_active` will also return True.
5147
5148 .. seealso::
5149
5150 :ref:`faq_session_rollback`
5151
5152 :meth:`_orm.Session.in_transaction`
5153
5154 """
5155 return self._transaction is None or self._transaction.is_active
5156
5157 @property
5158 def _dirty_states(self) -> Iterable[InstanceState[Any]]:
5159 """The set of all persistent states considered dirty.
5160
5161 This method returns all states that were modified including
5162 those that were possibly deleted.
5163
5164 """
5165 return self.identity_map._dirty_states()
5166
5167 @property
5168 def dirty(self) -> IdentitySet:
5169 """The set of all persistent instances considered dirty.
5170
5171 E.g.::
5172
5173 some_mapped_object in session.dirty
5174
5175 Instances are considered dirty when they were modified but not
5176 deleted.
5177
5178 Note that this 'dirty' calculation is 'optimistic'; most
5179 attribute-setting or collection modification operations will
5180 mark an instance as 'dirty' and place it in this set, even if
5181 there is no net change to the attribute's value. At flush
5182 time, the value of each attribute is compared to its
5183 previously saved value, and if there's no net change, no SQL
5184 operation will occur (this is a more expensive operation so
5185 it's only done at flush time).
5186
5187 To check if an instance has actionable net changes to its
5188 attributes, use the :meth:`.Session.is_modified` method.
5189
5190 """
5191 return IdentitySet(
5192 [
5193 state.obj()
5194 for state in self._dirty_states
5195 if state not in self._deleted
5196 ]
5197 )
5198
5199 @property
5200 def deleted(self) -> IdentitySet:
5201 "The set of all instances marked as 'deleted' within this ``Session``"
5202
5203 return util.IdentitySet(list(self._deleted.values()))
5204
5205 @property
5206 def new(self) -> IdentitySet:
5207 "The set of all instances marked as 'new' within this ``Session``."
5208
5209 return util.IdentitySet(list(self._new.values()))
5210
5211
5212_S = TypeVar("_S", bound="Session")
5213
5214
5215class sessionmaker(_SessionClassMethods, Generic[_S]):
5216 """A configurable :class:`.Session` factory.
5217
5218 The :class:`.sessionmaker` factory generates new
5219 :class:`.Session` objects when called, creating them given
5220 the configurational arguments established here.
5221
5222 e.g.::
5223
5224 from sqlalchemy import create_engine
5225 from sqlalchemy.orm import sessionmaker
5226
5227 # an Engine, which the Session will use for connection
5228 # resources
5229 engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/")
5230
5231 Session = sessionmaker(engine)
5232
5233 with Session() as session:
5234 session.add(some_object)
5235 session.add(some_other_object)
5236 session.commit()
5237
5238 Context manager use is optional; otherwise, the returned
5239 :class:`_orm.Session` object may be closed explicitly via the
5240 :meth:`_orm.Session.close` method. Using a
5241 ``try:/finally:`` block is optional, however will ensure that the close
5242 takes place even if there are database errors::
5243
5244 session = Session()
5245 try:
5246 session.add(some_object)
5247 session.add(some_other_object)
5248 session.commit()
5249 finally:
5250 session.close()
5251
5252 :class:`.sessionmaker` acts as a factory for :class:`_orm.Session`
5253 objects in the same way as an :class:`_engine.Engine` acts as a factory
5254 for :class:`_engine.Connection` objects. In this way it also includes
5255 a :meth:`_orm.sessionmaker.begin` method, that provides a context
5256 manager which both begins and commits a transaction, as well as closes
5257 out the :class:`_orm.Session` when complete, rolling back the transaction
5258 if any errors occur::
5259
5260 Session = sessionmaker(engine)
5261
5262 with Session.begin() as session:
5263 session.add(some_object)
5264 session.add(some_other_object)
5265 # commits transaction, closes session
5266
5267 .. versionadded:: 1.4
5268
5269 When calling upon :class:`_orm.sessionmaker` to construct a
5270 :class:`_orm.Session`, keyword arguments may also be passed to the
5271 method; these arguments will override that of the globally configured
5272 parameters. Below we use a :class:`_orm.sessionmaker` bound to a certain
5273 :class:`_engine.Engine` to produce a :class:`_orm.Session` that is instead
5274 bound to a specific :class:`_engine.Connection` procured from that engine::
5275
5276 Session = sessionmaker(engine)
5277
5278 # bind an individual session to a connection
5279
5280 with engine.connect() as connection:
5281 with Session(bind=connection) as session:
5282 ... # work with session
5283
5284 The class also includes a method :meth:`_orm.sessionmaker.configure`, which
5285 can be used to specify additional keyword arguments to the factory, which
5286 will take effect for subsequent :class:`.Session` objects generated. This
5287 is usually used to associate one or more :class:`_engine.Engine` objects
5288 with an existing
5289 :class:`.sessionmaker` factory before it is first used::
5290
5291 # application starts, sessionmaker does not have
5292 # an engine bound yet
5293 Session = sessionmaker()
5294
5295 # ... later, when an engine URL is read from a configuration
5296 # file or other events allow the engine to be created
5297 engine = create_engine("sqlite:///foo.db")
5298 Session.configure(bind=engine)
5299
5300 sess = Session()
5301 # work with session
5302
5303 .. seealso::
5304
5305 :ref:`session_getting` - introductory text on creating
5306 sessions using :class:`.sessionmaker`.
5307
5308 """
5309
5310 class_: Type[_S]
5311
5312 @overload
5313 def __init__(
5314 self,
5315 bind: Optional[_SessionBind] = ...,
5316 *,
5317 class_: Type[_S],
5318 autoflush: bool = ...,
5319 expire_on_commit: bool = ...,
5320 info: Optional[_InfoType] = ...,
5321 **kw: Any,
5322 ): ...
5323
5324 @overload
5325 def __init__(
5326 self: "sessionmaker[Session]",
5327 bind: Optional[_SessionBind] = ...,
5328 *,
5329 autoflush: bool = ...,
5330 expire_on_commit: bool = ...,
5331 info: Optional[_InfoType] = ...,
5332 **kw: Any,
5333 ): ...
5334
5335 def __init__(
5336 self,
5337 bind: Optional[_SessionBind] = None,
5338 *,
5339 class_: Type[_S] = Session, # type: ignore[assignment]
5340 autoflush: bool = True,
5341 expire_on_commit: bool = True,
5342 info: Optional[_InfoType] = None,
5343 **kw: Any,
5344 ):
5345 r"""Construct a new :class:`.sessionmaker`.
5346
5347 All arguments here except for ``class_`` correspond to arguments
5348 accepted by :class:`.Session` directly. See the
5349 :meth:`.Session.__init__` docstring for more details on parameters.
5350
5351 :param bind: a :class:`_engine.Engine` or other :class:`.Connectable`
5352 with
5353 which newly created :class:`.Session` objects will be associated.
5354 :param class\_: class to use in order to create new :class:`.Session`
5355 objects. Defaults to :class:`.Session`.
5356 :param autoflush: The autoflush setting to use with newly created
5357 :class:`.Session` objects.
5358
5359 .. seealso::
5360
5361 :ref:`session_flushing` - additional background on autoflush
5362
5363 :param expire_on_commit=True: the
5364 :paramref:`_orm.Session.expire_on_commit` setting to use
5365 with newly created :class:`.Session` objects.
5366
5367 :param info: optional dictionary of information that will be available
5368 via :attr:`.Session.info`. Note this dictionary is *updated*, not
5369 replaced, when the ``info`` parameter is specified to the specific
5370 :class:`.Session` construction operation.
5371
5372 :param \**kw: all other keyword arguments are passed to the
5373 constructor of newly created :class:`.Session` objects.
5374
5375 """
5376 kw["bind"] = bind
5377 kw["autoflush"] = autoflush
5378 kw["expire_on_commit"] = expire_on_commit
5379 if info is not None:
5380 kw["info"] = info
5381 self.kw = kw
5382 # make our own subclass of the given class, so that
5383 # events can be associated with it specifically.
5384 self.class_ = type(class_.__name__, (class_,), {})
5385
5386 def begin(self) -> contextlib.AbstractContextManager[_S]:
5387 """Produce a context manager that both provides a new
5388 :class:`_orm.Session` as well as a transaction that commits.
5389
5390
5391 e.g.::
5392
5393 Session = sessionmaker(some_engine)
5394
5395 with Session.begin() as session:
5396 session.add(some_object)
5397
5398 # commits transaction, closes session
5399
5400 .. versionadded:: 1.4
5401
5402
5403 """
5404
5405 session = self()
5406 return session._maker_context_manager()
5407
5408 def __call__(self, **local_kw: Any) -> _S:
5409 """Produce a new :class:`.Session` object using the configuration
5410 established in this :class:`.sessionmaker`.
5411
5412 In Python, the ``__call__`` method is invoked on an object when
5413 it is "called" in the same way as a function::
5414
5415 Session = sessionmaker(some_engine)
5416 session = Session() # invokes sessionmaker.__call__()
5417
5418 """
5419 for k, v in self.kw.items():
5420 if k == "info" and "info" in local_kw:
5421 d = v.copy()
5422 d.update(local_kw["info"])
5423 local_kw["info"] = d
5424 else:
5425 local_kw.setdefault(k, v)
5426 return self.class_(**local_kw)
5427
5428 def configure(self, **new_kw: Any) -> None:
5429 """(Re)configure the arguments for this sessionmaker.
5430
5431 e.g.::
5432
5433 Session = sessionmaker()
5434
5435 Session.configure(bind=create_engine("sqlite://"))
5436 """
5437 self.kw.update(new_kw)
5438
5439 def __repr__(self) -> str:
5440 return "%s(class_=%r, %s)" % (
5441 self.__class__.__name__,
5442 self.class_.__name__,
5443 ", ".join("%s=%r" % (k, v) for k, v in self.kw.items()),
5444 )
5445
5446
5447def close_all_sessions() -> None:
5448 """Close all sessions in memory.
5449
5450 This function consults a global registry of all :class:`.Session` objects
5451 and calls :meth:`.Session.close` on them, which resets them to a clean
5452 state.
5453
5454 This function is not for general use but may be useful for test suites
5455 within the teardown scheme.
5456
5457 """
5458
5459 for sess in _sessions.values():
5460 sess.close()
5461
5462
5463def make_transient(instance: object) -> None:
5464 """Alter the state of the given instance so that it is :term:`transient`.
5465
5466 .. note::
5467
5468 :func:`.make_transient` is a special-case function for
5469 advanced use cases only.
5470
5471 The given mapped instance is assumed to be in the :term:`persistent` or
5472 :term:`detached` state. The function will remove its association with any
5473 :class:`.Session` as well as its :attr:`.InstanceState.identity`. The
5474 effect is that the object will behave as though it were newly constructed,
5475 except retaining any attribute / collection values that were loaded at the
5476 time of the call. The :attr:`.InstanceState.deleted` flag is also reset
5477 if this object had been deleted as a result of using
5478 :meth:`.Session.delete`.
5479
5480 .. warning::
5481
5482 :func:`.make_transient` does **not** "unexpire" or otherwise eagerly
5483 load ORM-mapped attributes that are not currently loaded at the time
5484 the function is called. This includes attributes which:
5485
5486 * were expired via :meth:`.Session.expire`
5487
5488 * were expired as the natural effect of committing a session
5489 transaction, e.g. :meth:`.Session.commit`
5490
5491 * are normally :term:`lazy loaded` but are not currently loaded
5492
5493 * are "deferred" (see :ref:`orm_queryguide_column_deferral`) and are
5494 not yet loaded
5495
5496 * were not present in the query which loaded this object, such as that
5497 which is common in joined table inheritance and other scenarios.
5498
5499 After :func:`.make_transient` is called, unloaded attributes such
5500 as those above will normally resolve to the value ``None`` when
5501 accessed, or an empty collection for a collection-oriented attribute.
5502 As the object is transient and un-associated with any database
5503 identity, it will no longer retrieve these values.
5504
5505 .. seealso::
5506
5507 :func:`.make_transient_to_detached`
5508
5509 """
5510 state = attributes.instance_state(instance)
5511 s = _state_session(state)
5512 if s:
5513 s._expunge_states([state])
5514
5515 # remove expired state
5516 state.expired_attributes.clear()
5517
5518 # remove deferred callables
5519 if state.callables:
5520 del state.callables
5521
5522 if state.key:
5523 del state.key
5524 if state._deleted:
5525 del state._deleted
5526
5527
5528def make_transient_to_detached(instance: object) -> None:
5529 """Make the given transient instance :term:`detached`.
5530
5531 .. note::
5532
5533 :func:`.make_transient_to_detached` is a special-case function for
5534 advanced use cases only.
5535
5536 All attribute history on the given instance
5537 will be reset as though the instance were freshly loaded
5538 from a query. Missing attributes will be marked as expired.
5539 The primary key attributes of the object, which are required, will be made
5540 into the "key" of the instance.
5541
5542 The object can then be added to a session, or merged
5543 possibly with the load=False flag, at which point it will look
5544 as if it were loaded that way, without emitting SQL.
5545
5546 This is a special use case function that differs from a normal
5547 call to :meth:`.Session.merge` in that a given persistent state
5548 can be manufactured without any SQL calls.
5549
5550 .. seealso::
5551
5552 :func:`.make_transient`
5553
5554 :meth:`.Session.enable_relationship_loading`
5555
5556 """
5557 state = attributes.instance_state(instance)
5558 if state.session_id or state.key:
5559 raise sa_exc.InvalidRequestError("Given object must be transient")
5560 state.key = state.mapper._identity_key_from_state(state)
5561 if state._deleted:
5562 del state._deleted
5563 state._commit_all(state.dict)
5564 state._expire_attributes(state.dict, state.unloaded)
5565
5566
5567def object_session(instance: object) -> Optional[Session]:
5568 """Return the :class:`.Session` to which the given instance belongs.
5569
5570 This is essentially the same as the :attr:`.InstanceState.session`
5571 accessor. See that attribute for details.
5572
5573 """
5574
5575 try:
5576 state = attributes.instance_state(instance)
5577 except exc.NO_STATE as err:
5578 raise exc.UnmappedInstanceError(instance) from err
5579 else:
5580 return _state_session(state)
5581
5582
5583_new_sessionid = util.counter()