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
2232 bind = self.get_bind(**bind_arguments)
2233
2234 conn = self._connection_for_bind(bind)
2235
2236 if _scalar_result and not compile_state_cls:
2237 if TYPE_CHECKING:
2238 params = cast(_CoreSingleExecuteParams, params)
2239 return conn.scalar(
2240 statement, params or {}, execution_options=execution_options
2241 )
2242
2243 if compile_state_cls:
2244 result: Result[Unpack[TupleAny]] = (
2245 compile_state_cls.orm_execute_statement(
2246 self,
2247 statement,
2248 params or {},
2249 execution_options,
2250 bind_arguments,
2251 conn,
2252 )
2253 )
2254 else:
2255 result = conn.execute(
2256 statement, params, execution_options=execution_options
2257 )
2258
2259 if _scalar_result:
2260 return result.scalar()
2261 else:
2262 return result
2263
2264 @overload
2265 def execute(
2266 self,
2267 statement: TypedReturnsRows[Unpack[_Ts]],
2268 params: Optional[_CoreAnyExecuteParams] = None,
2269 *,
2270 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2271 bind_arguments: Optional[_BindArguments] = None,
2272 _parent_execute_state: Optional[Any] = None,
2273 _add_event: Optional[Any] = None,
2274 ) -> Result[Unpack[_Ts]]: ...
2275
2276 @overload
2277 def execute(
2278 self,
2279 statement: UpdateBase,
2280 params: Optional[_CoreAnyExecuteParams] = None,
2281 *,
2282 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2283 bind_arguments: Optional[_BindArguments] = None,
2284 _parent_execute_state: Optional[Any] = None,
2285 _add_event: Optional[Any] = None,
2286 ) -> CursorResult[Unpack[TupleAny]]: ...
2287
2288 @overload
2289 def execute(
2290 self,
2291 statement: Executable,
2292 params: Optional[_CoreAnyExecuteParams] = None,
2293 *,
2294 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2295 bind_arguments: Optional[_BindArguments] = None,
2296 _parent_execute_state: Optional[Any] = None,
2297 _add_event: Optional[Any] = None,
2298 ) -> Result[Unpack[TupleAny]]: ...
2299
2300 def execute(
2301 self,
2302 statement: Executable,
2303 params: Optional[_CoreAnyExecuteParams] = None,
2304 *,
2305 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2306 bind_arguments: Optional[_BindArguments] = None,
2307 _parent_execute_state: Optional[Any] = None,
2308 _add_event: Optional[Any] = None,
2309 ) -> Result[Unpack[TupleAny]]:
2310 r"""Execute a SQL expression construct.
2311
2312 Returns a :class:`_engine.Result` object representing
2313 results of the statement execution.
2314
2315 E.g.::
2316
2317 from sqlalchemy import select
2318
2319 result = session.execute(select(User).where(User.id == 5))
2320
2321 The API contract of :meth:`_orm.Session.execute` is similar to that
2322 of :meth:`_engine.Connection.execute`, the :term:`2.0 style` version
2323 of :class:`_engine.Connection`.
2324
2325 .. versionchanged:: 1.4 the :meth:`_orm.Session.execute` method is
2326 now the primary point of ORM statement execution when using
2327 :term:`2.0 style` ORM usage.
2328
2329 :param statement:
2330 An executable statement (i.e. an :class:`.Executable` expression
2331 such as :func:`_expression.select`).
2332
2333 :param params:
2334 Optional dictionary, or list of dictionaries, containing
2335 bound parameter values. If a single dictionary, single-row
2336 execution occurs; if a list of dictionaries, an
2337 "executemany" will be invoked. The keys in each dictionary
2338 must correspond to parameter names present in the statement.
2339
2340 :param execution_options: optional dictionary of execution options,
2341 which will be associated with the statement execution. This
2342 dictionary can provide a subset of the options that are accepted
2343 by :meth:`_engine.Connection.execution_options`, and may also
2344 provide additional options understood only in an ORM context.
2345
2346 .. seealso::
2347
2348 :ref:`orm_queryguide_execution_options` - ORM-specific execution
2349 options
2350
2351 :param bind_arguments: dictionary of additional arguments to determine
2352 the bind. May include "mapper", "bind", or other custom arguments.
2353 Contents of this dictionary are passed to the
2354 :meth:`.Session.get_bind` method.
2355
2356 :return: a :class:`_engine.Result` object.
2357
2358
2359 """
2360 return self._execute_internal(
2361 statement,
2362 params,
2363 execution_options=execution_options,
2364 bind_arguments=bind_arguments,
2365 _parent_execute_state=_parent_execute_state,
2366 _add_event=_add_event,
2367 )
2368
2369 @overload
2370 def scalar(
2371 self,
2372 statement: TypedReturnsRows[_T],
2373 params: Optional[_CoreSingleExecuteParams] = None,
2374 *,
2375 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2376 bind_arguments: Optional[_BindArguments] = None,
2377 **kw: Any,
2378 ) -> Optional[_T]: ...
2379
2380 @overload
2381 def scalar(
2382 self,
2383 statement: Executable,
2384 params: Optional[_CoreSingleExecuteParams] = None,
2385 *,
2386 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2387 bind_arguments: Optional[_BindArguments] = None,
2388 **kw: Any,
2389 ) -> Any: ...
2390
2391 def scalar(
2392 self,
2393 statement: Executable,
2394 params: Optional[_CoreSingleExecuteParams] = None,
2395 *,
2396 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2397 bind_arguments: Optional[_BindArguments] = None,
2398 **kw: Any,
2399 ) -> Any:
2400 """Execute a statement and return a scalar result.
2401
2402 Usage and parameters are the same as that of
2403 :meth:`_orm.Session.execute`; the return result is a scalar Python
2404 value.
2405
2406 """
2407
2408 return self._execute_internal(
2409 statement,
2410 params,
2411 execution_options=execution_options,
2412 bind_arguments=bind_arguments,
2413 _scalar_result=True,
2414 **kw,
2415 )
2416
2417 @overload
2418 def scalars(
2419 self,
2420 statement: TypedReturnsRows[_T],
2421 params: Optional[_CoreAnyExecuteParams] = None,
2422 *,
2423 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2424 bind_arguments: Optional[_BindArguments] = None,
2425 **kw: Any,
2426 ) -> ScalarResult[_T]: ...
2427
2428 @overload
2429 def scalars(
2430 self,
2431 statement: Executable,
2432 params: Optional[_CoreAnyExecuteParams] = None,
2433 *,
2434 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2435 bind_arguments: Optional[_BindArguments] = None,
2436 **kw: Any,
2437 ) -> ScalarResult[Any]: ...
2438
2439 def scalars(
2440 self,
2441 statement: Executable,
2442 params: Optional[_CoreAnyExecuteParams] = None,
2443 *,
2444 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2445 bind_arguments: Optional[_BindArguments] = None,
2446 **kw: Any,
2447 ) -> ScalarResult[Any]:
2448 """Execute a statement and return the results as scalars.
2449
2450 Usage and parameters are the same as that of
2451 :meth:`_orm.Session.execute`; the return result is a
2452 :class:`_result.ScalarResult` filtering object which
2453 will return single elements rather than :class:`_row.Row` objects.
2454
2455 :return: a :class:`_result.ScalarResult` object
2456
2457 .. versionadded:: 1.4.24 Added :meth:`_orm.Session.scalars`
2458
2459 .. versionadded:: 1.4.26 Added :meth:`_orm.scoped_session.scalars`
2460
2461 .. seealso::
2462
2463 :ref:`orm_queryguide_select_orm_entities` - contrasts the behavior
2464 of :meth:`_orm.Session.execute` to :meth:`_orm.Session.scalars`
2465
2466 """
2467
2468 return self._execute_internal(
2469 statement,
2470 params=params,
2471 execution_options=execution_options,
2472 bind_arguments=bind_arguments,
2473 _scalar_result=False, # mypy appreciates this
2474 **kw,
2475 ).scalars()
2476
2477 def close(self) -> None:
2478 """Close out the transactional resources and ORM objects used by this
2479 :class:`_orm.Session`.
2480
2481 This expunges all ORM objects associated with this
2482 :class:`_orm.Session`, ends any transaction in progress and
2483 :term:`releases` any :class:`_engine.Connection` objects which this
2484 :class:`_orm.Session` itself has checked out from associated
2485 :class:`_engine.Engine` objects. The operation then leaves the
2486 :class:`_orm.Session` in a state which it may be used again.
2487
2488 .. tip::
2489
2490 In the default running mode the :meth:`_orm.Session.close`
2491 method **does not prevent the Session from being used again**.
2492 The :class:`_orm.Session` itself does not actually have a
2493 distinct "closed" state; it merely means
2494 the :class:`_orm.Session` will release all database connections
2495 and ORM objects.
2496
2497 Setting the parameter :paramref:`_orm.Session.close_resets_only`
2498 to ``False`` will instead make the ``close`` final, meaning that
2499 any further action on the session will be forbidden.
2500
2501 .. versionchanged:: 1.4 The :meth:`.Session.close` method does not
2502 immediately create a new :class:`.SessionTransaction` object;
2503 instead, the new :class:`.SessionTransaction` is created only if
2504 the :class:`.Session` is used again for a database operation.
2505
2506 .. seealso::
2507
2508 :ref:`session_closing` - detail on the semantics of
2509 :meth:`_orm.Session.close` and :meth:`_orm.Session.reset`.
2510
2511 :meth:`_orm.Session.reset` - a similar method that behaves like
2512 ``close()`` with the parameter
2513 :paramref:`_orm.Session.close_resets_only` set to ``True``.
2514
2515 """
2516 self._close_impl(invalidate=False)
2517
2518 def reset(self) -> None:
2519 """Close out the transactional resources and ORM objects used by this
2520 :class:`_orm.Session`, resetting the session to its initial state.
2521
2522 This method provides for same "reset-only" behavior that the
2523 :meth:`_orm.Session.close` method has provided historically, where the
2524 state of the :class:`_orm.Session` is reset as though the object were
2525 brand new, and ready to be used again.
2526 This method may then be useful for :class:`_orm.Session` objects
2527 which set :paramref:`_orm.Session.close_resets_only` to ``False``,
2528 so that "reset only" behavior is still available.
2529
2530 .. versionadded:: 2.0.22
2531
2532 .. seealso::
2533
2534 :ref:`session_closing` - detail on the semantics of
2535 :meth:`_orm.Session.close` and :meth:`_orm.Session.reset`.
2536
2537 :meth:`_orm.Session.close` - a similar method will additionally
2538 prevent re-use of the Session when the parameter
2539 :paramref:`_orm.Session.close_resets_only` is set to ``False``.
2540 """
2541 self._close_impl(invalidate=False, is_reset=True)
2542
2543 def invalidate(self) -> None:
2544 """Close this Session, using connection invalidation.
2545
2546 This is a variant of :meth:`.Session.close` that will additionally
2547 ensure that the :meth:`_engine.Connection.invalidate`
2548 method will be called on each :class:`_engine.Connection` object
2549 that is currently in use for a transaction (typically there is only
2550 one connection unless the :class:`_orm.Session` is used with
2551 multiple engines).
2552
2553 This can be called when the database is known to be in a state where
2554 the connections are no longer safe to be used.
2555
2556 Below illustrates a scenario when using `gevent
2557 <https://www.gevent.org/>`_, which can produce ``Timeout`` exceptions
2558 that may mean the underlying connection should be discarded::
2559
2560 import gevent
2561
2562 try:
2563 sess = Session()
2564 sess.add(User())
2565 sess.commit()
2566 except gevent.Timeout:
2567 sess.invalidate()
2568 raise
2569 except:
2570 sess.rollback()
2571 raise
2572
2573 The method additionally does everything that :meth:`_orm.Session.close`
2574 does, including that all ORM objects are expunged.
2575
2576 """
2577 self._close_impl(invalidate=True)
2578
2579 def _close_impl(self, invalidate: bool, is_reset: bool = False) -> None:
2580 if not is_reset and self._close_state is _SessionCloseState.ACTIVE:
2581 self._close_state = _SessionCloseState.CLOSED
2582 self.expunge_all()
2583 if self._transaction is not None:
2584 for transaction in self._transaction._iterate_self_and_parents():
2585 transaction.close(invalidate)
2586
2587 def expunge_all(self) -> None:
2588 """Remove all object instances from this ``Session``.
2589
2590 This is equivalent to calling ``expunge(obj)`` on all objects in this
2591 ``Session``.
2592
2593 """
2594
2595 all_states = self.identity_map.all_states() + list(self._new)
2596 self.identity_map._kill()
2597 self.identity_map = identity._WeakInstanceDict()
2598 self._new = {}
2599 self._deleted = {}
2600
2601 statelib.InstanceState._detach_states(all_states, self)
2602
2603 def _add_bind(self, key: _SessionBindKey, bind: _SessionBind) -> None:
2604 try:
2605 insp = inspect(key)
2606 except sa_exc.NoInspectionAvailable as err:
2607 if not isinstance(key, type):
2608 raise sa_exc.ArgumentError(
2609 "Not an acceptable bind target: %s" % key
2610 ) from err
2611 else:
2612 self.__binds[key] = bind
2613 else:
2614 if TYPE_CHECKING:
2615 assert isinstance(insp, Inspectable)
2616
2617 if isinstance(insp, TableClause):
2618 self.__binds[insp] = bind
2619 elif insp_is_mapper(insp):
2620 self.__binds[insp.class_] = bind
2621 for _selectable in insp._all_tables:
2622 self.__binds[_selectable] = bind
2623 else:
2624 raise sa_exc.ArgumentError(
2625 "Not an acceptable bind target: %s" % key
2626 )
2627
2628 def bind_mapper(
2629 self, mapper: _EntityBindKey[_O], bind: _SessionBind
2630 ) -> None:
2631 """Associate a :class:`_orm.Mapper` or arbitrary Python class with a
2632 "bind", e.g. an :class:`_engine.Engine` or
2633 :class:`_engine.Connection`.
2634
2635 The given entity is added to a lookup used by the
2636 :meth:`.Session.get_bind` method.
2637
2638 :param mapper: a :class:`_orm.Mapper` object,
2639 or an instance of a mapped
2640 class, or any Python class that is the base of a set of mapped
2641 classes.
2642
2643 :param bind: an :class:`_engine.Engine` or :class:`_engine.Connection`
2644 object.
2645
2646 .. seealso::
2647
2648 :ref:`session_partitioning`
2649
2650 :paramref:`.Session.binds`
2651
2652 :meth:`.Session.bind_table`
2653
2654
2655 """
2656 self._add_bind(mapper, bind)
2657
2658 def bind_table(self, table: TableClause, bind: _SessionBind) -> None:
2659 """Associate a :class:`_schema.Table` with a "bind", e.g. an
2660 :class:`_engine.Engine`
2661 or :class:`_engine.Connection`.
2662
2663 The given :class:`_schema.Table` is added to a lookup used by the
2664 :meth:`.Session.get_bind` method.
2665
2666 :param table: a :class:`_schema.Table` object,
2667 which is typically the target
2668 of an ORM mapping, or is present within a selectable that is
2669 mapped.
2670
2671 :param bind: an :class:`_engine.Engine` or :class:`_engine.Connection`
2672 object.
2673
2674 .. seealso::
2675
2676 :ref:`session_partitioning`
2677
2678 :paramref:`.Session.binds`
2679
2680 :meth:`.Session.bind_mapper`
2681
2682
2683 """
2684 self._add_bind(table, bind)
2685
2686 def get_bind(
2687 self,
2688 mapper: Optional[_EntityBindKey[_O]] = None,
2689 *,
2690 clause: Optional[ClauseElement] = None,
2691 bind: Optional[_SessionBind] = None,
2692 _sa_skip_events: Optional[bool] = None,
2693 _sa_skip_for_implicit_returning: bool = False,
2694 **kw: Any,
2695 ) -> Union[Engine, Connection]:
2696 """Return a "bind" to which this :class:`.Session` is bound.
2697
2698 The "bind" is usually an instance of :class:`_engine.Engine`,
2699 except in the case where the :class:`.Session` has been
2700 explicitly bound directly to a :class:`_engine.Connection`.
2701
2702 For a multiply-bound or unbound :class:`.Session`, the
2703 ``mapper`` or ``clause`` arguments are used to determine the
2704 appropriate bind to return.
2705
2706 Note that the "mapper" argument is usually present
2707 when :meth:`.Session.get_bind` is called via an ORM
2708 operation such as a :meth:`.Session.query`, each
2709 individual INSERT/UPDATE/DELETE operation within a
2710 :meth:`.Session.flush`, call, etc.
2711
2712 The order of resolution is:
2713
2714 1. if mapper given and :paramref:`.Session.binds` is present,
2715 locate a bind based first on the mapper in use, then
2716 on the mapped class in use, then on any base classes that are
2717 present in the ``__mro__`` of the mapped class, from more specific
2718 superclasses to more general.
2719 2. if clause given and ``Session.binds`` is present,
2720 locate a bind based on :class:`_schema.Table` objects
2721 found in the given clause present in ``Session.binds``.
2722 3. if ``Session.binds`` is present, return that.
2723 4. if clause given, attempt to return a bind
2724 linked to the :class:`_schema.MetaData` ultimately
2725 associated with the clause.
2726 5. if mapper given, attempt to return a bind
2727 linked to the :class:`_schema.MetaData` ultimately
2728 associated with the :class:`_schema.Table` or other
2729 selectable to which the mapper is mapped.
2730 6. No bind can be found, :exc:`~sqlalchemy.exc.UnboundExecutionError`
2731 is raised.
2732
2733 Note that the :meth:`.Session.get_bind` method can be overridden on
2734 a user-defined subclass of :class:`.Session` to provide any kind
2735 of bind resolution scheme. See the example at
2736 :ref:`session_custom_partitioning`.
2737
2738 :param mapper:
2739 Optional mapped class or corresponding :class:`_orm.Mapper` instance.
2740 The bind can be derived from a :class:`_orm.Mapper` first by
2741 consulting the "binds" map associated with this :class:`.Session`,
2742 and secondly by consulting the :class:`_schema.MetaData` associated
2743 with the :class:`_schema.Table` to which the :class:`_orm.Mapper` is
2744 mapped for a bind.
2745
2746 :param clause:
2747 A :class:`_expression.ClauseElement` (i.e.
2748 :func:`_expression.select`,
2749 :func:`_expression.text`,
2750 etc.). If the ``mapper`` argument is not present or could not
2751 produce a bind, the given expression construct will be searched
2752 for a bound element, typically a :class:`_schema.Table`
2753 associated with
2754 bound :class:`_schema.MetaData`.
2755
2756 .. seealso::
2757
2758 :ref:`session_partitioning`
2759
2760 :paramref:`.Session.binds`
2761
2762 :meth:`.Session.bind_mapper`
2763
2764 :meth:`.Session.bind_table`
2765
2766 """
2767
2768 # this function is documented as a subclassing hook, so we have
2769 # to call this method even if the return is simple
2770 if bind:
2771 return bind
2772 elif not self.__binds and self.bind:
2773 # simplest and most common case, we have a bind and no
2774 # per-mapper/table binds, we're done
2775 return self.bind
2776
2777 # we don't have self.bind and either have self.__binds
2778 # or we don't have self.__binds (which is legacy). Look at the
2779 # mapper and the clause
2780 if mapper is None and clause is None:
2781 if self.bind:
2782 return self.bind
2783 else:
2784 raise sa_exc.UnboundExecutionError(
2785 "This session is not bound to a single Engine or "
2786 "Connection, and no context was provided to locate "
2787 "a binding."
2788 )
2789
2790 # look more closely at the mapper.
2791 if mapper is not None:
2792 try:
2793 inspected_mapper = inspect(mapper)
2794 except sa_exc.NoInspectionAvailable as err:
2795 if isinstance(mapper, type):
2796 raise exc.UnmappedClassError(mapper) from err
2797 else:
2798 raise
2799 else:
2800 inspected_mapper = None
2801
2802 # match up the mapper or clause in the __binds
2803 if self.__binds:
2804 # matching mappers and selectables to entries in the
2805 # binds dictionary; supported use case.
2806 if inspected_mapper:
2807 for cls in inspected_mapper.class_.__mro__:
2808 if cls in self.__binds:
2809 return self.__binds[cls]
2810 if clause is None:
2811 clause = inspected_mapper.persist_selectable
2812
2813 if clause is not None:
2814 plugin_subject = clause._propagate_attrs.get(
2815 "plugin_subject", None
2816 )
2817
2818 if plugin_subject is not None:
2819 for cls in plugin_subject.mapper.class_.__mro__:
2820 if cls in self.__binds:
2821 return self.__binds[cls]
2822
2823 for obj in visitors.iterate(clause):
2824 if obj in self.__binds:
2825 if TYPE_CHECKING:
2826 assert isinstance(obj, Table)
2827 return self.__binds[obj]
2828
2829 # none of the __binds matched, but we have a fallback bind.
2830 # return that
2831 if self.bind:
2832 return self.bind
2833
2834 context = []
2835 if inspected_mapper is not None:
2836 context.append(f"mapper {inspected_mapper}")
2837 if clause is not None:
2838 context.append("SQL expression")
2839
2840 raise sa_exc.UnboundExecutionError(
2841 f"Could not locate a bind configured on "
2842 f'{", ".join(context)} or this Session.'
2843 )
2844
2845 @overload
2846 def query(self, _entity: _EntityType[_O]) -> Query[_O]: ...
2847
2848 @overload
2849 def query(
2850 self, _colexpr: TypedColumnsClauseRole[_T]
2851 ) -> RowReturningQuery[_T]: ...
2852
2853 # START OVERLOADED FUNCTIONS self.query RowReturningQuery 2-8
2854
2855 # code within this block is **programmatically,
2856 # statically generated** by tools/generate_tuple_map_overloads.py
2857
2858 @overload
2859 def query(
2860 self, __ent0: _TCCA[_T0], __ent1: _TCCA[_T1], /
2861 ) -> RowReturningQuery[_T0, _T1]: ...
2862
2863 @overload
2864 def query(
2865 self, __ent0: _TCCA[_T0], __ent1: _TCCA[_T1], __ent2: _TCCA[_T2], /
2866 ) -> RowReturningQuery[_T0, _T1, _T2]: ...
2867
2868 @overload
2869 def query(
2870 self,
2871 __ent0: _TCCA[_T0],
2872 __ent1: _TCCA[_T1],
2873 __ent2: _TCCA[_T2],
2874 __ent3: _TCCA[_T3],
2875 /,
2876 ) -> RowReturningQuery[_T0, _T1, _T2, _T3]: ...
2877
2878 @overload
2879 def query(
2880 self,
2881 __ent0: _TCCA[_T0],
2882 __ent1: _TCCA[_T1],
2883 __ent2: _TCCA[_T2],
2884 __ent3: _TCCA[_T3],
2885 __ent4: _TCCA[_T4],
2886 /,
2887 ) -> RowReturningQuery[_T0, _T1, _T2, _T3, _T4]: ...
2888
2889 @overload
2890 def query(
2891 self,
2892 __ent0: _TCCA[_T0],
2893 __ent1: _TCCA[_T1],
2894 __ent2: _TCCA[_T2],
2895 __ent3: _TCCA[_T3],
2896 __ent4: _TCCA[_T4],
2897 __ent5: _TCCA[_T5],
2898 /,
2899 ) -> RowReturningQuery[_T0, _T1, _T2, _T3, _T4, _T5]: ...
2900
2901 @overload
2902 def query(
2903 self,
2904 __ent0: _TCCA[_T0],
2905 __ent1: _TCCA[_T1],
2906 __ent2: _TCCA[_T2],
2907 __ent3: _TCCA[_T3],
2908 __ent4: _TCCA[_T4],
2909 __ent5: _TCCA[_T5],
2910 __ent6: _TCCA[_T6],
2911 /,
2912 ) -> RowReturningQuery[_T0, _T1, _T2, _T3, _T4, _T5, _T6]: ...
2913
2914 @overload
2915 def query(
2916 self,
2917 __ent0: _TCCA[_T0],
2918 __ent1: _TCCA[_T1],
2919 __ent2: _TCCA[_T2],
2920 __ent3: _TCCA[_T3],
2921 __ent4: _TCCA[_T4],
2922 __ent5: _TCCA[_T5],
2923 __ent6: _TCCA[_T6],
2924 __ent7: _TCCA[_T7],
2925 /,
2926 *entities: _ColumnsClauseArgument[Any],
2927 ) -> RowReturningQuery[
2928 _T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7, Unpack[TupleAny]
2929 ]: ...
2930
2931 # END OVERLOADED FUNCTIONS self.query
2932
2933 @overload
2934 def query(
2935 self, *entities: _ColumnsClauseArgument[Any], **kwargs: Any
2936 ) -> Query[Any]: ...
2937
2938 def query(
2939 self, *entities: _ColumnsClauseArgument[Any], **kwargs: Any
2940 ) -> Query[Any]:
2941 """Return a new :class:`_query.Query` object corresponding to this
2942 :class:`_orm.Session`.
2943
2944 Note that the :class:`_query.Query` object is legacy as of
2945 SQLAlchemy 2.0; the :func:`_sql.select` construct is now used
2946 to construct ORM queries.
2947
2948 .. seealso::
2949
2950 :ref:`unified_tutorial`
2951
2952 :ref:`queryguide_toplevel`
2953
2954 :ref:`query_api_toplevel` - legacy API doc
2955
2956 """
2957
2958 return self._query_cls(entities, self, **kwargs)
2959
2960 def _identity_lookup(
2961 self,
2962 mapper: Mapper[_O],
2963 primary_key_identity: Union[Any, Tuple[Any, ...]],
2964 identity_token: Any = None,
2965 passive: PassiveFlag = PassiveFlag.PASSIVE_OFF,
2966 lazy_loaded_from: Optional[InstanceState[Any]] = None,
2967 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
2968 bind_arguments: Optional[_BindArguments] = None,
2969 ) -> Union[Optional[_O], LoaderCallableStatus]:
2970 """Locate an object in the identity map.
2971
2972 Given a primary key identity, constructs an identity key and then
2973 looks in the session's identity map. If present, the object may
2974 be run through unexpiration rules (e.g. load unloaded attributes,
2975 check if was deleted).
2976
2977 e.g.::
2978
2979 obj = session._identity_lookup(inspect(SomeClass), (1,))
2980
2981 :param mapper: mapper in use
2982 :param primary_key_identity: the primary key we are searching for, as
2983 a tuple.
2984 :param identity_token: identity token that should be used to create
2985 the identity key. Used as is, however overriding subclasses can
2986 repurpose this in order to interpret the value in a special way,
2987 such as if None then look among multiple target tokens.
2988 :param passive: passive load flag passed to
2989 :func:`.loading.get_from_identity`, which impacts the behavior if
2990 the object is found; the object may be validated and/or unexpired
2991 if the flag allows for SQL to be emitted.
2992 :param lazy_loaded_from: an :class:`.InstanceState` that is
2993 specifically asking for this identity as a related identity. Used
2994 for sharding schemes where there is a correspondence between an object
2995 and a related object being lazy-loaded (or otherwise
2996 relationship-loaded).
2997
2998 :return: None if the object is not found in the identity map, *or*
2999 if the object was unexpired and found to have been deleted.
3000 if passive flags disallow SQL and the object is expired, returns
3001 PASSIVE_NO_RESULT. In all other cases the instance is returned.
3002
3003 .. versionchanged:: 1.4.0 - the :meth:`.Session._identity_lookup`
3004 method was moved from :class:`_query.Query` to
3005 :class:`.Session`, to avoid having to instantiate the
3006 :class:`_query.Query` object.
3007
3008
3009 """
3010
3011 key = mapper.identity_key_from_primary_key(
3012 primary_key_identity, identity_token=identity_token
3013 )
3014
3015 # work around: https://github.com/python/typing/discussions/1143
3016 return_value = loading.get_from_identity(self, mapper, key, passive)
3017 return return_value
3018
3019 @util.non_memoized_property
3020 @contextlib.contextmanager
3021 def no_autoflush(self) -> Iterator[Session]:
3022 """Return a context manager that disables autoflush.
3023
3024 e.g.::
3025
3026 with session.no_autoflush:
3027
3028 some_object = SomeClass()
3029 session.add(some_object)
3030 # won't autoflush
3031 some_object.related_thing = session.query(SomeRelated).first()
3032
3033 Operations that proceed within the ``with:`` block
3034 will not be subject to flushes occurring upon query
3035 access. This is useful when initializing a series
3036 of objects which involve existing database queries,
3037 where the uncompleted object should not yet be flushed.
3038
3039 """
3040 autoflush = self.autoflush
3041 self.autoflush = False
3042 try:
3043 yield self
3044 finally:
3045 self.autoflush = autoflush
3046
3047 @util.langhelpers.tag_method_for_warnings(
3048 "This warning originated from the Session 'autoflush' process, "
3049 "which was invoked automatically in response to a user-initiated "
3050 "operation. Consider using ``no_autoflush`` context manager if this "
3051 "warning happened while initializing objects.",
3052 sa_exc.SAWarning,
3053 )
3054 def _autoflush(self) -> None:
3055 if self.autoflush and not self._flushing:
3056 try:
3057 self.flush()
3058 except sa_exc.StatementError as e:
3059 # note we are reraising StatementError as opposed to
3060 # raising FlushError with "chaining" to remain compatible
3061 # with code that catches StatementError, IntegrityError,
3062 # etc.
3063 e.add_detail(
3064 "raised as a result of Query-invoked autoflush; "
3065 "consider using a session.no_autoflush block if this "
3066 "flush is occurring prematurely"
3067 )
3068 raise e.with_traceback(sys.exc_info()[2])
3069
3070 def refresh(
3071 self,
3072 instance: object,
3073 attribute_names: Optional[Iterable[str]] = None,
3074 with_for_update: ForUpdateParameter = None,
3075 ) -> None:
3076 """Expire and refresh attributes on the given instance.
3077
3078 The selected attributes will first be expired as they would when using
3079 :meth:`_orm.Session.expire`; then a SELECT statement will be issued to
3080 the database to refresh column-oriented attributes with the current
3081 value available in the current transaction.
3082
3083 :func:`_orm.relationship` oriented attributes will also be immediately
3084 loaded if they were already eagerly loaded on the object, using the
3085 same eager loading strategy that they were loaded with originally.
3086
3087 .. versionadded:: 1.4 - the :meth:`_orm.Session.refresh` method
3088 can also refresh eagerly loaded attributes.
3089
3090 :func:`_orm.relationship` oriented attributes that would normally
3091 load using the ``select`` (or "lazy") loader strategy will also
3092 load **if they are named explicitly in the attribute_names
3093 collection**, emitting a SELECT statement for the attribute using the
3094 ``immediate`` loader strategy. If lazy-loaded relationships are not
3095 named in :paramref:`_orm.Session.refresh.attribute_names`, then
3096 they remain as "lazy loaded" attributes and are not implicitly
3097 refreshed.
3098
3099 .. versionchanged:: 2.0.4 The :meth:`_orm.Session.refresh` method
3100 will now refresh lazy-loaded :func:`_orm.relationship` oriented
3101 attributes for those which are named explicitly in the
3102 :paramref:`_orm.Session.refresh.attribute_names` collection.
3103
3104 .. tip::
3105
3106 While the :meth:`_orm.Session.refresh` method is capable of
3107 refreshing both column and relationship oriented attributes, its
3108 primary focus is on refreshing of local column-oriented attributes
3109 on a single instance. For more open ended "refresh" functionality,
3110 including the ability to refresh the attributes on many objects at
3111 once while having explicit control over relationship loader
3112 strategies, use the
3113 :ref:`populate existing <orm_queryguide_populate_existing>` feature
3114 instead.
3115
3116 Note that a highly isolated transaction will return the same values as
3117 were previously read in that same transaction, regardless of changes
3118 in database state outside of that transaction. Refreshing
3119 attributes usually only makes sense at the start of a transaction
3120 where database rows have not yet been accessed.
3121
3122 :param attribute_names: optional. An iterable collection of
3123 string attribute names indicating a subset of attributes to
3124 be refreshed.
3125
3126 :param with_for_update: optional boolean ``True`` indicating FOR UPDATE
3127 should be used, or may be a dictionary containing flags to
3128 indicate a more specific set of FOR UPDATE flags for the SELECT;
3129 flags should match the parameters of
3130 :meth:`_query.Query.with_for_update`.
3131 Supersedes the :paramref:`.Session.refresh.lockmode` parameter.
3132
3133 .. seealso::
3134
3135 :ref:`session_expire` - introductory material
3136
3137 :meth:`.Session.expire`
3138
3139 :meth:`.Session.expire_all`
3140
3141 :ref:`orm_queryguide_populate_existing` - allows any ORM query
3142 to refresh objects as they would be loaded normally.
3143
3144 """
3145 try:
3146 state = attributes.instance_state(instance)
3147 except exc.NO_STATE as err:
3148 raise exc.UnmappedInstanceError(instance) from err
3149
3150 self._expire_state(state, attribute_names)
3151
3152 # this autoflush previously used to occur as a secondary effect
3153 # of the load_on_ident below. Meaning we'd organize the SELECT
3154 # based on current DB pks, then flush, then if pks changed in that
3155 # flush, crash. this was unticketed but discovered as part of
3156 # #8703. So here, autoflush up front, dont autoflush inside
3157 # load_on_ident.
3158 self._autoflush()
3159
3160 if with_for_update == {}:
3161 raise sa_exc.ArgumentError(
3162 "with_for_update should be the boolean value "
3163 "True, or a dictionary with options. "
3164 "A blank dictionary is ambiguous."
3165 )
3166
3167 with_for_update = ForUpdateArg._from_argument(with_for_update)
3168
3169 stmt: Select[Unpack[TupleAny]] = sql.select(object_mapper(instance))
3170 if (
3171 loading._load_on_ident(
3172 self,
3173 stmt,
3174 state.key,
3175 refresh_state=state,
3176 with_for_update=with_for_update,
3177 only_load_props=attribute_names,
3178 require_pk_cols=True,
3179 # technically unnecessary as we just did autoflush
3180 # above, however removes the additional unnecessary
3181 # call to _autoflush()
3182 no_autoflush=True,
3183 is_user_refresh=True,
3184 )
3185 is None
3186 ):
3187 raise sa_exc.InvalidRequestError(
3188 "Could not refresh instance '%s'" % instance_str(instance)
3189 )
3190
3191 def expire_all(self) -> None:
3192 """Expires all persistent instances within this Session.
3193
3194 When any attributes on a persistent instance is next accessed,
3195 a query will be issued using the
3196 :class:`.Session` object's current transactional context in order to
3197 load all expired attributes for the given instance. Note that
3198 a highly isolated transaction will return the same values as were
3199 previously read in that same transaction, regardless of changes
3200 in database state outside of that transaction.
3201
3202 To expire individual objects and individual attributes
3203 on those objects, use :meth:`Session.expire`.
3204
3205 The :class:`.Session` object's default behavior is to
3206 expire all state whenever the :meth:`Session.rollback`
3207 or :meth:`Session.commit` methods are called, so that new
3208 state can be loaded for the new transaction. For this reason,
3209 calling :meth:`Session.expire_all` is not usually needed,
3210 assuming the transaction is isolated.
3211
3212 .. seealso::
3213
3214 :ref:`session_expire` - introductory material
3215
3216 :meth:`.Session.expire`
3217
3218 :meth:`.Session.refresh`
3219
3220 :meth:`_orm.Query.populate_existing`
3221
3222 """
3223 for state in self.identity_map.all_states():
3224 state._expire(state.dict, self.identity_map._modified)
3225
3226 def expire(
3227 self, instance: object, attribute_names: Optional[Iterable[str]] = None
3228 ) -> None:
3229 """Expire the attributes on an instance.
3230
3231 Marks the attributes of an instance as out of date. When an expired
3232 attribute is next accessed, a query will be issued to the
3233 :class:`.Session` object's current transactional context in order to
3234 load all expired attributes for the given instance. Note that
3235 a highly isolated transaction will return the same values as were
3236 previously read in that same transaction, regardless of changes
3237 in database state outside of that transaction.
3238
3239 To expire all objects in the :class:`.Session` simultaneously,
3240 use :meth:`Session.expire_all`.
3241
3242 The :class:`.Session` object's default behavior is to
3243 expire all state whenever the :meth:`Session.rollback`
3244 or :meth:`Session.commit` methods are called, so that new
3245 state can be loaded for the new transaction. For this reason,
3246 calling :meth:`Session.expire` only makes sense for the specific
3247 case that a non-ORM SQL statement was emitted in the current
3248 transaction.
3249
3250 :param instance: The instance to be refreshed.
3251 :param attribute_names: optional list of string attribute names
3252 indicating a subset of attributes to be expired.
3253
3254 .. seealso::
3255
3256 :ref:`session_expire` - introductory material
3257
3258 :meth:`.Session.expire`
3259
3260 :meth:`.Session.refresh`
3261
3262 :meth:`_orm.Query.populate_existing`
3263
3264 """
3265 try:
3266 state = attributes.instance_state(instance)
3267 except exc.NO_STATE as err:
3268 raise exc.UnmappedInstanceError(instance) from err
3269 self._expire_state(state, attribute_names)
3270
3271 def _expire_state(
3272 self,
3273 state: InstanceState[Any],
3274 attribute_names: Optional[Iterable[str]],
3275 ) -> None:
3276 self._validate_persistent(state)
3277 if attribute_names:
3278 state._expire_attributes(state.dict, attribute_names)
3279 else:
3280 # pre-fetch the full cascade since the expire is going to
3281 # remove associations
3282 cascaded = list(
3283 state.manager.mapper.cascade_iterator("refresh-expire", state)
3284 )
3285 self._conditional_expire(state)
3286 for o, m, st_, dct_ in cascaded:
3287 self._conditional_expire(st_)
3288
3289 def _conditional_expire(
3290 self, state: InstanceState[Any], autoflush: Optional[bool] = None
3291 ) -> None:
3292 """Expire a state if persistent, else expunge if pending"""
3293
3294 if state.key:
3295 state._expire(state.dict, self.identity_map._modified)
3296 elif state in self._new:
3297 self._new.pop(state)
3298 state._detach(self)
3299
3300 def expunge(self, instance: object) -> None:
3301 """Remove the `instance` from this ``Session``.
3302
3303 This will free all internal references to the instance. Cascading
3304 will be applied according to the *expunge* cascade rule.
3305
3306 """
3307 try:
3308 state = attributes.instance_state(instance)
3309 except exc.NO_STATE as err:
3310 raise exc.UnmappedInstanceError(instance) from err
3311 if state.session_id is not self.hash_key:
3312 raise sa_exc.InvalidRequestError(
3313 "Instance %s is not present in this Session" % state_str(state)
3314 )
3315
3316 cascaded = list(
3317 state.manager.mapper.cascade_iterator("expunge", state)
3318 )
3319 self._expunge_states([state] + [st_ for o, m, st_, dct_ in cascaded])
3320
3321 def _expunge_states(
3322 self, states: Iterable[InstanceState[Any]], to_transient: bool = False
3323 ) -> None:
3324 for state in states:
3325 if state in self._new:
3326 self._new.pop(state)
3327 elif self.identity_map.contains_state(state):
3328 self.identity_map.safe_discard(state)
3329 self._deleted.pop(state, None)
3330 elif self._transaction:
3331 # state is "detached" from being deleted, but still present
3332 # in the transaction snapshot
3333 self._transaction._deleted.pop(state, None)
3334 statelib.InstanceState._detach_states(
3335 states, self, to_transient=to_transient
3336 )
3337
3338 def _register_persistent(self, states: Set[InstanceState[Any]]) -> None:
3339 """Register all persistent objects from a flush.
3340
3341 This is used both for pending objects moving to the persistent
3342 state as well as already persistent objects.
3343
3344 """
3345
3346 pending_to_persistent = self.dispatch.pending_to_persistent or None
3347 for state in states:
3348 mapper = _state_mapper(state)
3349
3350 # prevent against last minute dereferences of the object
3351 obj = state.obj()
3352 if obj is not None:
3353 instance_key = mapper._identity_key_from_state(state)
3354
3355 if (
3356 _none_set.intersection(instance_key[1])
3357 and not mapper.allow_partial_pks
3358 or _none_set.issuperset(instance_key[1])
3359 ):
3360 raise exc.FlushError(
3361 "Instance %s has a NULL identity key. If this is an "
3362 "auto-generated value, check that the database table "
3363 "allows generation of new primary key values, and "
3364 "that the mapped Column object is configured to "
3365 "expect these generated values. Ensure also that "
3366 "this flush() is not occurring at an inappropriate "
3367 "time, such as within a load() event."
3368 % state_str(state)
3369 )
3370
3371 if state.key is None:
3372 state.key = instance_key
3373 elif state.key != instance_key:
3374 # primary key switch. use safe_discard() in case another
3375 # state has already replaced this one in the identity
3376 # map (see test/orm/test_naturalpks.py ReversePKsTest)
3377 self.identity_map.safe_discard(state)
3378 trans = self._transaction
3379 assert trans is not None
3380 if state in trans._key_switches:
3381 orig_key = trans._key_switches[state][0]
3382 else:
3383 orig_key = state.key
3384 trans._key_switches[state] = (
3385 orig_key,
3386 instance_key,
3387 )
3388 state.key = instance_key
3389
3390 # there can be an existing state in the identity map
3391 # that is replaced when the primary keys of two instances
3392 # are swapped; see test/orm/test_naturalpks.py -> test_reverse
3393 old = self.identity_map.replace(state)
3394 if (
3395 old is not None
3396 and mapper._identity_key_from_state(old) == instance_key
3397 and old.obj() is not None
3398 ):
3399 util.warn(
3400 "Identity map already had an identity for %s, "
3401 "replacing it with newly flushed object. Are there "
3402 "load operations occurring inside of an event handler "
3403 "within the flush?" % (instance_key,)
3404 )
3405 state._orphaned_outside_of_session = False
3406
3407 statelib.InstanceState._commit_all_states(
3408 ((state, state.dict) for state in states), self.identity_map
3409 )
3410
3411 self._register_altered(states)
3412
3413 if pending_to_persistent is not None:
3414 for state in states.intersection(self._new):
3415 pending_to_persistent(self, state)
3416
3417 # remove from new last, might be the last strong ref
3418 for state in set(states).intersection(self._new):
3419 self._new.pop(state)
3420
3421 def _register_altered(self, states: Iterable[InstanceState[Any]]) -> None:
3422 if self._transaction:
3423 for state in states:
3424 if state in self._new:
3425 self._transaction._new[state] = True
3426 else:
3427 self._transaction._dirty[state] = True
3428
3429 def _remove_newly_deleted(
3430 self, states: Iterable[InstanceState[Any]]
3431 ) -> None:
3432 persistent_to_deleted = self.dispatch.persistent_to_deleted or None
3433 for state in states:
3434 if self._transaction:
3435 self._transaction._deleted[state] = True
3436
3437 if persistent_to_deleted is not None:
3438 # get a strong reference before we pop out of
3439 # self._deleted
3440 obj = state.obj() # noqa
3441
3442 self.identity_map.safe_discard(state)
3443 self._deleted.pop(state, None)
3444 state._deleted = True
3445 # can't call state._detach() here, because this state
3446 # is still in the transaction snapshot and needs to be
3447 # tracked as part of that
3448 if persistent_to_deleted is not None:
3449 persistent_to_deleted(self, state)
3450
3451 def add(self, instance: object, *, _warn: bool = True) -> None:
3452 """Place an object into this :class:`_orm.Session`.
3453
3454 Objects that are in the :term:`transient` state when passed to the
3455 :meth:`_orm.Session.add` method will move to the
3456 :term:`pending` state, until the next flush, at which point they
3457 will move to the :term:`persistent` state.
3458
3459 Objects that are in the :term:`detached` state when passed to the
3460 :meth:`_orm.Session.add` method will move to the :term:`persistent`
3461 state directly.
3462
3463 If the transaction used by the :class:`_orm.Session` is rolled back,
3464 objects which were transient when they were passed to
3465 :meth:`_orm.Session.add` will be moved back to the
3466 :term:`transient` state, and will no longer be present within this
3467 :class:`_orm.Session`.
3468
3469 .. seealso::
3470
3471 :meth:`_orm.Session.add_all`
3472
3473 :ref:`session_adding` - at :ref:`session_basics`
3474
3475 """
3476 if _warn and self._warn_on_events:
3477 self._flush_warning("Session.add()")
3478
3479 try:
3480 state = attributes.instance_state(instance)
3481 except exc.NO_STATE as err:
3482 raise exc.UnmappedInstanceError(instance) from err
3483
3484 self._save_or_update_state(state)
3485
3486 def add_all(self, instances: Iterable[object]) -> None:
3487 """Add the given collection of instances to this :class:`_orm.Session`.
3488
3489 See the documentation for :meth:`_orm.Session.add` for a general
3490 behavioral description.
3491
3492 .. seealso::
3493
3494 :meth:`_orm.Session.add`
3495
3496 :ref:`session_adding` - at :ref:`session_basics`
3497
3498 """
3499
3500 if self._warn_on_events:
3501 self._flush_warning("Session.add_all()")
3502
3503 for instance in instances:
3504 self.add(instance, _warn=False)
3505
3506 def _save_or_update_state(self, state: InstanceState[Any]) -> None:
3507 state._orphaned_outside_of_session = False
3508 self._save_or_update_impl(state)
3509
3510 mapper = _state_mapper(state)
3511 for o, m, st_, dct_ in mapper.cascade_iterator(
3512 "save-update", state, halt_on=self._contains_state
3513 ):
3514 self._save_or_update_impl(st_)
3515
3516 def delete(self, instance: object) -> None:
3517 """Mark an instance as deleted.
3518
3519 The object is assumed to be either :term:`persistent` or
3520 :term:`detached` when passed; after the method is called, the
3521 object will remain in the :term:`persistent` state until the next
3522 flush proceeds. During this time, the object will also be a member
3523 of the :attr:`_orm.Session.deleted` collection.
3524
3525 When the next flush proceeds, the object will move to the
3526 :term:`deleted` state, indicating a ``DELETE`` statement was emitted
3527 for its row within the current transaction. When the transaction
3528 is successfully committed,
3529 the deleted object is moved to the :term:`detached` state and is
3530 no longer present within this :class:`_orm.Session`.
3531
3532 .. seealso::
3533
3534 :ref:`session_deleting` - at :ref:`session_basics`
3535
3536 :meth:`.Session.delete_all` - multiple instance version
3537
3538 """
3539 if self._warn_on_events:
3540 self._flush_warning("Session.delete()")
3541
3542 self._delete_impl(object_state(instance), instance, head=True)
3543
3544 def delete_all(self, instances: Iterable[object]) -> None:
3545 """Calls :meth:`.Session.delete` on multiple instances.
3546
3547 .. seealso::
3548
3549 :meth:`.Session.delete` - main documentation on delete
3550
3551 .. versionadded:: 2.1
3552
3553 """
3554
3555 if self._warn_on_events:
3556 self._flush_warning("Session.delete_all()")
3557
3558 for instance in instances:
3559 self._delete_impl(object_state(instance), instance, head=True)
3560
3561 def _delete_impl(
3562 self, state: InstanceState[Any], obj: object, head: bool
3563 ) -> None:
3564 if state.key is None:
3565 if head:
3566 raise sa_exc.InvalidRequestError(
3567 "Instance '%s' is not persisted" % state_str(state)
3568 )
3569 else:
3570 return
3571
3572 to_attach = self._before_attach(state, obj)
3573
3574 if state in self._deleted:
3575 return
3576
3577 self.identity_map.add(state)
3578
3579 if to_attach:
3580 self._after_attach(state, obj)
3581
3582 if head:
3583 # grab the cascades before adding the item to the deleted list
3584 # so that autoflush does not delete the item
3585 # the strong reference to the instance itself is significant here
3586 cascade_states = list(
3587 state.manager.mapper.cascade_iterator("delete", state)
3588 )
3589 else:
3590 cascade_states = None
3591
3592 self._deleted[state] = obj
3593
3594 if head:
3595 if TYPE_CHECKING:
3596 assert cascade_states is not None
3597 for o, m, st_, dct_ in cascade_states:
3598 self._delete_impl(st_, o, False)
3599
3600 def get(
3601 self,
3602 entity: _EntityBindKey[_O],
3603 ident: _PKIdentityArgument,
3604 *,
3605 options: Optional[Sequence[ORMOption]] = None,
3606 populate_existing: bool = False,
3607 with_for_update: ForUpdateParameter = None,
3608 identity_token: Optional[Any] = None,
3609 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
3610 bind_arguments: Optional[_BindArguments] = None,
3611 ) -> Optional[_O]:
3612 """Return an instance based on the given primary key identifier,
3613 or ``None`` if not found.
3614
3615 E.g.::
3616
3617 my_user = session.get(User, 5)
3618
3619 some_object = session.get(VersionedFoo, (5, 10))
3620
3621 some_object = session.get(VersionedFoo, {"id": 5, "version_id": 10})
3622
3623 .. versionadded:: 1.4 Added :meth:`_orm.Session.get`, which is moved
3624 from the now legacy :meth:`_orm.Query.get` method.
3625
3626 :meth:`_orm.Session.get` is special in that it provides direct
3627 access to the identity map of the :class:`.Session`.
3628 If the given primary key identifier is present
3629 in the local identity map, the object is returned
3630 directly from this collection and no SQL is emitted,
3631 unless the object has been marked fully expired.
3632 If not present,
3633 a SELECT is performed in order to locate the object.
3634
3635 :meth:`_orm.Session.get` also will perform a check if
3636 the object is present in the identity map and
3637 marked as expired - a SELECT
3638 is emitted to refresh the object as well as to
3639 ensure that the row is still present.
3640 If not, :class:`~sqlalchemy.orm.exc.ObjectDeletedError` is raised.
3641
3642 :param entity: a mapped class or :class:`.Mapper` indicating the
3643 type of entity to be loaded.
3644
3645 :param ident: A scalar, tuple, or dictionary representing the
3646 primary key. For a composite (e.g. multiple column) primary key,
3647 a tuple or dictionary should be passed.
3648
3649 For a single-column primary key, the scalar calling form is typically
3650 the most expedient. If the primary key of a row is the value "5",
3651 the call looks like::
3652
3653 my_object = session.get(SomeClass, 5)
3654
3655 The tuple form contains primary key values typically in
3656 the order in which they correspond to the mapped
3657 :class:`_schema.Table`
3658 object's primary key columns, or if the
3659 :paramref:`_orm.Mapper.primary_key` configuration parameter were
3660 used, in
3661 the order used for that parameter. For example, if the primary key
3662 of a row is represented by the integer
3663 digits "5, 10" the call would look like::
3664
3665 my_object = session.get(SomeClass, (5, 10))
3666
3667 The dictionary form should include as keys the mapped attribute names
3668 corresponding to each element of the primary key. If the mapped class
3669 has the attributes ``id``, ``version_id`` as the attributes which
3670 store the object's primary key value, the call would look like::
3671
3672 my_object = session.get(SomeClass, {"id": 5, "version_id": 10})
3673
3674 :param options: optional sequence of loader options which will be
3675 applied to the query, if one is emitted.
3676
3677 :param populate_existing: causes the method to unconditionally emit
3678 a SQL query and refresh the object with the newly loaded data,
3679 regardless of whether or not the object is already present.
3680
3681 :param with_for_update: optional boolean ``True`` indicating FOR UPDATE
3682 should be used, or may be a dictionary containing flags to
3683 indicate a more specific set of FOR UPDATE flags for the SELECT;
3684 flags should match the parameters of
3685 :meth:`_query.Query.with_for_update`.
3686 Supersedes the :paramref:`.Session.refresh.lockmode` parameter.
3687
3688 :param execution_options: optional dictionary of execution options,
3689 which will be associated with the query execution if one is emitted.
3690 This dictionary can provide a subset of the options that are
3691 accepted by :meth:`_engine.Connection.execution_options`, and may
3692 also provide additional options understood only in an ORM context.
3693
3694 .. versionadded:: 1.4.29
3695
3696 .. seealso::
3697
3698 :ref:`orm_queryguide_execution_options` - ORM-specific execution
3699 options
3700
3701 :param bind_arguments: dictionary of additional arguments to determine
3702 the bind. May include "mapper", "bind", or other custom arguments.
3703 Contents of this dictionary are passed to the
3704 :meth:`.Session.get_bind` method.
3705
3706 .. versionadded:: 2.0.0rc1
3707
3708 :return: The object instance, or ``None``.
3709
3710 """ # noqa: E501
3711 return self._get_impl(
3712 entity,
3713 ident,
3714 loading._load_on_pk_identity,
3715 options=options,
3716 populate_existing=populate_existing,
3717 with_for_update=with_for_update,
3718 identity_token=identity_token,
3719 execution_options=execution_options,
3720 bind_arguments=bind_arguments,
3721 )
3722
3723 def get_one(
3724 self,
3725 entity: _EntityBindKey[_O],
3726 ident: _PKIdentityArgument,
3727 *,
3728 options: Optional[Sequence[ORMOption]] = None,
3729 populate_existing: bool = False,
3730 with_for_update: ForUpdateParameter = None,
3731 identity_token: Optional[Any] = None,
3732 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
3733 bind_arguments: Optional[_BindArguments] = None,
3734 ) -> _O:
3735 """Return exactly one instance based on the given primary key
3736 identifier, or raise an exception if not found.
3737
3738 Raises :class:`_exc.NoResultFound` if the query selects no rows.
3739
3740 For a detailed documentation of the arguments see the
3741 method :meth:`.Session.get`.
3742
3743 .. versionadded:: 2.0.22
3744
3745 :return: The object instance.
3746
3747 .. seealso::
3748
3749 :meth:`.Session.get` - equivalent method that instead
3750 returns ``None`` if no row was found with the provided primary
3751 key
3752
3753 """
3754
3755 instance = self.get(
3756 entity,
3757 ident,
3758 options=options,
3759 populate_existing=populate_existing,
3760 with_for_update=with_for_update,
3761 identity_token=identity_token,
3762 execution_options=execution_options,
3763 bind_arguments=bind_arguments,
3764 )
3765
3766 if instance is None:
3767 raise sa_exc.NoResultFound(
3768 "No row was found when one was required"
3769 )
3770
3771 return instance
3772
3773 def _get_impl(
3774 self,
3775 entity: _EntityBindKey[_O],
3776 primary_key_identity: _PKIdentityArgument,
3777 db_load_fn: Callable[..., _O],
3778 *,
3779 options: Optional[Sequence[ExecutableOption]] = None,
3780 populate_existing: bool = False,
3781 with_for_update: ForUpdateParameter = None,
3782 identity_token: Optional[Any] = None,
3783 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
3784 bind_arguments: Optional[_BindArguments] = None,
3785 ) -> Optional[_O]:
3786 # convert composite types to individual args
3787 if (
3788 is_composite_class(primary_key_identity)
3789 and type(primary_key_identity)
3790 in descriptor_props._composite_getters
3791 ):
3792 getter = descriptor_props._composite_getters[
3793 type(primary_key_identity)
3794 ]
3795 primary_key_identity = getter(primary_key_identity)
3796
3797 mapper: Optional[Mapper[_O]] = inspect(entity)
3798
3799 if mapper is None or not mapper.is_mapper:
3800 raise sa_exc.ArgumentError(
3801 "Expected mapped class or mapper, got: %r" % entity
3802 )
3803
3804 is_dict = isinstance(primary_key_identity, dict)
3805 if not is_dict:
3806 primary_key_identity = util.to_list(
3807 primary_key_identity, default=[None]
3808 )
3809
3810 if len(primary_key_identity) != len(mapper.primary_key):
3811 raise sa_exc.InvalidRequestError(
3812 "Incorrect number of values in identifier to formulate "
3813 "primary key for session.get(); primary key columns "
3814 "are %s" % ",".join("'%s'" % c for c in mapper.primary_key)
3815 )
3816
3817 if is_dict:
3818 pk_synonyms = mapper._pk_synonyms
3819
3820 if pk_synonyms:
3821 correct_keys = set(pk_synonyms).intersection(
3822 primary_key_identity
3823 )
3824
3825 if correct_keys:
3826 primary_key_identity = dict(primary_key_identity)
3827 for k in correct_keys:
3828 primary_key_identity[pk_synonyms[k]] = (
3829 primary_key_identity[k]
3830 )
3831
3832 try:
3833 primary_key_identity = list(
3834 primary_key_identity[prop.key]
3835 for prop in mapper._identity_key_props
3836 )
3837
3838 except KeyError as err:
3839 raise sa_exc.InvalidRequestError(
3840 "Incorrect names of values in identifier to formulate "
3841 "primary key for session.get(); primary key attribute "
3842 "names are %s (synonym names are also accepted)"
3843 % ",".join(
3844 "'%s'" % prop.key
3845 for prop in mapper._identity_key_props
3846 )
3847 ) from err
3848
3849 if (
3850 not populate_existing
3851 and not mapper.always_refresh
3852 and with_for_update is None
3853 ):
3854 instance = self._identity_lookup(
3855 mapper,
3856 primary_key_identity,
3857 identity_token=identity_token,
3858 execution_options=execution_options,
3859 bind_arguments=bind_arguments,
3860 )
3861
3862 if instance is not None:
3863 # reject calls for id in identity map but class
3864 # mismatch.
3865 if not isinstance(instance, mapper.class_):
3866 return None
3867 return instance
3868
3869 # TODO: this was being tested before, but this is not possible
3870 assert instance is not LoaderCallableStatus.PASSIVE_CLASS_MISMATCH
3871
3872 # set_label_style() not strictly necessary, however this will ensure
3873 # that tablename_colname style is used which at the moment is
3874 # asserted in a lot of unit tests :)
3875
3876 load_options = context.QueryContext.default_load_options
3877
3878 if populate_existing:
3879 load_options += {"_populate_existing": populate_existing}
3880 statement = sql.select(mapper).set_label_style(
3881 LABEL_STYLE_TABLENAME_PLUS_COL
3882 )
3883 if with_for_update is not None:
3884 statement._for_update_arg = ForUpdateArg._from_argument(
3885 with_for_update
3886 )
3887
3888 if options:
3889 statement = statement.options(*options)
3890 return db_load_fn(
3891 self,
3892 statement,
3893 primary_key_identity,
3894 load_options=load_options,
3895 identity_token=identity_token,
3896 execution_options=execution_options,
3897 bind_arguments=bind_arguments,
3898 )
3899
3900 def merge(
3901 self,
3902 instance: _O,
3903 *,
3904 load: bool = True,
3905 options: Optional[Sequence[ORMOption]] = None,
3906 ) -> _O:
3907 """Copy the state of a given instance into a corresponding instance
3908 within this :class:`.Session`.
3909
3910 :meth:`.Session.merge` examines the primary key attributes of the
3911 source instance, and attempts to reconcile it with an instance of the
3912 same primary key in the session. If not found locally, it attempts
3913 to load the object from the database based on primary key, and if
3914 none can be located, creates a new instance. The state of each
3915 attribute on the source instance is then copied to the target
3916 instance. The resulting target instance is then returned by the
3917 method; the original source instance is left unmodified, and
3918 un-associated with the :class:`.Session` if not already.
3919
3920 This operation cascades to associated instances if the association is
3921 mapped with ``cascade="merge"``.
3922
3923 See :ref:`unitofwork_merging` for a detailed discussion of merging.
3924
3925 :param instance: Instance to be merged.
3926 :param load: Boolean, when False, :meth:`.merge` switches into
3927 a "high performance" mode which causes it to forego emitting history
3928 events as well as all database access. This flag is used for
3929 cases such as transferring graphs of objects into a :class:`.Session`
3930 from a second level cache, or to transfer just-loaded objects
3931 into the :class:`.Session` owned by a worker thread or process
3932 without re-querying the database.
3933
3934 The ``load=False`` use case adds the caveat that the given
3935 object has to be in a "clean" state, that is, has no pending changes
3936 to be flushed - even if the incoming object is detached from any
3937 :class:`.Session`. This is so that when
3938 the merge operation populates local attributes and
3939 cascades to related objects and
3940 collections, the values can be "stamped" onto the
3941 target object as is, without generating any history or attribute
3942 events, and without the need to reconcile the incoming data with
3943 any existing related objects or collections that might not
3944 be loaded. The resulting objects from ``load=False`` are always
3945 produced as "clean", so it is only appropriate that the given objects
3946 should be "clean" as well, else this suggests a mis-use of the
3947 method.
3948 :param options: optional sequence of loader options which will be
3949 applied to the :meth:`_orm.Session.get` method when the merge
3950 operation loads the existing version of the object from the database.
3951
3952 .. versionadded:: 1.4.24
3953
3954
3955 .. seealso::
3956
3957 :func:`.make_transient_to_detached` - provides for an alternative
3958 means of "merging" a single object into the :class:`.Session`
3959
3960 :meth:`.Session.merge_all` - multiple instance version
3961
3962 """
3963
3964 if self._warn_on_events:
3965 self._flush_warning("Session.merge()")
3966
3967 if load:
3968 # flush current contents if we expect to load data
3969 self._autoflush()
3970
3971 with self.no_autoflush:
3972 return self._merge(
3973 object_state(instance),
3974 attributes.instance_dict(instance),
3975 load=load,
3976 options=options,
3977 _recursive={},
3978 _resolve_conflict_map={},
3979 )
3980
3981 def merge_all(
3982 self,
3983 instances: Iterable[_O],
3984 *,
3985 load: bool = True,
3986 options: Optional[Sequence[ORMOption]] = None,
3987 ) -> Sequence[_O]:
3988 """Calls :meth:`.Session.merge` on multiple instances.
3989
3990 .. seealso::
3991
3992 :meth:`.Session.merge` - main documentation on merge
3993
3994 .. versionadded:: 2.1
3995
3996 """
3997
3998 if self._warn_on_events:
3999 self._flush_warning("Session.merge_all()")
4000
4001 if load:
4002 # flush current contents if we expect to load data
4003 self._autoflush()
4004
4005 return [
4006 self._merge(
4007 object_state(instance),
4008 attributes.instance_dict(instance),
4009 load=load,
4010 options=options,
4011 _recursive={},
4012 _resolve_conflict_map={},
4013 )
4014 for instance in instances
4015 ]
4016
4017 def _merge(
4018 self,
4019 state: InstanceState[_O],
4020 state_dict: _InstanceDict,
4021 *,
4022 options: Optional[Sequence[ORMOption]] = None,
4023 load: bool,
4024 _recursive: Dict[Any, object],
4025 _resolve_conflict_map: Dict[_IdentityKeyType[Any], object],
4026 ) -> _O:
4027 mapper: Mapper[_O] = _state_mapper(state)
4028 if state in _recursive:
4029 return cast(_O, _recursive[state])
4030
4031 new_instance = False
4032 key = state.key
4033
4034 merged: Optional[_O]
4035
4036 if key is None:
4037 if state in self._new:
4038 util.warn(
4039 "Instance %s is already pending in this Session yet is "
4040 "being merged again; this is probably not what you want "
4041 "to do" % state_str(state)
4042 )
4043
4044 if not load:
4045 raise sa_exc.InvalidRequestError(
4046 "merge() with load=False option does not support "
4047 "objects transient (i.e. unpersisted) objects. flush() "
4048 "all changes on mapped instances before merging with "
4049 "load=False."
4050 )
4051 key = mapper._identity_key_from_state(state)
4052 key_is_persistent = LoaderCallableStatus.NEVER_SET not in key[
4053 1
4054 ] and (
4055 not _none_set.intersection(key[1])
4056 or (
4057 mapper.allow_partial_pks
4058 and not _none_set.issuperset(key[1])
4059 )
4060 )
4061 else:
4062 key_is_persistent = True
4063
4064 merged = self.identity_map.get(key)
4065
4066 if merged is None:
4067 if key_is_persistent and key in _resolve_conflict_map:
4068 merged = cast(_O, _resolve_conflict_map[key])
4069
4070 elif not load:
4071 if state.modified:
4072 raise sa_exc.InvalidRequestError(
4073 "merge() with load=False option does not support "
4074 "objects marked as 'dirty'. flush() all changes on "
4075 "mapped instances before merging with load=False."
4076 )
4077 merged = mapper.class_manager.new_instance()
4078 merged_state = attributes.instance_state(merged)
4079 merged_state.key = key
4080 self._update_impl(merged_state)
4081 new_instance = True
4082
4083 elif key_is_persistent:
4084 merged = self.get(
4085 mapper.class_,
4086 key[1],
4087 identity_token=key[2],
4088 options=options,
4089 )
4090
4091 if merged is None:
4092 merged = mapper.class_manager.new_instance()
4093 merged_state = attributes.instance_state(merged)
4094 merged_dict = attributes.instance_dict(merged)
4095 new_instance = True
4096 self._save_or_update_state(merged_state)
4097 else:
4098 merged_state = attributes.instance_state(merged)
4099 merged_dict = attributes.instance_dict(merged)
4100
4101 _recursive[state] = merged
4102 _resolve_conflict_map[key] = merged
4103
4104 # check that we didn't just pull the exact same
4105 # state out.
4106 if state is not merged_state:
4107 # version check if applicable
4108 if mapper.version_id_col is not None:
4109 existing_version = mapper._get_state_attr_by_column(
4110 state,
4111 state_dict,
4112 mapper.version_id_col,
4113 passive=PassiveFlag.PASSIVE_NO_INITIALIZE,
4114 )
4115
4116 merged_version = mapper._get_state_attr_by_column(
4117 merged_state,
4118 merged_dict,
4119 mapper.version_id_col,
4120 passive=PassiveFlag.PASSIVE_NO_INITIALIZE,
4121 )
4122
4123 if (
4124 existing_version
4125 is not LoaderCallableStatus.PASSIVE_NO_RESULT
4126 and merged_version
4127 is not LoaderCallableStatus.PASSIVE_NO_RESULT
4128 and existing_version != merged_version
4129 ):
4130 raise exc.StaleDataError(
4131 "Version id '%s' on merged state %s "
4132 "does not match existing version '%s'. "
4133 "Leave the version attribute unset when "
4134 "merging to update the most recent version."
4135 % (
4136 existing_version,
4137 state_str(merged_state),
4138 merged_version,
4139 )
4140 )
4141
4142 merged_state.load_path = state.load_path
4143 merged_state.load_options = state.load_options
4144
4145 # since we are copying load_options, we need to copy
4146 # the callables_ that would have been generated by those
4147 # load_options.
4148 # assumes that the callables we put in state.callables_
4149 # are not instance-specific (which they should not be)
4150 merged_state._copy_callables(state)
4151
4152 for prop in mapper.iterate_properties:
4153 prop.merge(
4154 self,
4155 state,
4156 state_dict,
4157 merged_state,
4158 merged_dict,
4159 load,
4160 _recursive,
4161 _resolve_conflict_map,
4162 )
4163
4164 if not load:
4165 # remove any history
4166 merged_state._commit_all(merged_dict, self.identity_map)
4167 merged_state.manager.dispatch._sa_event_merge_wo_load(
4168 merged_state, None
4169 )
4170
4171 if new_instance:
4172 merged_state.manager.dispatch.load(merged_state, None)
4173
4174 return merged
4175
4176 def _validate_persistent(self, state: InstanceState[Any]) -> None:
4177 if not self.identity_map.contains_state(state):
4178 raise sa_exc.InvalidRequestError(
4179 "Instance '%s' is not persistent within this Session"
4180 % state_str(state)
4181 )
4182
4183 def _save_impl(self, state: InstanceState[Any]) -> None:
4184 if state.key is not None:
4185 raise sa_exc.InvalidRequestError(
4186 "Object '%s' already has an identity - "
4187 "it can't be registered as pending" % state_str(state)
4188 )
4189
4190 obj = state.obj()
4191 to_attach = self._before_attach(state, obj)
4192 if state not in self._new:
4193 self._new[state] = obj
4194 state.insert_order = len(self._new)
4195 if to_attach:
4196 self._after_attach(state, obj)
4197
4198 def _update_impl(
4199 self, state: InstanceState[Any], revert_deletion: bool = False
4200 ) -> None:
4201 if state.key is None:
4202 raise sa_exc.InvalidRequestError(
4203 "Instance '%s' is not persisted" % state_str(state)
4204 )
4205
4206 if state._deleted:
4207 if revert_deletion:
4208 if not state._attached:
4209 return
4210 del state._deleted
4211 else:
4212 raise sa_exc.InvalidRequestError(
4213 "Instance '%s' has been deleted. "
4214 "Use the make_transient() "
4215 "function to send this object back "
4216 "to the transient state." % state_str(state)
4217 )
4218
4219 obj = state.obj()
4220
4221 # check for late gc
4222 if obj is None:
4223 return
4224
4225 to_attach = self._before_attach(state, obj)
4226
4227 self._deleted.pop(state, None)
4228 if revert_deletion:
4229 self.identity_map.replace(state)
4230 else:
4231 self.identity_map.add(state)
4232
4233 if to_attach:
4234 self._after_attach(state, obj)
4235 elif revert_deletion:
4236 self.dispatch.deleted_to_persistent(self, state)
4237
4238 def _save_or_update_impl(self, state: InstanceState[Any]) -> None:
4239 if state.key is None:
4240 self._save_impl(state)
4241 else:
4242 self._update_impl(state)
4243
4244 def enable_relationship_loading(self, obj: object) -> None:
4245 """Associate an object with this :class:`.Session` for related
4246 object loading.
4247
4248 .. warning::
4249
4250 :meth:`.enable_relationship_loading` exists to serve special
4251 use cases and is not recommended for general use.
4252
4253 Accesses of attributes mapped with :func:`_orm.relationship`
4254 will attempt to load a value from the database using this
4255 :class:`.Session` as the source of connectivity. The values
4256 will be loaded based on foreign key and primary key values
4257 present on this object - if not present, then those relationships
4258 will be unavailable.
4259
4260 The object will be attached to this session, but will
4261 **not** participate in any persistence operations; its state
4262 for almost all purposes will remain either "transient" or
4263 "detached", except for the case of relationship loading.
4264
4265 Also note that backrefs will often not work as expected.
4266 Altering a relationship-bound attribute on the target object
4267 may not fire off a backref event, if the effective value
4268 is what was already loaded from a foreign-key-holding value.
4269
4270 The :meth:`.Session.enable_relationship_loading` method is
4271 similar to the ``load_on_pending`` flag on :func:`_orm.relationship`.
4272 Unlike that flag, :meth:`.Session.enable_relationship_loading` allows
4273 an object to remain transient while still being able to load
4274 related items.
4275
4276 To make a transient object associated with a :class:`.Session`
4277 via :meth:`.Session.enable_relationship_loading` pending, add
4278 it to the :class:`.Session` using :meth:`.Session.add` normally.
4279 If the object instead represents an existing identity in the database,
4280 it should be merged using :meth:`.Session.merge`.
4281
4282 :meth:`.Session.enable_relationship_loading` does not improve
4283 behavior when the ORM is used normally - object references should be
4284 constructed at the object level, not at the foreign key level, so
4285 that they are present in an ordinary way before flush()
4286 proceeds. This method is not intended for general use.
4287
4288 .. seealso::
4289
4290 :paramref:`_orm.relationship.load_on_pending` - this flag
4291 allows per-relationship loading of many-to-ones on items that
4292 are pending.
4293
4294 :func:`.make_transient_to_detached` - allows for an object to
4295 be added to a :class:`.Session` without SQL emitted, which then
4296 will unexpire attributes on access.
4297
4298 """
4299 try:
4300 state = attributes.instance_state(obj)
4301 except exc.NO_STATE as err:
4302 raise exc.UnmappedInstanceError(obj) from err
4303
4304 to_attach = self._before_attach(state, obj)
4305 state._load_pending = True
4306 if to_attach:
4307 self._after_attach(state, obj)
4308
4309 def _before_attach(self, state: InstanceState[Any], obj: object) -> bool:
4310 self._autobegin_t()
4311
4312 if state.session_id == self.hash_key:
4313 return False
4314
4315 if state.session_id and state.session_id in _sessions:
4316 raise sa_exc.InvalidRequestError(
4317 "Object '%s' is already attached to session '%s' "
4318 "(this is '%s')"
4319 % (state_str(state), state.session_id, self.hash_key)
4320 )
4321
4322 self.dispatch.before_attach(self, state)
4323
4324 return True
4325
4326 def _after_attach(self, state: InstanceState[Any], obj: object) -> None:
4327 state.session_id = self.hash_key
4328 if state.modified and state._strong_obj is None:
4329 state._strong_obj = obj
4330 self.dispatch.after_attach(self, state)
4331
4332 if state.key:
4333 self.dispatch.detached_to_persistent(self, state)
4334 else:
4335 self.dispatch.transient_to_pending(self, state)
4336
4337 def __contains__(self, instance: object) -> bool:
4338 """Return True if the instance is associated with this session.
4339
4340 The instance may be pending or persistent within the Session for a
4341 result of True.
4342
4343 """
4344 try:
4345 state = attributes.instance_state(instance)
4346 except exc.NO_STATE as err:
4347 raise exc.UnmappedInstanceError(instance) from err
4348 return self._contains_state(state)
4349
4350 def __iter__(self) -> Iterator[object]:
4351 """Iterate over all pending or persistent instances within this
4352 Session.
4353
4354 """
4355 return iter(
4356 list(self._new.values()) + list(self.identity_map.values())
4357 )
4358
4359 def _contains_state(self, state: InstanceState[Any]) -> bool:
4360 return state in self._new or self.identity_map.contains_state(state)
4361
4362 def flush(self, objects: Optional[Sequence[Any]] = None) -> None:
4363 """Flush all the object changes to the database.
4364
4365 Writes out all pending object creations, deletions and modifications
4366 to the database as INSERTs, DELETEs, UPDATEs, etc. Operations are
4367 automatically ordered by the Session's unit of work dependency
4368 solver.
4369
4370 Database operations will be issued in the current transactional
4371 context and do not affect the state of the transaction, unless an
4372 error occurs, in which case the entire transaction is rolled back.
4373 You may flush() as often as you like within a transaction to move
4374 changes from Python to the database's transaction buffer.
4375
4376 :param objects: Optional; restricts the flush operation to operate
4377 only on elements that are in the given collection.
4378
4379 This feature is for an extremely narrow set of use cases where
4380 particular objects may need to be operated upon before the
4381 full flush() occurs. It is not intended for general use.
4382
4383 .. deprecated:: 2.1
4384
4385 """
4386
4387 if self._flushing:
4388 raise sa_exc.InvalidRequestError("Session is already flushing")
4389
4390 if self._is_clean():
4391 return
4392 try:
4393 self._flushing = True
4394 self._flush(objects)
4395 finally:
4396 self._flushing = False
4397
4398 def _flush_warning(self, method: Any) -> None:
4399 util.warn(
4400 "Usage of the '%s' operation is not currently supported "
4401 "within the execution stage of the flush process. "
4402 "Results may not be consistent. Consider using alternative "
4403 "event listeners or connection-level operations instead." % method
4404 )
4405
4406 def _is_clean(self) -> bool:
4407 return (
4408 not self.identity_map.check_modified()
4409 and not self._deleted
4410 and not self._new
4411 )
4412
4413 # have this here since it otherwise causes issues with the proxy
4414 # method generation
4415 @deprecated_params(
4416 objects=(
4417 "2.1",
4418 "The `objects` parameter of `Session.flush` is deprecated",
4419 )
4420 )
4421 def _flush(self, objects: Optional[Sequence[object]] = None) -> None:
4422 dirty = self._dirty_states
4423 if not dirty and not self._deleted and not self._new:
4424 self.identity_map._modified.clear()
4425 return
4426
4427 flush_context = UOWTransaction(self)
4428
4429 if self.dispatch.before_flush:
4430 self.dispatch.before_flush(self, flush_context, objects)
4431 # re-establish "dirty states" in case the listeners
4432 # added
4433 dirty = self._dirty_states
4434
4435 deleted = set(self._deleted)
4436 new = set(self._new)
4437
4438 dirty = set(dirty).difference(deleted)
4439
4440 # create the set of all objects we want to operate upon
4441 if objects:
4442 # specific list passed in
4443 objset = set()
4444 for o in objects:
4445 try:
4446 state = attributes.instance_state(o)
4447
4448 except exc.NO_STATE as err:
4449 raise exc.UnmappedInstanceError(o) from err
4450 objset.add(state)
4451 else:
4452 objset = None
4453
4454 # store objects whose fate has been decided
4455 processed = set()
4456
4457 # put all saves/updates into the flush context. detect top-level
4458 # orphans and throw them into deleted.
4459 if objset:
4460 proc = new.union(dirty).intersection(objset).difference(deleted)
4461 else:
4462 proc = new.union(dirty).difference(deleted)
4463
4464 for state in proc:
4465 is_orphan = _state_mapper(state)._is_orphan(state)
4466
4467 is_persistent_orphan = is_orphan and state.has_identity
4468
4469 if (
4470 is_orphan
4471 and not is_persistent_orphan
4472 and state._orphaned_outside_of_session
4473 ):
4474 self._expunge_states([state])
4475 else:
4476 _reg = flush_context.register_object(
4477 state, isdelete=is_persistent_orphan
4478 )
4479 assert _reg, "Failed to add object to the flush context!"
4480 processed.add(state)
4481
4482 # put all remaining deletes into the flush context.
4483 if objset:
4484 proc = deleted.intersection(objset).difference(processed)
4485 else:
4486 proc = deleted.difference(processed)
4487 for state in proc:
4488 _reg = flush_context.register_object(state, isdelete=True)
4489 assert _reg, "Failed to add object to the flush context!"
4490
4491 if not flush_context.has_work:
4492 return
4493
4494 flush_context.transaction = transaction = self._autobegin_t()._begin()
4495 try:
4496 self._warn_on_events = True
4497 try:
4498 flush_context.execute()
4499 finally:
4500 self._warn_on_events = False
4501
4502 self.dispatch.after_flush(self, flush_context)
4503
4504 flush_context.finalize_flush_changes()
4505
4506 if not objects and self.identity_map._modified:
4507 len_ = len(self.identity_map._modified)
4508
4509 statelib.InstanceState._commit_all_states(
4510 [
4511 (state, state.dict)
4512 for state in self.identity_map._modified
4513 ],
4514 instance_dict=self.identity_map,
4515 )
4516 util.warn(
4517 "Attribute history events accumulated on %d "
4518 "previously clean instances "
4519 "within inner-flush event handlers have been "
4520 "reset, and will not result in database updates. "
4521 "Consider using set_committed_value() within "
4522 "inner-flush event handlers to avoid this warning." % len_
4523 )
4524
4525 # useful assertions:
4526 # if not objects:
4527 # assert not self.identity_map._modified
4528 # else:
4529 # assert self.identity_map._modified == \
4530 # self.identity_map._modified.difference(objects)
4531
4532 self.dispatch.after_flush_postexec(self, flush_context)
4533
4534 transaction.commit()
4535
4536 except:
4537 with util.safe_reraise():
4538 transaction.rollback(_capture_exception=True)
4539
4540 def bulk_save_objects(
4541 self,
4542 objects: Iterable[object],
4543 return_defaults: bool = False,
4544 update_changed_only: bool = True,
4545 preserve_order: bool = True,
4546 ) -> None:
4547 """Perform a bulk save of the given list of objects.
4548
4549 .. legacy::
4550
4551 This method is a legacy feature as of the 2.0 series of
4552 SQLAlchemy. For modern bulk INSERT and UPDATE, see
4553 the sections :ref:`orm_queryguide_bulk_insert` and
4554 :ref:`orm_queryguide_bulk_update`.
4555
4556 For general INSERT and UPDATE of existing ORM mapped objects,
4557 prefer standard :term:`unit of work` data management patterns,
4558 introduced in the :ref:`unified_tutorial` at
4559 :ref:`tutorial_orm_data_manipulation`. SQLAlchemy 2.0
4560 now uses :ref:`engine_insertmanyvalues` with modern dialects
4561 which solves previous issues of bulk INSERT slowness.
4562
4563 :param objects: a sequence of mapped object instances. The mapped
4564 objects are persisted as is, and are **not** associated with the
4565 :class:`.Session` afterwards.
4566
4567 For each object, whether the object is sent as an INSERT or an
4568 UPDATE is dependent on the same rules used by the :class:`.Session`
4569 in traditional operation; if the object has the
4570 :attr:`.InstanceState.key`
4571 attribute set, then the object is assumed to be "detached" and
4572 will result in an UPDATE. Otherwise, an INSERT is used.
4573
4574 In the case of an UPDATE, statements are grouped based on which
4575 attributes have changed, and are thus to be the subject of each
4576 SET clause. If ``update_changed_only`` is False, then all
4577 attributes present within each object are applied to the UPDATE
4578 statement, which may help in allowing the statements to be grouped
4579 together into a larger executemany(), and will also reduce the
4580 overhead of checking history on attributes.
4581
4582 :param return_defaults: when True, rows that are missing values which
4583 generate defaults, namely integer primary key defaults and sequences,
4584 will be inserted **one at a time**, so that the primary key value
4585 is available. In particular this will allow joined-inheritance
4586 and other multi-table mappings to insert correctly without the need
4587 to provide primary key values ahead of time; however,
4588 :paramref:`.Session.bulk_save_objects.return_defaults` **greatly
4589 reduces the performance gains** of the method overall. It is strongly
4590 advised to please use the standard :meth:`_orm.Session.add_all`
4591 approach.
4592
4593 :param update_changed_only: when True, UPDATE statements are rendered
4594 based on those attributes in each state that have logged changes.
4595 When False, all attributes present are rendered into the SET clause
4596 with the exception of primary key attributes.
4597
4598 :param preserve_order: when True, the order of inserts and updates
4599 matches exactly the order in which the objects are given. When
4600 False, common types of objects are grouped into inserts
4601 and updates, to allow for more batching opportunities.
4602
4603 .. seealso::
4604
4605 :doc:`queryguide/dml`
4606
4607 :meth:`.Session.bulk_insert_mappings`
4608
4609 :meth:`.Session.bulk_update_mappings`
4610
4611 """
4612
4613 obj_states: Iterable[InstanceState[Any]]
4614
4615 obj_states = (attributes.instance_state(obj) for obj in objects)
4616
4617 if not preserve_order:
4618 # the purpose of this sort is just so that common mappers
4619 # and persistence states are grouped together, so that groupby
4620 # will return a single group for a particular type of mapper.
4621 # it's not trying to be deterministic beyond that.
4622 obj_states = sorted(
4623 obj_states,
4624 key=lambda state: (id(state.mapper), state.key is not None),
4625 )
4626
4627 def grouping_key(
4628 state: InstanceState[_O],
4629 ) -> Tuple[Mapper[_O], bool]:
4630 return (state.mapper, state.key is not None)
4631
4632 for (mapper, isupdate), states in itertools.groupby(
4633 obj_states, grouping_key
4634 ):
4635 self._bulk_save_mappings(
4636 mapper,
4637 states,
4638 isupdate=isupdate,
4639 isstates=True,
4640 return_defaults=return_defaults,
4641 update_changed_only=update_changed_only,
4642 render_nulls=False,
4643 )
4644
4645 def bulk_insert_mappings(
4646 self,
4647 mapper: Mapper[Any],
4648 mappings: Iterable[Dict[str, Any]],
4649 return_defaults: bool = False,
4650 render_nulls: bool = False,
4651 ) -> None:
4652 """Perform a bulk insert of the given list of mapping dictionaries.
4653
4654 .. legacy::
4655
4656 This method is a legacy feature as of the 2.0 series of
4657 SQLAlchemy. For modern bulk INSERT and UPDATE, see
4658 the sections :ref:`orm_queryguide_bulk_insert` and
4659 :ref:`orm_queryguide_bulk_update`. The 2.0 API shares
4660 implementation details with this method and adds new features
4661 as well.
4662
4663 :param mapper: a mapped class, or the actual :class:`_orm.Mapper`
4664 object,
4665 representing the single kind of object represented within the mapping
4666 list.
4667
4668 :param mappings: a sequence of dictionaries, each one containing the
4669 state of the mapped row to be inserted, in terms of the attribute
4670 names on the mapped class. If the mapping refers to multiple tables,
4671 such as a joined-inheritance mapping, each dictionary must contain all
4672 keys to be populated into all tables.
4673
4674 :param return_defaults: when True, the INSERT process will be altered
4675 to ensure that newly generated primary key values will be fetched.
4676 The rationale for this parameter is typically to enable
4677 :ref:`Joined Table Inheritance <joined_inheritance>` mappings to
4678 be bulk inserted.
4679
4680 .. note:: for backends that don't support RETURNING, the
4681 :paramref:`_orm.Session.bulk_insert_mappings.return_defaults`
4682 parameter can significantly decrease performance as INSERT
4683 statements can no longer be batched. See
4684 :ref:`engine_insertmanyvalues`
4685 for background on which backends are affected.
4686
4687 :param render_nulls: When True, a value of ``None`` will result
4688 in a NULL value being included in the INSERT statement, rather
4689 than the column being omitted from the INSERT. This allows all
4690 the rows being INSERTed to have the identical set of columns which
4691 allows the full set of rows to be batched to the DBAPI. Normally,
4692 each column-set that contains a different combination of NULL values
4693 than the previous row must omit a different series of columns from
4694 the rendered INSERT statement, which means it must be emitted as a
4695 separate statement. By passing this flag, the full set of rows
4696 are guaranteed to be batchable into one batch; the cost however is
4697 that server-side defaults which are invoked by an omitted column will
4698 be skipped, so care must be taken to ensure that these are not
4699 necessary.
4700
4701 .. warning::
4702
4703 When this flag is set, **server side default SQL values will
4704 not be invoked** for those columns that are inserted as NULL;
4705 the NULL value will be sent explicitly. Care must be taken
4706 to ensure that no server-side default functions need to be
4707 invoked for the operation as a whole.
4708
4709 .. seealso::
4710
4711 :doc:`queryguide/dml`
4712
4713 :meth:`.Session.bulk_save_objects`
4714
4715 :meth:`.Session.bulk_update_mappings`
4716
4717 """
4718 self._bulk_save_mappings(
4719 mapper,
4720 mappings,
4721 isupdate=False,
4722 isstates=False,
4723 return_defaults=return_defaults,
4724 update_changed_only=False,
4725 render_nulls=render_nulls,
4726 )
4727
4728 def bulk_update_mappings(
4729 self, mapper: Mapper[Any], mappings: Iterable[Dict[str, Any]]
4730 ) -> None:
4731 """Perform a bulk update of the given list of mapping dictionaries.
4732
4733 .. legacy::
4734
4735 This method is a legacy feature as of the 2.0 series of
4736 SQLAlchemy. For modern bulk INSERT and UPDATE, see
4737 the sections :ref:`orm_queryguide_bulk_insert` and
4738 :ref:`orm_queryguide_bulk_update`. The 2.0 API shares
4739 implementation details with this method and adds new features
4740 as well.
4741
4742 :param mapper: a mapped class, or the actual :class:`_orm.Mapper`
4743 object,
4744 representing the single kind of object represented within the mapping
4745 list.
4746
4747 :param mappings: a sequence of dictionaries, each one containing the
4748 state of the mapped row to be updated, in terms of the attribute names
4749 on the mapped class. If the mapping refers to multiple tables, such
4750 as a joined-inheritance mapping, each dictionary may contain keys
4751 corresponding to all tables. All those keys which are present and
4752 are not part of the primary key are applied to the SET clause of the
4753 UPDATE statement; the primary key values, which are required, are
4754 applied to the WHERE clause.
4755
4756
4757 .. seealso::
4758
4759 :doc:`queryguide/dml`
4760
4761 :meth:`.Session.bulk_insert_mappings`
4762
4763 :meth:`.Session.bulk_save_objects`
4764
4765 """
4766 self._bulk_save_mappings(
4767 mapper,
4768 mappings,
4769 isupdate=True,
4770 isstates=False,
4771 return_defaults=False,
4772 update_changed_only=False,
4773 render_nulls=False,
4774 )
4775
4776 def _bulk_save_mappings(
4777 self,
4778 mapper: Mapper[_O],
4779 mappings: Union[Iterable[InstanceState[_O]], Iterable[Dict[str, Any]]],
4780 *,
4781 isupdate: bool,
4782 isstates: bool,
4783 return_defaults: bool,
4784 update_changed_only: bool,
4785 render_nulls: bool,
4786 ) -> None:
4787 mapper = _class_to_mapper(mapper)
4788 self._flushing = True
4789
4790 transaction = self._autobegin_t()._begin()
4791 try:
4792 if isupdate:
4793 bulk_persistence._bulk_update(
4794 mapper,
4795 mappings,
4796 transaction,
4797 isstates=isstates,
4798 update_changed_only=update_changed_only,
4799 )
4800 else:
4801 bulk_persistence._bulk_insert(
4802 mapper,
4803 mappings,
4804 transaction,
4805 isstates=isstates,
4806 return_defaults=return_defaults,
4807 render_nulls=render_nulls,
4808 )
4809 transaction.commit()
4810
4811 except:
4812 with util.safe_reraise():
4813 transaction.rollback(_capture_exception=True)
4814 finally:
4815 self._flushing = False
4816
4817 def is_modified(
4818 self, instance: object, include_collections: bool = True
4819 ) -> bool:
4820 r"""Return ``True`` if the given instance has locally
4821 modified attributes.
4822
4823 This method retrieves the history for each instrumented
4824 attribute on the instance and performs a comparison of the current
4825 value to its previously flushed or committed value, if any.
4826
4827 It is in effect a more expensive and accurate
4828 version of checking for the given instance in the
4829 :attr:`.Session.dirty` collection; a full test for
4830 each attribute's net "dirty" status is performed.
4831
4832 E.g.::
4833
4834 return session.is_modified(someobject)
4835
4836 A few caveats to this method apply:
4837
4838 * Instances present in the :attr:`.Session.dirty` collection may
4839 report ``False`` when tested with this method. This is because
4840 the object may have received change events via attribute mutation,
4841 thus placing it in :attr:`.Session.dirty`, but ultimately the state
4842 is the same as that loaded from the database, resulting in no net
4843 change here.
4844 * Scalar attributes may not have recorded the previously set
4845 value when a new value was applied, if the attribute was not loaded,
4846 or was expired, at the time the new value was received - in these
4847 cases, the attribute is assumed to have a change, even if there is
4848 ultimately no net change against its database value. SQLAlchemy in
4849 most cases does not need the "old" value when a set event occurs, so
4850 it skips the expense of a SQL call if the old value isn't present,
4851 based on the assumption that an UPDATE of the scalar value is
4852 usually needed, and in those few cases where it isn't, is less
4853 expensive on average than issuing a defensive SELECT.
4854
4855 The "old" value is fetched unconditionally upon set only if the
4856 attribute container has the ``active_history`` flag set to ``True``.
4857 This flag is set typically for primary key attributes and scalar
4858 object references that are not a simple many-to-one. To set this
4859 flag for any arbitrary mapped column, use the ``active_history``
4860 argument with :func:`.column_property`.
4861
4862 :param instance: mapped instance to be tested for pending changes.
4863 :param include_collections: Indicates if multivalued collections
4864 should be included in the operation. Setting this to ``False`` is a
4865 way to detect only local-column based properties (i.e. scalar columns
4866 or many-to-one foreign keys) that would result in an UPDATE for this
4867 instance upon flush.
4868
4869 """
4870 state = object_state(instance)
4871
4872 if not state.modified:
4873 return False
4874
4875 dict_ = state.dict
4876
4877 for attr in state.manager.attributes:
4878 if (
4879 not include_collections
4880 and hasattr(attr.impl, "get_collection")
4881 ) or not hasattr(attr.impl, "get_history"):
4882 continue
4883
4884 (added, unchanged, deleted) = attr.impl.get_history(
4885 state, dict_, passive=PassiveFlag.NO_CHANGE
4886 )
4887
4888 if added or deleted:
4889 return True
4890 else:
4891 return False
4892
4893 @property
4894 def is_active(self) -> bool:
4895 """True if this :class:`.Session` not in "partial rollback" state.
4896
4897 .. versionchanged:: 1.4 The :class:`_orm.Session` no longer begins
4898 a new transaction immediately, so this attribute will be False
4899 when the :class:`_orm.Session` is first instantiated.
4900
4901 "partial rollback" state typically indicates that the flush process
4902 of the :class:`_orm.Session` has failed, and that the
4903 :meth:`_orm.Session.rollback` method must be emitted in order to
4904 fully roll back the transaction.
4905
4906 If this :class:`_orm.Session` is not in a transaction at all, the
4907 :class:`_orm.Session` will autobegin when it is first used, so in this
4908 case :attr:`_orm.Session.is_active` will return True.
4909
4910 Otherwise, if this :class:`_orm.Session` is within a transaction,
4911 and that transaction has not been rolled back internally, the
4912 :attr:`_orm.Session.is_active` will also return True.
4913
4914 .. seealso::
4915
4916 :ref:`faq_session_rollback`
4917
4918 :meth:`_orm.Session.in_transaction`
4919
4920 """
4921 return self._transaction is None or self._transaction.is_active
4922
4923 @property
4924 def _dirty_states(self) -> Iterable[InstanceState[Any]]:
4925 """The set of all persistent states considered dirty.
4926
4927 This method returns all states that were modified including
4928 those that were possibly deleted.
4929
4930 """
4931 return self.identity_map._dirty_states()
4932
4933 @property
4934 def dirty(self) -> IdentitySet:
4935 """The set of all persistent instances considered dirty.
4936
4937 E.g.::
4938
4939 some_mapped_object in session.dirty
4940
4941 Instances are considered dirty when they were modified but not
4942 deleted.
4943
4944 Note that this 'dirty' calculation is 'optimistic'; most
4945 attribute-setting or collection modification operations will
4946 mark an instance as 'dirty' and place it in this set, even if
4947 there is no net change to the attribute's value. At flush
4948 time, the value of each attribute is compared to its
4949 previously saved value, and if there's no net change, no SQL
4950 operation will occur (this is a more expensive operation so
4951 it's only done at flush time).
4952
4953 To check if an instance has actionable net changes to its
4954 attributes, use the :meth:`.Session.is_modified` method.
4955
4956 """
4957 return IdentitySet(
4958 [
4959 state.obj()
4960 for state in self._dirty_states
4961 if state not in self._deleted
4962 ]
4963 )
4964
4965 @property
4966 def deleted(self) -> IdentitySet:
4967 "The set of all instances marked as 'deleted' within this ``Session``"
4968
4969 return util.IdentitySet(list(self._deleted.values()))
4970
4971 @property
4972 def new(self) -> IdentitySet:
4973 "The set of all instances marked as 'new' within this ``Session``."
4974
4975 return util.IdentitySet(list(self._new.values()))
4976
4977
4978_S = TypeVar("_S", bound="Session")
4979
4980
4981class sessionmaker(_SessionClassMethods, Generic[_S]):
4982 """A configurable :class:`.Session` factory.
4983
4984 The :class:`.sessionmaker` factory generates new
4985 :class:`.Session` objects when called, creating them given
4986 the configurational arguments established here.
4987
4988 e.g.::
4989
4990 from sqlalchemy import create_engine
4991 from sqlalchemy.orm import sessionmaker
4992
4993 # an Engine, which the Session will use for connection
4994 # resources
4995 engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/")
4996
4997 Session = sessionmaker(engine)
4998
4999 with Session() as session:
5000 session.add(some_object)
5001 session.add(some_other_object)
5002 session.commit()
5003
5004 Context manager use is optional; otherwise, the returned
5005 :class:`_orm.Session` object may be closed explicitly via the
5006 :meth:`_orm.Session.close` method. Using a
5007 ``try:/finally:`` block is optional, however will ensure that the close
5008 takes place even if there are database errors::
5009
5010 session = Session()
5011 try:
5012 session.add(some_object)
5013 session.add(some_other_object)
5014 session.commit()
5015 finally:
5016 session.close()
5017
5018 :class:`.sessionmaker` acts as a factory for :class:`_orm.Session`
5019 objects in the same way as an :class:`_engine.Engine` acts as a factory
5020 for :class:`_engine.Connection` objects. In this way it also includes
5021 a :meth:`_orm.sessionmaker.begin` method, that provides a context
5022 manager which both begins and commits a transaction, as well as closes
5023 out the :class:`_orm.Session` when complete, rolling back the transaction
5024 if any errors occur::
5025
5026 Session = sessionmaker(engine)
5027
5028 with Session.begin() as session:
5029 session.add(some_object)
5030 session.add(some_other_object)
5031 # commits transaction, closes session
5032
5033 .. versionadded:: 1.4
5034
5035 When calling upon :class:`_orm.sessionmaker` to construct a
5036 :class:`_orm.Session`, keyword arguments may also be passed to the
5037 method; these arguments will override that of the globally configured
5038 parameters. Below we use a :class:`_orm.sessionmaker` bound to a certain
5039 :class:`_engine.Engine` to produce a :class:`_orm.Session` that is instead
5040 bound to a specific :class:`_engine.Connection` procured from that engine::
5041
5042 Session = sessionmaker(engine)
5043
5044 # bind an individual session to a connection
5045
5046 with engine.connect() as connection:
5047 with Session(bind=connection) as session:
5048 ... # work with session
5049
5050 The class also includes a method :meth:`_orm.sessionmaker.configure`, which
5051 can be used to specify additional keyword arguments to the factory, which
5052 will take effect for subsequent :class:`.Session` objects generated. This
5053 is usually used to associate one or more :class:`_engine.Engine` objects
5054 with an existing
5055 :class:`.sessionmaker` factory before it is first used::
5056
5057 # application starts, sessionmaker does not have
5058 # an engine bound yet
5059 Session = sessionmaker()
5060
5061 # ... later, when an engine URL is read from a configuration
5062 # file or other events allow the engine to be created
5063 engine = create_engine("sqlite:///foo.db")
5064 Session.configure(bind=engine)
5065
5066 sess = Session()
5067 # work with session
5068
5069 .. seealso::
5070
5071 :ref:`session_getting` - introductory text on creating
5072 sessions using :class:`.sessionmaker`.
5073
5074 """
5075
5076 class_: Type[_S]
5077
5078 @overload
5079 def __init__(
5080 self,
5081 bind: Optional[_SessionBind] = ...,
5082 *,
5083 class_: Type[_S],
5084 autoflush: bool = ...,
5085 expire_on_commit: bool = ...,
5086 info: Optional[_InfoType] = ...,
5087 **kw: Any,
5088 ): ...
5089
5090 @overload
5091 def __init__(
5092 self: "sessionmaker[Session]",
5093 bind: Optional[_SessionBind] = ...,
5094 *,
5095 autoflush: bool = ...,
5096 expire_on_commit: bool = ...,
5097 info: Optional[_InfoType] = ...,
5098 **kw: Any,
5099 ): ...
5100
5101 def __init__(
5102 self,
5103 bind: Optional[_SessionBind] = None,
5104 *,
5105 class_: Type[_S] = Session, # type: ignore
5106 autoflush: bool = True,
5107 expire_on_commit: bool = True,
5108 info: Optional[_InfoType] = None,
5109 **kw: Any,
5110 ):
5111 r"""Construct a new :class:`.sessionmaker`.
5112
5113 All arguments here except for ``class_`` correspond to arguments
5114 accepted by :class:`.Session` directly. See the
5115 :meth:`.Session.__init__` docstring for more details on parameters.
5116
5117 :param bind: a :class:`_engine.Engine` or other :class:`.Connectable`
5118 with
5119 which newly created :class:`.Session` objects will be associated.
5120 :param class\_: class to use in order to create new :class:`.Session`
5121 objects. Defaults to :class:`.Session`.
5122 :param autoflush: The autoflush setting to use with newly created
5123 :class:`.Session` objects.
5124
5125 .. seealso::
5126
5127 :ref:`session_flushing` - additional background on autoflush
5128
5129 :param expire_on_commit=True: the
5130 :paramref:`_orm.Session.expire_on_commit` setting to use
5131 with newly created :class:`.Session` objects.
5132
5133 :param info: optional dictionary of information that will be available
5134 via :attr:`.Session.info`. Note this dictionary is *updated*, not
5135 replaced, when the ``info`` parameter is specified to the specific
5136 :class:`.Session` construction operation.
5137
5138 :param \**kw: all other keyword arguments are passed to the
5139 constructor of newly created :class:`.Session` objects.
5140
5141 """
5142 kw["bind"] = bind
5143 kw["autoflush"] = autoflush
5144 kw["expire_on_commit"] = expire_on_commit
5145 if info is not None:
5146 kw["info"] = info
5147 self.kw = kw
5148 # make our own subclass of the given class, so that
5149 # events can be associated with it specifically.
5150 self.class_ = type(class_.__name__, (class_,), {})
5151
5152 def begin(self) -> contextlib.AbstractContextManager[_S]:
5153 """Produce a context manager that both provides a new
5154 :class:`_orm.Session` as well as a transaction that commits.
5155
5156
5157 e.g.::
5158
5159 Session = sessionmaker(some_engine)
5160
5161 with Session.begin() as session:
5162 session.add(some_object)
5163
5164 # commits transaction, closes session
5165
5166 .. versionadded:: 1.4
5167
5168
5169 """
5170
5171 session = self()
5172 return session._maker_context_manager()
5173
5174 def __call__(self, **local_kw: Any) -> _S:
5175 """Produce a new :class:`.Session` object using the configuration
5176 established in this :class:`.sessionmaker`.
5177
5178 In Python, the ``__call__`` method is invoked on an object when
5179 it is "called" in the same way as a function::
5180
5181 Session = sessionmaker(some_engine)
5182 session = Session() # invokes sessionmaker.__call__()
5183
5184 """
5185 for k, v in self.kw.items():
5186 if k == "info" and "info" in local_kw:
5187 d = v.copy()
5188 d.update(local_kw["info"])
5189 local_kw["info"] = d
5190 else:
5191 local_kw.setdefault(k, v)
5192 return self.class_(**local_kw)
5193
5194 def configure(self, **new_kw: Any) -> None:
5195 """(Re)configure the arguments for this sessionmaker.
5196
5197 e.g.::
5198
5199 Session = sessionmaker()
5200
5201 Session.configure(bind=create_engine("sqlite://"))
5202 """
5203 self.kw.update(new_kw)
5204
5205 def __repr__(self) -> str:
5206 return "%s(class_=%r, %s)" % (
5207 self.__class__.__name__,
5208 self.class_.__name__,
5209 ", ".join("%s=%r" % (k, v) for k, v in self.kw.items()),
5210 )
5211
5212
5213def close_all_sessions() -> None:
5214 """Close all sessions in memory.
5215
5216 This function consults a global registry of all :class:`.Session` objects
5217 and calls :meth:`.Session.close` on them, which resets them to a clean
5218 state.
5219
5220 This function is not for general use but may be useful for test suites
5221 within the teardown scheme.
5222
5223 """
5224
5225 for sess in _sessions.values():
5226 sess.close()
5227
5228
5229def make_transient(instance: object) -> None:
5230 """Alter the state of the given instance so that it is :term:`transient`.
5231
5232 .. note::
5233
5234 :func:`.make_transient` is a special-case function for
5235 advanced use cases only.
5236
5237 The given mapped instance is assumed to be in the :term:`persistent` or
5238 :term:`detached` state. The function will remove its association with any
5239 :class:`.Session` as well as its :attr:`.InstanceState.identity`. The
5240 effect is that the object will behave as though it were newly constructed,
5241 except retaining any attribute / collection values that were loaded at the
5242 time of the call. The :attr:`.InstanceState.deleted` flag is also reset
5243 if this object had been deleted as a result of using
5244 :meth:`.Session.delete`.
5245
5246 .. warning::
5247
5248 :func:`.make_transient` does **not** "unexpire" or otherwise eagerly
5249 load ORM-mapped attributes that are not currently loaded at the time
5250 the function is called. This includes attributes which:
5251
5252 * were expired via :meth:`.Session.expire`
5253
5254 * were expired as the natural effect of committing a session
5255 transaction, e.g. :meth:`.Session.commit`
5256
5257 * are normally :term:`lazy loaded` but are not currently loaded
5258
5259 * are "deferred" (see :ref:`orm_queryguide_column_deferral`) and are
5260 not yet loaded
5261
5262 * were not present in the query which loaded this object, such as that
5263 which is common in joined table inheritance and other scenarios.
5264
5265 After :func:`.make_transient` is called, unloaded attributes such
5266 as those above will normally resolve to the value ``None`` when
5267 accessed, or an empty collection for a collection-oriented attribute.
5268 As the object is transient and un-associated with any database
5269 identity, it will no longer retrieve these values.
5270
5271 .. seealso::
5272
5273 :func:`.make_transient_to_detached`
5274
5275 """
5276 state = attributes.instance_state(instance)
5277 s = _state_session(state)
5278 if s:
5279 s._expunge_states([state])
5280
5281 # remove expired state
5282 state.expired_attributes.clear()
5283
5284 # remove deferred callables
5285 if state.callables:
5286 del state.callables
5287
5288 if state.key:
5289 del state.key
5290 if state._deleted:
5291 del state._deleted
5292
5293
5294def make_transient_to_detached(instance: object) -> None:
5295 """Make the given transient instance :term:`detached`.
5296
5297 .. note::
5298
5299 :func:`.make_transient_to_detached` is a special-case function for
5300 advanced use cases only.
5301
5302 All attribute history on the given instance
5303 will be reset as though the instance were freshly loaded
5304 from a query. Missing attributes will be marked as expired.
5305 The primary key attributes of the object, which are required, will be made
5306 into the "key" of the instance.
5307
5308 The object can then be added to a session, or merged
5309 possibly with the load=False flag, at which point it will look
5310 as if it were loaded that way, without emitting SQL.
5311
5312 This is a special use case function that differs from a normal
5313 call to :meth:`.Session.merge` in that a given persistent state
5314 can be manufactured without any SQL calls.
5315
5316 .. seealso::
5317
5318 :func:`.make_transient`
5319
5320 :meth:`.Session.enable_relationship_loading`
5321
5322 """
5323 state = attributes.instance_state(instance)
5324 if state.session_id or state.key:
5325 raise sa_exc.InvalidRequestError("Given object must be transient")
5326 state.key = state.mapper._identity_key_from_state(state)
5327 if state._deleted:
5328 del state._deleted
5329 state._commit_all(state.dict)
5330 state._expire_attributes(state.dict, state.unloaded)
5331
5332
5333def object_session(instance: object) -> Optional[Session]:
5334 """Return the :class:`.Session` to which the given instance belongs.
5335
5336 This is essentially the same as the :attr:`.InstanceState.session`
5337 accessor. See that attribute for details.
5338
5339 """
5340
5341 try:
5342 state = attributes.instance_state(instance)
5343 except exc.NO_STATE as err:
5344 raise exc.UnmappedInstanceError(instance) from err
5345 else:
5346 return _state_session(state)
5347
5348
5349_new_sessionid = util.counter()