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