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