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