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