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