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