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