Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/session.py: 19%
1201 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
1# orm/session.py
2# Copyright (C) 2005-2023 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"""Provides the Session class and related utilities."""
10import itertools
11import sys
12import weakref
14from . import attributes
15from . import context
16from . import exc
17from . import identity
18from . import loading
19from . import persistence
20from . import query
21from . import state as statelib
22from .base import _class_to_mapper
23from .base import _none_set
24from .base import _state_mapper
25from .base import instance_str
26from .base import object_mapper
27from .base import object_state
28from .base import state_str
29from .unitofwork import UOWTransaction
30from .. import engine
31from .. import exc as sa_exc
32from .. import sql
33from .. import util
34from ..engine.util import TransactionalContext
35from ..inspection import inspect
36from ..sql import coercions
37from ..sql import dml
38from ..sql import roles
39from ..sql import visitors
40from ..sql.base import CompileState
41from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL
43__all__ = [
44 "Session",
45 "SessionTransaction",
46 "sessionmaker",
47 "ORMExecuteState",
48 "close_all_sessions",
49 "make_transient",
50 "make_transient_to_detached",
51 "object_session",
52]
54_sessions = weakref.WeakValueDictionary()
55"""Weak-referencing dictionary of :class:`.Session` objects.
56"""
58statelib._sessions = _sessions
61def _state_session(state):
62 """Given an :class:`.InstanceState`, return the :class:`.Session`
63 associated, if any.
64 """
65 return state.session
68class _SessionClassMethods(object):
69 """Class-level methods for :class:`.Session`, :class:`.sessionmaker`."""
71 @classmethod
72 @util.deprecated(
73 "1.3",
74 "The :meth:`.Session.close_all` method is deprecated and will be "
75 "removed in a future release. Please refer to "
76 ":func:`.session.close_all_sessions`.",
77 )
78 def close_all(cls):
79 """Close *all* sessions in memory."""
81 close_all_sessions()
83 @classmethod
84 @util.preload_module("sqlalchemy.orm.util")
85 def identity_key(cls, *args, **kwargs):
86 """Return an identity key.
88 This is an alias of :func:`.util.identity_key`.
90 """
91 return util.preloaded.orm_util.identity_key(*args, **kwargs)
93 @classmethod
94 def object_session(cls, instance):
95 """Return the :class:`.Session` to which an object belongs.
97 This is an alias of :func:`.object_session`.
99 """
101 return object_session(instance)
104ACTIVE = util.symbol("ACTIVE")
105PREPARED = util.symbol("PREPARED")
106COMMITTED = util.symbol("COMMITTED")
107DEACTIVE = util.symbol("DEACTIVE")
108CLOSED = util.symbol("CLOSED")
111class ORMExecuteState(util.MemoizedSlots):
112 """Represents a call to the :meth:`_orm.Session.execute` method, as passed
113 to the :meth:`.SessionEvents.do_orm_execute` event hook.
115 .. versionadded:: 1.4
117 .. seealso::
119 :ref:`session_execute_events` - top level documentation on how
120 to use :meth:`_orm.SessionEvents.do_orm_execute`
122 """
124 __slots__ = (
125 "session",
126 "statement",
127 "parameters",
128 "execution_options",
129 "local_execution_options",
130 "bind_arguments",
131 "_compile_state_cls",
132 "_starting_event_idx",
133 "_events_todo",
134 "_update_execution_options",
135 )
137 def __init__(
138 self,
139 session,
140 statement,
141 parameters,
142 execution_options,
143 bind_arguments,
144 compile_state_cls,
145 events_todo,
146 ):
147 self.session = session
148 self.statement = statement
149 self.parameters = parameters
150 self.local_execution_options = execution_options
151 self.execution_options = statement._execution_options.union(
152 execution_options
153 )
154 self.bind_arguments = bind_arguments
155 self._compile_state_cls = compile_state_cls
156 self._events_todo = list(events_todo)
158 def _remaining_events(self):
159 return self._events_todo[self._starting_event_idx + 1 :]
161 def invoke_statement(
162 self,
163 statement=None,
164 params=None,
165 execution_options=None,
166 bind_arguments=None,
167 ):
168 """Execute the statement represented by this
169 :class:`.ORMExecuteState`, without re-invoking events that have
170 already proceeded.
172 This method essentially performs a re-entrant execution of the current
173 statement for which the :meth:`.SessionEvents.do_orm_execute` event is
174 being currently invoked. The use case for this is for event handlers
175 that want to override how the ultimate
176 :class:`_engine.Result` object is returned, such as for schemes that
177 retrieve results from an offline cache or which concatenate results
178 from multiple executions.
180 When the :class:`_engine.Result` object is returned by the actual
181 handler function within :meth:`_orm.SessionEvents.do_orm_execute` and
182 is propagated to the calling
183 :meth:`_orm.Session.execute` method, the remainder of the
184 :meth:`_orm.Session.execute` method is preempted and the
185 :class:`_engine.Result` object is returned to the caller of
186 :meth:`_orm.Session.execute` immediately.
188 :param statement: optional statement to be invoked, in place of the
189 statement currently represented by :attr:`.ORMExecuteState.statement`.
191 :param params: optional dictionary of parameters which will be merged
192 into the existing :attr:`.ORMExecuteState.parameters` of this
193 :class:`.ORMExecuteState`.
195 :param execution_options: optional dictionary of execution options
196 will be merged into the existing
197 :attr:`.ORMExecuteState.execution_options` of this
198 :class:`.ORMExecuteState`.
200 :param bind_arguments: optional dictionary of bind_arguments
201 which will be merged amongst the current
202 :attr:`.ORMExecuteState.bind_arguments`
203 of this :class:`.ORMExecuteState`.
205 :return: a :class:`_engine.Result` object with ORM-level results.
207 .. seealso::
209 :ref:`do_orm_execute_re_executing` - background and examples on the
210 appropriate usage of :meth:`_orm.ORMExecuteState.invoke_statement`.
213 """
215 if statement is None:
216 statement = self.statement
218 _bind_arguments = dict(self.bind_arguments)
219 if bind_arguments:
220 _bind_arguments.update(bind_arguments)
221 _bind_arguments["_sa_skip_events"] = True
223 if params:
224 _params = dict(self.parameters)
225 _params.update(params)
226 else:
227 _params = self.parameters
229 _execution_options = self.local_execution_options
230 if execution_options:
231 _execution_options = _execution_options.union(execution_options)
233 return self.session.execute(
234 statement,
235 _params,
236 _execution_options,
237 _bind_arguments,
238 _parent_execute_state=self,
239 )
241 @property
242 def bind_mapper(self):
243 """Return the :class:`_orm.Mapper` that is the primary "bind" mapper.
245 For an :class:`_orm.ORMExecuteState` object invoking an ORM
246 statement, that is, the :attr:`_orm.ORMExecuteState.is_orm_statement`
247 attribute is ``True``, this attribute will return the
248 :class:`_orm.Mapper` that is considered to be the "primary" mapper
249 of the statement. The term "bind mapper" refers to the fact that
250 a :class:`_orm.Session` object may be "bound" to multiple
251 :class:`_engine.Engine` objects keyed to mapped classes, and the
252 "bind mapper" determines which of those :class:`_engine.Engine` objects
253 would be selected.
255 For a statement that is invoked against a single mapped class,
256 :attr:`_orm.ORMExecuteState.bind_mapper` is intended to be a reliable
257 way of getting this mapper.
259 .. versionadded:: 1.4.0b2
261 .. seealso::
263 :attr:`_orm.ORMExecuteState.all_mappers`
266 """
267 return self.bind_arguments.get("mapper", None)
269 @property
270 def all_mappers(self):
271 """Return a sequence of all :class:`_orm.Mapper` objects that are
272 involved at the top level of this statement.
274 By "top level" we mean those :class:`_orm.Mapper` objects that would
275 be represented in the result set rows for a :func:`_sql.select`
276 query, or for a :func:`_dml.update` or :func:`_dml.delete` query,
277 the mapper that is the main subject of the UPDATE or DELETE.
279 .. versionadded:: 1.4.0b2
281 .. seealso::
283 :attr:`_orm.ORMExecuteState.bind_mapper`
287 """
288 if not self.is_orm_statement:
289 return []
290 elif self.is_select:
291 result = []
292 seen = set()
293 for d in self.statement.column_descriptions:
294 ent = d["entity"]
295 if ent:
296 insp = inspect(ent, raiseerr=False)
297 if insp and insp.mapper and insp.mapper not in seen:
298 seen.add(insp.mapper)
299 result.append(insp.mapper)
300 return result
301 elif self.is_update or self.is_delete:
302 return [self.bind_mapper]
303 else:
304 return []
306 @property
307 def is_orm_statement(self):
308 """return True if the operation is an ORM statement.
310 This indicates that the select(), update(), or delete() being
311 invoked contains ORM entities as subjects. For a statement
312 that does not have ORM entities and instead refers only to
313 :class:`.Table` metadata, it is invoked as a Core SQL statement
314 and no ORM-level automation takes place.
316 """
317 return self._compile_state_cls is not None
319 @property
320 def is_select(self):
321 """return True if this is a SELECT operation."""
322 return self.statement.is_select
324 @property
325 def is_insert(self):
326 """return True if this is an INSERT operation."""
327 return self.statement.is_dml and self.statement.is_insert
329 @property
330 def is_update(self):
331 """return True if this is an UPDATE operation."""
332 return self.statement.is_dml and self.statement.is_update
334 @property
335 def is_delete(self):
336 """return True if this is a DELETE operation."""
337 return self.statement.is_dml and self.statement.is_delete
339 @property
340 def _is_crud(self):
341 return isinstance(self.statement, (dml.Update, dml.Delete))
343 def update_execution_options(self, **opts):
344 # TODO: no coverage
345 self.local_execution_options = self.local_execution_options.union(opts)
347 def _orm_compile_options(self):
348 if not self.is_select:
349 return None
350 try:
351 opts = self.statement._compile_options
352 except AttributeError:
353 return None
354 if opts.isinstance(context.ORMCompileState.default_compile_options):
355 return opts
356 else:
357 return None
359 @property
360 def lazy_loaded_from(self):
361 """An :class:`.InstanceState` that is using this statement execution
362 for a lazy load operation.
364 The primary rationale for this attribute is to support the horizontal
365 sharding extension, where it is available within specific query
366 execution time hooks created by this extension. To that end, the
367 attribute is only intended to be meaningful at **query execution
368 time**, and importantly not any time prior to that, including query
369 compilation time.
371 """
372 return self.load_options._lazy_loaded_from
374 @property
375 def loader_strategy_path(self):
376 """Return the :class:`.PathRegistry` for the current load path.
378 This object represents the "path" in a query along relationships
379 when a particular object or collection is being loaded.
381 """
382 opts = self._orm_compile_options()
383 if opts is not None:
384 return opts._current_path
385 else:
386 return None
388 @property
389 def is_column_load(self):
390 """Return True if the operation is refreshing column-oriented
391 attributes on an existing ORM object.
393 This occurs during operations such as :meth:`_orm.Session.refresh`,
394 as well as when an attribute deferred by :func:`_orm.defer` is
395 being loaded, or an attribute that was expired either directly
396 by :meth:`_orm.Session.expire` or via a commit operation is being
397 loaded.
399 Handlers will very likely not want to add any options to queries
400 when such an operation is occurring as the query should be a straight
401 primary key fetch which should not have any additional WHERE criteria,
402 and loader options travelling with the instance
403 will have already been added to the query.
405 .. versionadded:: 1.4.0b2
407 .. seealso::
409 :attr:`_orm.ORMExecuteState.is_relationship_load`
411 """
412 opts = self._orm_compile_options()
413 return opts is not None and opts._for_refresh_state
415 @property
416 def is_relationship_load(self):
417 """Return True if this load is loading objects on behalf of a
418 relationship.
420 This means, the loader in effect is either a LazyLoader,
421 SelectInLoader, SubqueryLoader, or similar, and the entire
422 SELECT statement being emitted is on behalf of a relationship
423 load.
425 Handlers will very likely not want to add any options to queries
426 when such an operation is occurring, as loader options are already
427 capable of being propagated to relationship loaders and should
428 be already present.
430 .. seealso::
432 :attr:`_orm.ORMExecuteState.is_column_load`
434 """
435 opts = self._orm_compile_options()
436 if opts is None:
437 return False
438 path = self.loader_strategy_path
439 return path is not None and not path.is_root
441 @property
442 def load_options(self):
443 """Return the load_options that will be used for this execution."""
445 if not self.is_select:
446 raise sa_exc.InvalidRequestError(
447 "This ORM execution is not against a SELECT statement "
448 "so there are no load options."
449 )
450 return self.execution_options.get(
451 "_sa_orm_load_options", context.QueryContext.default_load_options
452 )
454 @property
455 def update_delete_options(self):
456 """Return the update_delete_options that will be used for this
457 execution."""
459 if not self._is_crud:
460 raise sa_exc.InvalidRequestError(
461 "This ORM execution is not against an UPDATE or DELETE "
462 "statement so there are no update options."
463 )
464 return self.execution_options.get(
465 "_sa_orm_update_options",
466 persistence.BulkUDCompileState.default_update_options,
467 )
469 @property
470 def user_defined_options(self):
471 """The sequence of :class:`.UserDefinedOptions` that have been
472 associated with the statement being invoked.
474 """
475 return [
476 opt
477 for opt in self.statement._with_options
478 if not opt._is_compile_state and not opt._is_legacy_option
479 ]
482class SessionTransaction(TransactionalContext):
483 """A :class:`.Session`-level transaction.
485 :class:`.SessionTransaction` is produced from the
486 :meth:`_orm.Session.begin`
487 and :meth:`_orm.Session.begin_nested` methods. It's largely an internal
488 object that in modern use provides a context manager for session
489 transactions.
491 Documentation on interacting with :class:`_orm.SessionTransaction` is
492 at: :ref:`unitofwork_transaction`.
495 .. versionchanged:: 1.4 The scoping and API methods to work with the
496 :class:`_orm.SessionTransaction` object directly have been simplified.
498 .. seealso::
500 :ref:`unitofwork_transaction`
502 :meth:`.Session.begin`
504 :meth:`.Session.begin_nested`
506 :meth:`.Session.rollback`
508 :meth:`.Session.commit`
510 :meth:`.Session.in_transaction`
512 :meth:`.Session.in_nested_transaction`
514 :meth:`.Session.get_transaction`
516 :meth:`.Session.get_nested_transaction`
519 """
521 _rollback_exception = None
523 def __init__(
524 self,
525 session,
526 parent=None,
527 nested=False,
528 autobegin=False,
529 ):
530 TransactionalContext._trans_ctx_check(session)
532 self.session = session
533 self._connections = {}
534 self._parent = parent
535 self.nested = nested
536 if nested:
537 self._previous_nested_transaction = session._nested_transaction
538 self._state = ACTIVE
539 if not parent and nested:
540 raise sa_exc.InvalidRequestError(
541 "Can't start a SAVEPOINT transaction when no existing "
542 "transaction is in progress"
543 )
545 self._take_snapshot(autobegin=autobegin)
547 # make sure transaction is assigned before we call the
548 # dispatch
549 self.session._transaction = self
551 self.session.dispatch.after_transaction_create(self.session, self)
553 @property
554 def parent(self):
555 """The parent :class:`.SessionTransaction` of this
556 :class:`.SessionTransaction`.
558 If this attribute is ``None``, indicates this
559 :class:`.SessionTransaction` is at the top of the stack, and
560 corresponds to a real "COMMIT"/"ROLLBACK"
561 block. If non-``None``, then this is either a "subtransaction"
562 or a "nested" / SAVEPOINT transaction. If the
563 :attr:`.SessionTransaction.nested` attribute is ``True``, then
564 this is a SAVEPOINT, and if ``False``, indicates this a subtransaction.
566 .. versionadded:: 1.0.16 - use ._parent for previous versions
568 """
569 return self._parent
571 nested = False
572 """Indicates if this is a nested, or SAVEPOINT, transaction.
574 When :attr:`.SessionTransaction.nested` is True, it is expected
575 that :attr:`.SessionTransaction.parent` will be True as well.
577 """
579 @property
580 def is_active(self):
581 return self.session is not None and self._state is ACTIVE
583 def _assert_active(
584 self,
585 prepared_ok=False,
586 rollback_ok=False,
587 deactive_ok=False,
588 closed_msg="This transaction is closed",
589 ):
590 if self._state is COMMITTED:
591 raise sa_exc.InvalidRequestError(
592 "This session is in 'committed' state; no further "
593 "SQL can be emitted within this transaction."
594 )
595 elif self._state is PREPARED:
596 if not prepared_ok:
597 raise sa_exc.InvalidRequestError(
598 "This session is in 'prepared' state; no further "
599 "SQL can be emitted within this transaction."
600 )
601 elif self._state is DEACTIVE:
602 if not deactive_ok and not rollback_ok:
603 if self._rollback_exception:
604 raise sa_exc.PendingRollbackError(
605 "This Session's transaction has been rolled back "
606 "due to a previous exception during flush."
607 " To begin a new transaction with this Session, "
608 "first issue Session.rollback()."
609 " Original exception was: %s"
610 % self._rollback_exception,
611 code="7s2a",
612 )
613 elif not deactive_ok:
614 raise sa_exc.InvalidRequestError(
615 "This session is in 'inactive' state, due to the "
616 "SQL transaction being rolled back; no further "
617 "SQL can be emitted within this transaction."
618 )
619 elif self._state is CLOSED:
620 raise sa_exc.ResourceClosedError(closed_msg)
622 @property
623 def _is_transaction_boundary(self):
624 return self.nested or not self._parent
626 def connection(self, bindkey, execution_options=None, **kwargs):
627 self._assert_active()
628 bind = self.session.get_bind(bindkey, **kwargs)
629 return self._connection_for_bind(bind, execution_options)
631 def _begin(self, nested=False):
632 self._assert_active()
633 return SessionTransaction(self.session, self, nested=nested)
635 def _iterate_self_and_parents(self, upto=None):
637 current = self
638 result = ()
639 while current:
640 result += (current,)
641 if current._parent is upto:
642 break
643 elif current._parent is None:
644 raise sa_exc.InvalidRequestError(
645 "Transaction %s is not on the active transaction list"
646 % (upto)
647 )
648 else:
649 current = current._parent
651 return result
653 def _take_snapshot(self, autobegin=False):
654 if not self._is_transaction_boundary:
655 self._new = self._parent._new
656 self._deleted = self._parent._deleted
657 self._dirty = self._parent._dirty
658 self._key_switches = self._parent._key_switches
659 return
661 if not autobegin and not self.session._flushing:
662 self.session.flush()
664 self._new = weakref.WeakKeyDictionary()
665 self._deleted = weakref.WeakKeyDictionary()
666 self._dirty = weakref.WeakKeyDictionary()
667 self._key_switches = weakref.WeakKeyDictionary()
669 def _restore_snapshot(self, dirty_only=False):
670 """Restore the restoration state taken before a transaction began.
672 Corresponds to a rollback.
674 """
675 assert self._is_transaction_boundary
677 to_expunge = set(self._new).union(self.session._new)
678 self.session._expunge_states(to_expunge, to_transient=True)
680 for s, (oldkey, newkey) in self._key_switches.items():
681 # we probably can do this conditionally based on
682 # if we expunged or not, but safe_discard does that anyway
683 self.session.identity_map.safe_discard(s)
685 # restore the old key
686 s.key = oldkey
688 # now restore the object, but only if we didn't expunge
689 if s not in to_expunge:
690 self.session.identity_map.replace(s)
692 for s in set(self._deleted).union(self.session._deleted):
693 self.session._update_impl(s, revert_deletion=True)
695 assert not self.session._deleted
697 for s in self.session.identity_map.all_states():
698 if not dirty_only or s.modified or s in self._dirty:
699 s._expire(s.dict, self.session.identity_map._modified)
701 def _remove_snapshot(self):
702 """Remove the restoration state taken before a transaction began.
704 Corresponds to a commit.
706 """
707 assert self._is_transaction_boundary
709 if not self.nested and self.session.expire_on_commit:
710 for s in self.session.identity_map.all_states():
711 s._expire(s.dict, self.session.identity_map._modified)
713 statelib.InstanceState._detach_states(
714 list(self._deleted), self.session
715 )
716 self._deleted.clear()
717 elif self.nested:
718 self._parent._new.update(self._new)
719 self._parent._dirty.update(self._dirty)
720 self._parent._deleted.update(self._deleted)
721 self._parent._key_switches.update(self._key_switches)
723 def _connection_for_bind(self, bind, execution_options):
724 self._assert_active()
726 if bind in self._connections:
727 if execution_options:
728 util.warn(
729 "Connection is already established for the "
730 "given bind; execution_options ignored"
731 )
732 return self._connections[bind][0]
734 local_connect = False
735 should_commit = True
737 if self._parent:
738 conn = self._parent._connection_for_bind(bind, execution_options)
739 if not self.nested:
740 return conn
741 else:
742 if isinstance(bind, engine.Connection):
743 conn = bind
744 if conn.engine in self._connections:
745 raise sa_exc.InvalidRequestError(
746 "Session already has a Connection associated for the "
747 "given Connection's Engine"
748 )
749 else:
750 conn = bind.connect()
751 local_connect = True
753 try:
754 if execution_options:
755 conn = conn.execution_options(**execution_options)
757 if self.session.twophase and self._parent is None:
758 transaction = conn.begin_twophase()
759 elif self.nested:
760 transaction = conn.begin_nested()
761 elif conn.in_transaction():
762 # if given a future connection already in a transaction, don't
763 # commit that transaction unless it is a savepoint
764 if conn.in_nested_transaction():
765 transaction = conn.get_nested_transaction()
766 else:
767 transaction = conn.get_transaction()
768 should_commit = False
769 else:
770 transaction = conn.begin()
771 except:
772 # connection will not not be associated with this Session;
773 # close it immediately so that it isn't closed under GC
774 if local_connect:
775 conn.close()
776 raise
777 else:
778 bind_is_connection = isinstance(bind, engine.Connection)
780 self._connections[conn] = self._connections[conn.engine] = (
781 conn,
782 transaction,
783 should_commit,
784 not bind_is_connection,
785 )
786 self.session.dispatch.after_begin(self.session, self, conn)
787 return conn
789 def prepare(self):
790 if self._parent is not None or not self.session.twophase:
791 raise sa_exc.InvalidRequestError(
792 "'twophase' mode not enabled, or not root transaction; "
793 "can't prepare."
794 )
795 self._prepare_impl()
797 def _prepare_impl(self):
798 self._assert_active()
799 if self._parent is None or self.nested:
800 self.session.dispatch.before_commit(self.session)
802 stx = self.session._transaction
803 if stx is not self:
804 for subtransaction in stx._iterate_self_and_parents(upto=self):
805 subtransaction.commit()
807 if not self.session._flushing:
808 for _flush_guard in range(100):
809 if self.session._is_clean():
810 break
811 self.session.flush()
812 else:
813 raise exc.FlushError(
814 "Over 100 subsequent flushes have occurred within "
815 "session.commit() - is an after_flush() hook "
816 "creating new objects?"
817 )
819 if self._parent is None and self.session.twophase:
820 try:
821 for t in set(self._connections.values()):
822 t[1].prepare()
823 except:
824 with util.safe_reraise():
825 self.rollback()
827 self._state = PREPARED
829 def commit(self, _to_root=False):
830 self._assert_active(prepared_ok=True)
831 if self._state is not PREPARED:
832 self._prepare_impl()
834 if self._parent is None or self.nested:
835 for conn, trans, should_commit, autoclose in set(
836 self._connections.values()
837 ):
838 if should_commit:
839 trans.commit()
841 self._state = COMMITTED
842 self.session.dispatch.after_commit(self.session)
844 self._remove_snapshot()
846 self.close()
848 if _to_root and self._parent:
849 return self._parent.commit(_to_root=True)
851 return self._parent
853 def rollback(self, _capture_exception=False, _to_root=False):
854 self._assert_active(prepared_ok=True, rollback_ok=True)
856 stx = self.session._transaction
857 if stx is not self:
858 for subtransaction in stx._iterate_self_and_parents(upto=self):
859 subtransaction.close()
861 boundary = self
862 rollback_err = None
863 if self._state in (ACTIVE, PREPARED):
864 for transaction in self._iterate_self_and_parents():
865 if transaction._parent is None or transaction.nested:
866 try:
867 for t in set(transaction._connections.values()):
868 t[1].rollback()
870 transaction._state = DEACTIVE
871 self.session.dispatch.after_rollback(self.session)
872 except:
873 rollback_err = sys.exc_info()
874 finally:
875 transaction._state = DEACTIVE
876 transaction._restore_snapshot(
877 dirty_only=transaction.nested
878 )
879 boundary = transaction
880 break
881 else:
882 transaction._state = DEACTIVE
884 sess = self.session
886 if not rollback_err and not sess._is_clean():
888 # if items were added, deleted, or mutated
889 # here, we need to re-restore the snapshot
890 util.warn(
891 "Session's state has been changed on "
892 "a non-active transaction - this state "
893 "will be discarded."
894 )
895 boundary._restore_snapshot(dirty_only=boundary.nested)
897 self.close()
899 if self._parent and _capture_exception:
900 self._parent._rollback_exception = sys.exc_info()[1]
902 if rollback_err:
903 util.raise_(rollback_err[1], with_traceback=rollback_err[2])
905 sess.dispatch.after_soft_rollback(sess, self)
907 if _to_root and self._parent:
908 return self._parent.rollback(_to_root=True)
909 return self._parent
911 def close(self, invalidate=False):
912 if self.nested:
913 self.session._nested_transaction = (
914 self._previous_nested_transaction
915 )
917 self.session._transaction = self._parent
919 if self._parent is None:
920 for connection, transaction, should_commit, autoclose in set(
921 self._connections.values()
922 ):
923 if invalidate:
924 connection.invalidate()
925 if should_commit and transaction.is_active:
926 transaction.close()
927 if autoclose:
928 connection.close()
930 self._state = CLOSED
931 self.session.dispatch.after_transaction_end(self.session, self)
933 self.session = None
934 self._connections = None
936 def _get_subject(self):
937 return self.session
939 def _transaction_is_active(self):
940 return self._state is ACTIVE
942 def _transaction_is_closed(self):
943 return self._state is CLOSED
945 def _rollback_can_be_called(self):
946 return self._state not in (COMMITTED, CLOSED)
949class Session(_SessionClassMethods):
950 """Manages persistence operations for ORM-mapped objects.
952 The Session's usage paradigm is described at :doc:`/orm/session`.
955 """
957 _is_asyncio = False
959 @util.deprecated_params(
960 autocommit=(
961 "2.0",
962 "The :paramref:`.Session.autocommit` parameter is deprecated "
963 "and will be removed in SQLAlchemy version 2.0. The "
964 ':class:`_orm.Session` now features "autobegin" behavior '
965 "such that the :meth:`.Session.begin` method may be called "
966 "if a transaction has not yet been started yet. See the section "
967 ":ref:`session_explicit_begin` for background.",
968 ),
969 )
970 def __init__(
971 self,
972 bind=None,
973 autoflush=True,
974 future=False,
975 expire_on_commit=True,
976 autocommit=False,
977 twophase=False,
978 binds=None,
979 enable_baked_queries=True,
980 info=None,
981 query_cls=None,
982 ):
983 r"""Construct a new Session.
985 See also the :class:`.sessionmaker` function which is used to
986 generate a :class:`.Session`-producing callable with a given
987 set of arguments.
989 :param autocommit:
990 Defaults to ``False``. When ``True``, the
991 :class:`.Session` does not automatically begin transactions for
992 individual statement executions, will acquire connections from the
993 engine on an as-needed basis, releasing to the connection pool
994 after each statement. Flushes will begin and commit (or possibly
995 rollback) their own transaction if no transaction is present.
996 When using this mode, the
997 :meth:`.Session.begin` method may be used to explicitly start
998 transactions, but the usual "autobegin" behavior is not present.
1000 :param autoflush: When ``True``, all query operations will issue a
1001 :meth:`~.Session.flush` call to this ``Session`` before proceeding.
1002 This is a convenience feature so that :meth:`~.Session.flush` need
1003 not be called repeatedly in order for database queries to retrieve
1004 results. It's typical that ``autoflush`` is used in conjunction
1005 with ``autocommit=False``. In this scenario, explicit calls to
1006 :meth:`~.Session.flush` are rarely needed; you usually only need to
1007 call :meth:`~.Session.commit` (which flushes) to finalize changes.
1009 .. seealso::
1011 :ref:`session_flushing` - additional background on autoflush
1013 :param bind: An optional :class:`_engine.Engine` or
1014 :class:`_engine.Connection` to
1015 which this ``Session`` should be bound. When specified, all SQL
1016 operations performed by this session will execute via this
1017 connectable.
1019 :param binds: A dictionary which may specify any number of
1020 :class:`_engine.Engine` or :class:`_engine.Connection`
1021 objects as the source of
1022 connectivity for SQL operations on a per-entity basis. The keys
1023 of the dictionary consist of any series of mapped classes,
1024 arbitrary Python classes that are bases for mapped classes,
1025 :class:`_schema.Table` objects and :class:`_orm.Mapper` objects.
1026 The
1027 values of the dictionary are then instances of
1028 :class:`_engine.Engine`
1029 or less commonly :class:`_engine.Connection` objects.
1030 Operations which
1031 proceed relative to a particular mapped class will consult this
1032 dictionary for the closest matching entity in order to determine
1033 which :class:`_engine.Engine` should be used for a particular SQL
1034 operation. The complete heuristics for resolution are
1035 described at :meth:`.Session.get_bind`. Usage looks like::
1037 Session = sessionmaker(binds={
1038 SomeMappedClass: create_engine('postgresql://engine1'),
1039 SomeDeclarativeBase: create_engine('postgresql://engine2'),
1040 some_mapper: create_engine('postgresql://engine3'),
1041 some_table: create_engine('postgresql://engine4'),
1042 })
1044 .. seealso::
1046 :ref:`session_partitioning`
1048 :meth:`.Session.bind_mapper`
1050 :meth:`.Session.bind_table`
1052 :meth:`.Session.get_bind`
1055 :param \class_: Specify an alternate class other than
1056 ``sqlalchemy.orm.session.Session`` which should be used by the
1057 returned class. This is the only argument that is local to the
1058 :class:`.sessionmaker` function, and is not sent directly to the
1059 constructor for ``Session``.
1061 :param enable_baked_queries: defaults to ``True``. A flag consumed
1062 by the :mod:`sqlalchemy.ext.baked` extension to determine if
1063 "baked queries" should be cached, as is the normal operation
1064 of this extension. When set to ``False``, caching as used by
1065 this particular extension is disabled.
1067 .. versionchanged:: 1.4 The ``sqlalchemy.ext.baked`` extension is
1068 legacy and is not used by any of SQLAlchemy's internals. This
1069 flag therefore only affects applications that are making explicit
1070 use of this extension within their own code.
1072 :param expire_on_commit: Defaults to ``True``. When ``True``, all
1073 instances will be fully expired after each :meth:`~.commit`,
1074 so that all attribute/object access subsequent to a completed
1075 transaction will load from the most recent database state.
1077 .. seealso::
1079 :ref:`session_committing`
1081 :param future: if True, use 2.0 style transactional and engine
1082 behavior. Future mode includes the following behaviors:
1084 * The :class:`_orm.Session` will not use "bound" metadata in order
1085 to locate an :class:`_engine.Engine`; the engine or engines in use
1086 must be specified to the constructor of :class:`_orm.Session` or
1087 otherwise be configured against the :class:`_orm.sessionmaker`
1088 in use
1090 * The "subtransactions" feature of :meth:`_orm.Session.begin` is
1091 removed in version 2.0 and is disabled when the future flag is
1092 set.
1094 * The behavior of the :paramref:`_orm.relationship.cascade_backrefs`
1095 flag on a :func:`_orm.relationship` will always assume
1096 "False" behavior.
1098 .. versionadded:: 1.4
1100 .. seealso::
1102 :ref:`migration_20_toplevel`
1104 :param info: optional dictionary of arbitrary data to be associated
1105 with this :class:`.Session`. Is available via the
1106 :attr:`.Session.info` attribute. Note the dictionary is copied at
1107 construction time so that modifications to the per-
1108 :class:`.Session` dictionary will be local to that
1109 :class:`.Session`.
1111 :param query_cls: Class which should be used to create new Query
1112 objects, as returned by the :meth:`~.Session.query` method.
1113 Defaults to :class:`_query.Query`.
1115 :param twophase: When ``True``, all transactions will be started as
1116 a "two phase" transaction, i.e. using the "two phase" semantics
1117 of the database in use along with an XID. During a
1118 :meth:`~.commit`, after :meth:`~.flush` has been issued for all
1119 attached databases, the :meth:`~.TwoPhaseTransaction.prepare`
1120 method on each database's :class:`.TwoPhaseTransaction` will be
1121 called. This allows each database to roll back the entire
1122 transaction, before each transaction is committed.
1124 """
1125 self.identity_map = identity.WeakInstanceDict()
1127 self._new = {} # InstanceState->object, strong refs object
1128 self._deleted = {} # same
1129 self.bind = bind
1130 self.__binds = {}
1131 self._flushing = False
1132 self._warn_on_events = False
1133 self._transaction = None
1134 self._nested_transaction = None
1135 self.future = future
1136 self.hash_key = _new_sessionid()
1137 self.autoflush = autoflush
1138 self.expire_on_commit = expire_on_commit
1139 self.enable_baked_queries = enable_baked_queries
1141 if autocommit:
1142 if future:
1143 raise sa_exc.ArgumentError(
1144 "Cannot use autocommit mode with future=True."
1145 )
1146 self.autocommit = True
1147 else:
1148 self.autocommit = False
1150 self.twophase = twophase
1151 self._query_cls = query_cls if query_cls else query.Query
1152 if info:
1153 self.info.update(info)
1155 if binds is not None:
1156 for key, bind in binds.items():
1157 self._add_bind(key, bind)
1159 _sessions[self.hash_key] = self
1161 # used by sqlalchemy.engine.util.TransactionalContext
1162 _trans_context_manager = None
1164 connection_callable = None
1166 def __enter__(self):
1167 return self
1169 def __exit__(self, type_, value, traceback):
1170 self.close()
1172 @util.contextmanager
1173 def _maker_context_manager(self):
1174 with self:
1175 with self.begin():
1176 yield self
1178 @property
1179 @util.deprecated_20(
1180 ":attr:`_orm.Session.transaction`",
1181 alternative="For context manager use, use "
1182 ":meth:`_orm.Session.begin`. To access "
1183 "the current root transaction, use "
1184 ":meth:`_orm.Session.get_transaction`.",
1185 warn_on_attribute_access=True,
1186 )
1187 def transaction(self):
1188 """The current active or inactive :class:`.SessionTransaction`.
1190 May be None if no transaction has begun yet.
1192 .. versionchanged:: 1.4 the :attr:`.Session.transaction` attribute
1193 is now a read-only descriptor that also may return None if no
1194 transaction has begun yet.
1197 """
1198 return self._legacy_transaction()
1200 def _legacy_transaction(self):
1201 if not self.future:
1202 self._autobegin()
1203 return self._transaction
1205 def in_transaction(self):
1206 """Return True if this :class:`_orm.Session` has begun a transaction.
1208 .. versionadded:: 1.4
1210 .. seealso::
1212 :attr:`_orm.Session.is_active`
1215 """
1216 return self._transaction is not None
1218 def in_nested_transaction(self):
1219 """Return True if this :class:`_orm.Session` has begun a nested
1220 transaction, e.g. SAVEPOINT.
1222 .. versionadded:: 1.4
1224 """
1225 return self._nested_transaction is not None
1227 def get_transaction(self):
1228 """Return the current root transaction in progress, if any.
1230 .. versionadded:: 1.4
1232 """
1233 trans = self._transaction
1234 while trans is not None and trans._parent is not None:
1235 trans = trans._parent
1236 return trans
1238 def get_nested_transaction(self):
1239 """Return the current nested transaction in progress, if any.
1241 .. versionadded:: 1.4
1243 """
1245 return self._nested_transaction
1247 @util.memoized_property
1248 def info(self):
1249 """A user-modifiable dictionary.
1251 The initial value of this dictionary can be populated using the
1252 ``info`` argument to the :class:`.Session` constructor or
1253 :class:`.sessionmaker` constructor or factory methods. The dictionary
1254 here is always local to this :class:`.Session` and can be modified
1255 independently of all other :class:`.Session` objects.
1257 """
1258 return {}
1260 def _autobegin(self):
1261 if not self.autocommit and self._transaction is None:
1263 trans = SessionTransaction(self, autobegin=True)
1264 assert self._transaction is trans
1265 return True
1267 return False
1269 @util.deprecated_params(
1270 subtransactions=(
1271 "2.0",
1272 "The :paramref:`_orm.Session.begin.subtransactions` flag is "
1273 "deprecated and "
1274 "will be removed in SQLAlchemy version 2.0. See "
1275 "the documentation at :ref:`session_subtransactions` for "
1276 "background on a compatible alternative pattern.",
1277 )
1278 )
1279 def begin(self, subtransactions=False, nested=False, _subtrans=False):
1280 """Begin a transaction, or nested transaction,
1281 on this :class:`.Session`, if one is not already begun.
1283 The :class:`_orm.Session` object features **autobegin** behavior,
1284 so that normally it is not necessary to call the
1285 :meth:`_orm.Session.begin`
1286 method explicitly. However, it may be used in order to control
1287 the scope of when the transactional state is begun.
1289 When used to begin the outermost transaction, an error is raised
1290 if this :class:`.Session` is already inside of a transaction.
1292 :param nested: if True, begins a SAVEPOINT transaction and is
1293 equivalent to calling :meth:`~.Session.begin_nested`. For
1294 documentation on SAVEPOINT transactions, please see
1295 :ref:`session_begin_nested`.
1297 :param subtransactions: if True, indicates that this
1298 :meth:`~.Session.begin` can create a "subtransaction".
1300 :return: the :class:`.SessionTransaction` object. Note that
1301 :class:`.SessionTransaction`
1302 acts as a Python context manager, allowing :meth:`.Session.begin`
1303 to be used in a "with" block. See :ref:`session_autocommit` for
1304 an example.
1306 .. seealso::
1308 :ref:`session_autobegin`
1310 :ref:`unitofwork_transaction`
1312 :meth:`.Session.begin_nested`
1315 """
1317 if subtransactions and self.future:
1318 raise NotImplementedError(
1319 "subtransactions are not implemented in future "
1320 "Session objects."
1321 )
1323 if self._autobegin():
1324 if not subtransactions and not nested and not _subtrans:
1325 return self._transaction
1327 if self._transaction is not None:
1328 if subtransactions or _subtrans or nested:
1329 trans = self._transaction._begin(nested=nested)
1330 assert self._transaction is trans
1331 if nested:
1332 self._nested_transaction = trans
1333 else:
1334 raise sa_exc.InvalidRequestError(
1335 "A transaction is already begun on this Session."
1336 )
1337 elif not self.autocommit:
1338 # outermost transaction. must be a not nested and not
1339 # a subtransaction
1341 assert not nested and not _subtrans and not subtransactions
1342 trans = SessionTransaction(self)
1343 assert self._transaction is trans
1344 else:
1345 # legacy autocommit mode
1346 assert not self.future
1347 trans = SessionTransaction(self, nested=nested)
1348 assert self._transaction is trans
1350 return self._transaction # needed for __enter__/__exit__ hook
1352 def begin_nested(self):
1353 """Begin a "nested" transaction on this Session, e.g. SAVEPOINT.
1355 The target database(s) and associated drivers must support SQL
1356 SAVEPOINT for this method to function correctly.
1358 For documentation on SAVEPOINT
1359 transactions, please see :ref:`session_begin_nested`.
1361 :return: the :class:`.SessionTransaction` object. Note that
1362 :class:`.SessionTransaction` acts as a context manager, allowing
1363 :meth:`.Session.begin_nested` to be used in a "with" block.
1364 See :ref:`session_begin_nested` for a usage example.
1366 .. seealso::
1368 :ref:`session_begin_nested`
1370 :ref:`pysqlite_serializable` - special workarounds required
1371 with the SQLite driver in order for SAVEPOINT to work
1372 correctly.
1374 """
1375 return self.begin(nested=True)
1377 def rollback(self):
1378 """Rollback the current transaction in progress.
1380 If no transaction is in progress, this method is a pass-through.
1382 In :term:`1.x-style` use, this method rolls back the topmost
1383 database transaction if no nested transactions are in effect, or
1384 to the current nested transaction if one is in effect.
1386 When
1387 :term:`2.0-style` use is in effect via the
1388 :paramref:`_orm.Session.future` flag, the method always rolls back
1389 the topmost database transaction, discarding any nested
1390 transactions that may be in progress.
1392 .. seealso::
1394 :ref:`session_rollback`
1396 :ref:`unitofwork_transaction`
1398 """
1399 if self._transaction is None:
1400 pass
1401 else:
1402 self._transaction.rollback(_to_root=self.future)
1404 def commit(self):
1405 """Flush pending changes and commit the current transaction.
1407 When the COMMIT operation is complete, all objects are fully
1408 :term:`expired`, erasing their internal contents, which will be
1409 automatically re-loaded when the objects are next accessed. In the
1410 interim, these objects are in an expired state and will not function if
1411 they are :term:`detached` from the :class:`.Session`. Additionally,
1412 this re-load operation is not supported when using asyncio-oriented
1413 APIs. The :paramref:`.Session.expire_on_commit` parameter may be used
1414 to disable this behavior.
1416 When there is no transaction in place for the :class:`.Session`,
1417 indicating that no operations were invoked on this :class:`.Session`
1418 since the previous call to :meth:`.Session.commit`, the method will
1419 begin and commit an internal-only "logical" transaction, that does not
1420 normally affect the database unless pending flush changes were
1421 detected, but will still invoke event handlers and object expiration
1422 rules.
1424 If :term:`1.x-style` use is in effect and there are currently
1425 SAVEPOINTs in progress via :meth:`_orm.Session.begin_nested`,
1426 the operation will release the current SAVEPOINT but not commit
1427 the outermost database transaction.
1429 If :term:`2.0-style` use is in effect via the
1430 :paramref:`_orm.Session.future` flag, the outermost database
1431 transaction is committed unconditionally, automatically releasing any
1432 SAVEPOINTs in effect.
1434 When using legacy "autocommit" mode, this method is only
1435 valid to call if a transaction is actually in progress, else
1436 an error is raised. Similarly, when using legacy "subtransactions",
1437 the method will instead close out the current "subtransaction",
1438 rather than the actual database transaction, if a transaction
1439 is in progress.
1441 .. seealso::
1443 :ref:`session_committing`
1445 :ref:`unitofwork_transaction`
1447 :ref:`asyncio_orm_avoid_lazyloads`
1449 """
1450 if self._transaction is None:
1451 if not self._autobegin():
1452 raise sa_exc.InvalidRequestError("No transaction is begun.")
1454 self._transaction.commit(_to_root=self.future)
1456 def prepare(self):
1457 """Prepare the current transaction in progress for two phase commit.
1459 If no transaction is in progress, this method raises an
1460 :exc:`~sqlalchemy.exc.InvalidRequestError`.
1462 Only root transactions of two phase sessions can be prepared. If the
1463 current transaction is not such, an
1464 :exc:`~sqlalchemy.exc.InvalidRequestError` is raised.
1466 """
1467 if self._transaction is None:
1468 if not self._autobegin():
1469 raise sa_exc.InvalidRequestError("No transaction is begun.")
1471 self._transaction.prepare()
1473 def connection(
1474 self,
1475 bind_arguments=None,
1476 close_with_result=False,
1477 execution_options=None,
1478 **kw
1479 ):
1480 r"""Return a :class:`_engine.Connection` object corresponding to this
1481 :class:`.Session` object's transactional state.
1483 If this :class:`.Session` is configured with ``autocommit=False``,
1484 either the :class:`_engine.Connection` corresponding to the current
1485 transaction is returned, or if no transaction is in progress, a new
1486 one is begun and the :class:`_engine.Connection`
1487 returned (note that no
1488 transactional state is established with the DBAPI until the first
1489 SQL statement is emitted).
1491 Alternatively, if this :class:`.Session` is configured with
1492 ``autocommit=True``, an ad-hoc :class:`_engine.Connection` is returned
1493 using :meth:`_engine.Engine.connect` on the underlying
1494 :class:`_engine.Engine`.
1496 Ambiguity in multi-bind or unbound :class:`.Session` objects can be
1497 resolved through any of the optional keyword arguments. This
1498 ultimately makes usage of the :meth:`.get_bind` method for resolution.
1500 :param bind_arguments: dictionary of bind arguments. May include
1501 "mapper", "bind", "clause", other custom arguments that are passed
1502 to :meth:`.Session.get_bind`.
1504 :param bind:
1505 deprecated; use bind_arguments
1507 :param mapper:
1508 deprecated; use bind_arguments
1510 :param clause:
1511 deprecated; use bind_arguments
1513 :param close_with_result: Passed to :meth:`_engine.Engine.connect`,
1514 indicating the :class:`_engine.Connection` should be considered
1515 "single use", automatically closing when the first result set is
1516 closed. This flag only has an effect if this :class:`.Session` is
1517 configured with ``autocommit=True`` and does not already have a
1518 transaction in progress.
1520 .. deprecated:: 1.4 this parameter is deprecated and will be removed
1521 in SQLAlchemy 2.0
1523 :param execution_options: a dictionary of execution options that will
1524 be passed to :meth:`_engine.Connection.execution_options`, **when the
1525 connection is first procured only**. If the connection is already
1526 present within the :class:`.Session`, a warning is emitted and
1527 the arguments are ignored.
1529 .. seealso::
1531 :ref:`session_transaction_isolation`
1533 :param \**kw:
1534 deprecated; use bind_arguments
1536 """
1538 if not bind_arguments:
1539 bind_arguments = kw
1541 bind = bind_arguments.pop("bind", None)
1542 if bind is None:
1543 bind = self.get_bind(**bind_arguments)
1545 return self._connection_for_bind(
1546 bind,
1547 close_with_result=close_with_result,
1548 execution_options=execution_options,
1549 )
1551 def _connection_for_bind(self, engine, execution_options=None, **kw):
1552 TransactionalContext._trans_ctx_check(self)
1554 if self._transaction is not None or self._autobegin():
1555 return self._transaction._connection_for_bind(
1556 engine, execution_options
1557 )
1559 assert self._transaction is None
1560 assert self.autocommit
1561 conn = engine.connect(**kw)
1562 if execution_options:
1563 conn = conn.execution_options(**execution_options)
1564 return conn
1566 def execute(
1567 self,
1568 statement,
1569 params=None,
1570 execution_options=util.EMPTY_DICT,
1571 bind_arguments=None,
1572 _parent_execute_state=None,
1573 _add_event=None,
1574 **kw
1575 ):
1576 r"""Execute a SQL expression construct.
1578 Returns a :class:`_engine.Result` object representing
1579 results of the statement execution.
1581 E.g.::
1583 from sqlalchemy import select
1584 result = session.execute(
1585 select(User).where(User.id == 5)
1586 )
1588 The API contract of :meth:`_orm.Session.execute` is similar to that
1589 of :meth:`_future.Connection.execute`, the :term:`2.0 style` version
1590 of :class:`_future.Connection`.
1592 .. versionchanged:: 1.4 the :meth:`_orm.Session.execute` method is
1593 now the primary point of ORM statement execution when using
1594 :term:`2.0 style` ORM usage.
1596 :param statement:
1597 An executable statement (i.e. an :class:`.Executable` expression
1598 such as :func:`_expression.select`).
1600 :param params:
1601 Optional dictionary, or list of dictionaries, containing
1602 bound parameter values. If a single dictionary, single-row
1603 execution occurs; if a list of dictionaries, an
1604 "executemany" will be invoked. The keys in each dictionary
1605 must correspond to parameter names present in the statement.
1607 :param execution_options: optional dictionary of execution options,
1608 which will be associated with the statement execution. This
1609 dictionary can provide a subset of the options that are accepted
1610 by :meth:`_engine.Connection.execution_options`, and may also
1611 provide additional options understood only in an ORM context.
1613 :param bind_arguments: dictionary of additional arguments to determine
1614 the bind. May include "mapper", "bind", or other custom arguments.
1615 Contents of this dictionary are passed to the
1616 :meth:`.Session.get_bind` method.
1618 :param mapper:
1619 deprecated; use the bind_arguments dictionary
1621 :param bind:
1622 deprecated; use the bind_arguments dictionary
1624 :param \**kw:
1625 deprecated; use the bind_arguments dictionary
1627 :return: a :class:`_engine.Result` object.
1630 """
1631 statement = coercions.expect(roles.StatementRole, statement)
1633 if kw:
1634 util.warn_deprecated_20(
1635 "Passing bind arguments to Session.execute() as keyword "
1636 "arguments is deprecated and will be removed SQLAlchemy 2.0. "
1637 "Please use the bind_arguments parameter."
1638 )
1639 if not bind_arguments:
1640 bind_arguments = kw
1641 else:
1642 bind_arguments.update(kw)
1643 elif not bind_arguments:
1644 bind_arguments = {}
1645 else:
1646 bind_arguments = dict(bind_arguments)
1648 if (
1649 statement._propagate_attrs.get("compile_state_plugin", None)
1650 == "orm"
1651 ):
1652 # note that even without "future" mode, we need
1653 compile_state_cls = CompileState._get_plugin_class_for_plugin(
1654 statement, "orm"
1655 )
1656 else:
1657 compile_state_cls = None
1659 execution_options = util.coerce_to_immutabledict(execution_options)
1661 if compile_state_cls is not None:
1662 (
1663 statement,
1664 execution_options,
1665 ) = compile_state_cls.orm_pre_session_exec(
1666 self,
1667 statement,
1668 params,
1669 execution_options,
1670 bind_arguments,
1671 _parent_execute_state is not None,
1672 )
1673 else:
1674 bind_arguments.setdefault("clause", statement)
1675 execution_options = execution_options.union(
1676 {"future_result": True}
1677 )
1679 if _parent_execute_state:
1680 events_todo = _parent_execute_state._remaining_events()
1681 else:
1682 events_todo = self.dispatch.do_orm_execute
1683 if _add_event:
1684 events_todo = list(events_todo) + [_add_event]
1686 if events_todo:
1687 orm_exec_state = ORMExecuteState(
1688 self,
1689 statement,
1690 params,
1691 execution_options,
1692 bind_arguments,
1693 compile_state_cls,
1694 events_todo,
1695 )
1696 for idx, fn in enumerate(events_todo):
1697 orm_exec_state._starting_event_idx = idx
1698 result = fn(orm_exec_state)
1699 if result:
1700 return result
1702 statement = orm_exec_state.statement
1703 execution_options = orm_exec_state.local_execution_options
1705 bind = self.get_bind(**bind_arguments)
1707 if self.autocommit:
1708 # legacy stuff, we can't use future_result w/ autocommit because
1709 # we rely upon close_with_result, also legacy. it's all
1710 # interrelated
1711 conn = self._connection_for_bind(bind, close_with_result=True)
1712 execution_options = execution_options.union(
1713 dict(future_result=False)
1714 )
1715 else:
1716 conn = self._connection_for_bind(bind)
1717 result = conn._execute_20(statement, params or {}, execution_options)
1719 if compile_state_cls:
1720 result = compile_state_cls.orm_setup_cursor_result(
1721 self,
1722 statement,
1723 params,
1724 execution_options,
1725 bind_arguments,
1726 result,
1727 )
1729 return result
1731 def scalar(
1732 self,
1733 statement,
1734 params=None,
1735 execution_options=util.EMPTY_DICT,
1736 bind_arguments=None,
1737 **kw
1738 ):
1739 """Execute a statement and return a scalar result.
1741 Usage and parameters are the same as that of
1742 :meth:`_orm.Session.execute`; the return result is a scalar Python
1743 value.
1745 """
1747 return self.execute(
1748 statement,
1749 params=params,
1750 execution_options=execution_options,
1751 bind_arguments=bind_arguments,
1752 **kw
1753 ).scalar()
1755 def scalars(
1756 self,
1757 statement,
1758 params=None,
1759 execution_options=util.EMPTY_DICT,
1760 bind_arguments=None,
1761 **kw
1762 ):
1763 """Execute a statement and return the results as scalars.
1765 Usage and parameters are the same as that of
1766 :meth:`_orm.Session.execute`; the return result is a
1767 :class:`_result.ScalarResult` filtering object which
1768 will return single elements rather than :class:`_row.Row` objects.
1770 :return: a :class:`_result.ScalarResult` object
1772 .. versionadded:: 1.4.24 Added :meth:`_orm.Session.scalars`
1774 .. versionadded:: 1.4.26 Added :meth:`_orm.scoped_session.scalars`
1776 """
1778 return self.execute(
1779 statement,
1780 params=params,
1781 execution_options=execution_options,
1782 bind_arguments=bind_arguments,
1783 **kw
1784 ).scalars()
1786 def close(self):
1787 """Close out the transactional resources and ORM objects used by this
1788 :class:`_orm.Session`.
1790 This expunges all ORM objects associated with this
1791 :class:`_orm.Session`, ends any transaction in progress and
1792 :term:`releases` any :class:`_engine.Connection` objects which this
1793 :class:`_orm.Session` itself has checked out from associated
1794 :class:`_engine.Engine` objects. The operation then leaves the
1795 :class:`_orm.Session` in a state which it may be used again.
1797 .. tip::
1799 The :meth:`_orm.Session.close` method **does not prevent the
1800 Session from being used again**. The :class:`_orm.Session` itself
1801 does not actually have a distinct "closed" state; it merely means
1802 the :class:`_orm.Session` will release all database connections
1803 and ORM objects.
1805 .. versionchanged:: 1.4 The :meth:`.Session.close` method does not
1806 immediately create a new :class:`.SessionTransaction` object;
1807 instead, the new :class:`.SessionTransaction` is created only if
1808 the :class:`.Session` is used again for a database operation.
1810 .. seealso::
1812 :ref:`session_closing` - detail on the semantics of
1813 :meth:`_orm.Session.close`
1815 """
1816 self._close_impl(invalidate=False)
1818 def invalidate(self):
1819 """Close this Session, using connection invalidation.
1821 This is a variant of :meth:`.Session.close` that will additionally
1822 ensure that the :meth:`_engine.Connection.invalidate`
1823 method will be called on each :class:`_engine.Connection` object
1824 that is currently in use for a transaction (typically there is only
1825 one connection unless the :class:`_orm.Session` is used with
1826 multiple engines).
1828 This can be called when the database is known to be in a state where
1829 the connections are no longer safe to be used.
1831 Below illustrates a scenario when using `gevent
1832 <https://www.gevent.org/>`_, which can produce ``Timeout`` exceptions
1833 that may mean the underlying connection should be discarded::
1835 import gevent
1837 try:
1838 sess = Session()
1839 sess.add(User())
1840 sess.commit()
1841 except gevent.Timeout:
1842 sess.invalidate()
1843 raise
1844 except:
1845 sess.rollback()
1846 raise
1848 The method additionally does everything that :meth:`_orm.Session.close`
1849 does, including that all ORM objects are expunged.
1851 """
1852 self._close_impl(invalidate=True)
1854 def _close_impl(self, invalidate):
1855 self.expunge_all()
1856 if self._transaction is not None:
1857 for transaction in self._transaction._iterate_self_and_parents():
1858 transaction.close(invalidate)
1860 def expunge_all(self):
1861 """Remove all object instances from this ``Session``.
1863 This is equivalent to calling ``expunge(obj)`` on all objects in this
1864 ``Session``.
1866 """
1868 all_states = self.identity_map.all_states() + list(self._new)
1869 self.identity_map._kill()
1870 self.identity_map = identity.WeakInstanceDict()
1871 self._new = {}
1872 self._deleted = {}
1874 statelib.InstanceState._detach_states(all_states, self)
1876 def _add_bind(self, key, bind):
1877 try:
1878 insp = inspect(key)
1879 except sa_exc.NoInspectionAvailable as err:
1880 if not isinstance(key, type):
1881 util.raise_(
1882 sa_exc.ArgumentError(
1883 "Not an acceptable bind target: %s" % key
1884 ),
1885 replace_context=err,
1886 )
1887 else:
1888 self.__binds[key] = bind
1889 else:
1890 if insp.is_selectable:
1891 self.__binds[insp] = bind
1892 elif insp.is_mapper:
1893 self.__binds[insp.class_] = bind
1894 for _selectable in insp._all_tables:
1895 self.__binds[_selectable] = bind
1896 else:
1897 raise sa_exc.ArgumentError(
1898 "Not an acceptable bind target: %s" % key
1899 )
1901 def bind_mapper(self, mapper, bind):
1902 """Associate a :class:`_orm.Mapper` or arbitrary Python class with a
1903 "bind", e.g. an :class:`_engine.Engine` or
1904 :class:`_engine.Connection`.
1906 The given entity is added to a lookup used by the
1907 :meth:`.Session.get_bind` method.
1909 :param mapper: a :class:`_orm.Mapper` object,
1910 or an instance of a mapped
1911 class, or any Python class that is the base of a set of mapped
1912 classes.
1914 :param bind: an :class:`_engine.Engine` or :class:`_engine.Connection`
1915 object.
1917 .. seealso::
1919 :ref:`session_partitioning`
1921 :paramref:`.Session.binds`
1923 :meth:`.Session.bind_table`
1926 """
1927 self._add_bind(mapper, bind)
1929 def bind_table(self, table, bind):
1930 """Associate a :class:`_schema.Table` with a "bind", e.g. an
1931 :class:`_engine.Engine`
1932 or :class:`_engine.Connection`.
1934 The given :class:`_schema.Table` is added to a lookup used by the
1935 :meth:`.Session.get_bind` method.
1937 :param table: a :class:`_schema.Table` object,
1938 which is typically the target
1939 of an ORM mapping, or is present within a selectable that is
1940 mapped.
1942 :param bind: an :class:`_engine.Engine` or :class:`_engine.Connection`
1943 object.
1945 .. seealso::
1947 :ref:`session_partitioning`
1949 :paramref:`.Session.binds`
1951 :meth:`.Session.bind_mapper`
1954 """
1955 self._add_bind(table, bind)
1957 def get_bind(
1958 self,
1959 mapper=None,
1960 clause=None,
1961 bind=None,
1962 _sa_skip_events=None,
1963 _sa_skip_for_implicit_returning=False,
1964 ):
1965 """Return a "bind" to which this :class:`.Session` is bound.
1967 The "bind" is usually an instance of :class:`_engine.Engine`,
1968 except in the case where the :class:`.Session` has been
1969 explicitly bound directly to a :class:`_engine.Connection`.
1971 For a multiply-bound or unbound :class:`.Session`, the
1972 ``mapper`` or ``clause`` arguments are used to determine the
1973 appropriate bind to return.
1975 Note that the "mapper" argument is usually present
1976 when :meth:`.Session.get_bind` is called via an ORM
1977 operation such as a :meth:`.Session.query`, each
1978 individual INSERT/UPDATE/DELETE operation within a
1979 :meth:`.Session.flush`, call, etc.
1981 The order of resolution is:
1983 1. if mapper given and :paramref:`.Session.binds` is present,
1984 locate a bind based first on the mapper in use, then
1985 on the mapped class in use, then on any base classes that are
1986 present in the ``__mro__`` of the mapped class, from more specific
1987 superclasses to more general.
1988 2. if clause given and ``Session.binds`` is present,
1989 locate a bind based on :class:`_schema.Table` objects
1990 found in the given clause present in ``Session.binds``.
1991 3. if ``Session.binds`` is present, return that.
1992 4. if clause given, attempt to return a bind
1993 linked to the :class:`_schema.MetaData` ultimately
1994 associated with the clause.
1995 5. if mapper given, attempt to return a bind
1996 linked to the :class:`_schema.MetaData` ultimately
1997 associated with the :class:`_schema.Table` or other
1998 selectable to which the mapper is mapped.
1999 6. No bind can be found, :exc:`~sqlalchemy.exc.UnboundExecutionError`
2000 is raised.
2002 Note that the :meth:`.Session.get_bind` method can be overridden on
2003 a user-defined subclass of :class:`.Session` to provide any kind
2004 of bind resolution scheme. See the example at
2005 :ref:`session_custom_partitioning`.
2007 :param mapper:
2008 Optional :func:`.mapper` mapped class or instance of
2009 :class:`_orm.Mapper`. The bind can be derived from a
2010 :class:`_orm.Mapper`
2011 first by consulting the "binds" map associated with this
2012 :class:`.Session`, and secondly by consulting the
2013 :class:`_schema.MetaData`
2014 associated with the :class:`_schema.Table` to which the
2015 :class:`_orm.Mapper`
2016 is mapped for a bind.
2018 :param clause:
2019 A :class:`_expression.ClauseElement` (i.e.
2020 :func:`_expression.select`,
2021 :func:`_expression.text`,
2022 etc.). If the ``mapper`` argument is not present or could not
2023 produce a bind, the given expression construct will be searched
2024 for a bound element, typically a :class:`_schema.Table`
2025 associated with
2026 bound :class:`_schema.MetaData`.
2028 .. seealso::
2030 :ref:`session_partitioning`
2032 :paramref:`.Session.binds`
2034 :meth:`.Session.bind_mapper`
2036 :meth:`.Session.bind_table`
2038 """
2040 # this function is documented as a subclassing hook, so we have
2041 # to call this method even if the return is simple
2042 if bind:
2043 return bind
2044 elif not self.__binds and self.bind:
2045 # simplest and most common case, we have a bind and no
2046 # per-mapper/table binds, we're done
2047 return self.bind
2049 # we don't have self.bind and either have self.__binds
2050 # or we don't have self.__binds (which is legacy). Look at the
2051 # mapper and the clause
2052 if mapper is clause is None:
2053 if self.bind:
2054 return self.bind
2055 else:
2056 raise sa_exc.UnboundExecutionError(
2057 "This session is not bound to a single Engine or "
2058 "Connection, and no context was provided to locate "
2059 "a binding."
2060 )
2062 # look more closely at the mapper.
2063 if mapper is not None:
2064 try:
2065 mapper = inspect(mapper)
2066 except sa_exc.NoInspectionAvailable as err:
2067 if isinstance(mapper, type):
2068 util.raise_(
2069 exc.UnmappedClassError(mapper),
2070 replace_context=err,
2071 )
2072 else:
2073 raise
2075 # match up the mapper or clause in the __binds
2076 if self.__binds:
2077 # matching mappers and selectables to entries in the
2078 # binds dictionary; supported use case.
2079 if mapper:
2080 for cls in mapper.class_.__mro__:
2081 if cls in self.__binds:
2082 return self.__binds[cls]
2083 if clause is None:
2084 clause = mapper.persist_selectable
2086 if clause is not None:
2087 plugin_subject = clause._propagate_attrs.get(
2088 "plugin_subject", None
2089 )
2091 if plugin_subject is not None:
2092 for cls in plugin_subject.mapper.class_.__mro__:
2093 if cls in self.__binds:
2094 return self.__binds[cls]
2096 for obj in visitors.iterate(clause):
2097 if obj in self.__binds:
2098 return self.__binds[obj]
2100 # none of the __binds matched, but we have a fallback bind.
2101 # return that
2102 if self.bind:
2103 return self.bind
2105 # now we are in legacy territory. looking for "bind" on tables
2106 # that are via bound metadata. this goes away in 2.0.
2108 future_msg = ""
2109 future_code = ""
2111 if mapper and clause is None:
2112 clause = mapper.persist_selectable
2114 if clause is not None:
2115 if clause.bind:
2116 if self.future:
2117 future_msg = (
2118 " A bind was located via legacy bound metadata, but "
2119 "since future=True is set on this Session, this "
2120 "bind is ignored."
2121 )
2122 else:
2123 util.warn_deprecated_20(
2124 "This Session located a target engine via bound "
2125 "metadata; as this functionality will be removed in "
2126 "SQLAlchemy 2.0, an Engine object should be passed "
2127 "to the Session() constructor directly."
2128 )
2129 return clause.bind
2131 if mapper:
2132 if mapper.persist_selectable.bind:
2133 if self.future:
2134 future_msg = (
2135 " A bind was located via legacy bound metadata, but "
2136 "since future=True is set on this Session, this "
2137 "bind is ignored."
2138 )
2139 else:
2140 util.warn_deprecated_20(
2141 "This Session located a target engine via bound "
2142 "metadata; as this functionality will be removed in "
2143 "SQLAlchemy 2.0, an Engine object should be passed "
2144 "to the Session() constructor directly."
2145 )
2146 return mapper.persist_selectable.bind
2148 context = []
2149 if mapper is not None:
2150 context.append("mapper %s" % mapper)
2151 if clause is not None:
2152 context.append("SQL expression")
2154 raise sa_exc.UnboundExecutionError(
2155 "Could not locate a bind configured on %s or this Session.%s"
2156 % (", ".join(context), future_msg),
2157 code=future_code,
2158 )
2160 def query(self, *entities, **kwargs):
2161 """Return a new :class:`_query.Query` object corresponding to this
2162 :class:`_orm.Session`.
2164 """
2166 return self._query_cls(entities, self, **kwargs)
2168 def _identity_lookup(
2169 self,
2170 mapper,
2171 primary_key_identity,
2172 identity_token=None,
2173 passive=attributes.PASSIVE_OFF,
2174 lazy_loaded_from=None,
2175 ):
2176 """Locate an object in the identity map.
2178 Given a primary key identity, constructs an identity key and then
2179 looks in the session's identity map. If present, the object may
2180 be run through unexpiration rules (e.g. load unloaded attributes,
2181 check if was deleted).
2183 e.g.::
2185 obj = session._identity_lookup(inspect(SomeClass), (1, ))
2187 :param mapper: mapper in use
2188 :param primary_key_identity: the primary key we are searching for, as
2189 a tuple.
2190 :param identity_token: identity token that should be used to create
2191 the identity key. Used as is, however overriding subclasses can
2192 repurpose this in order to interpret the value in a special way,
2193 such as if None then look among multiple target tokens.
2194 :param passive: passive load flag passed to
2195 :func:`.loading.get_from_identity`, which impacts the behavior if
2196 the object is found; the object may be validated and/or unexpired
2197 if the flag allows for SQL to be emitted.
2198 :param lazy_loaded_from: an :class:`.InstanceState` that is
2199 specifically asking for this identity as a related identity. Used
2200 for sharding schemes where there is a correspondence between an object
2201 and a related object being lazy-loaded (or otherwise
2202 relationship-loaded).
2204 :return: None if the object is not found in the identity map, *or*
2205 if the object was unexpired and found to have been deleted.
2206 if passive flags disallow SQL and the object is expired, returns
2207 PASSIVE_NO_RESULT. In all other cases the instance is returned.
2209 .. versionchanged:: 1.4.0 - the :meth:`.Session._identity_lookup`
2210 method was moved from :class:`_query.Query` to
2211 :class:`.Session`, to avoid having to instantiate the
2212 :class:`_query.Query` object.
2215 """
2217 key = mapper.identity_key_from_primary_key(
2218 primary_key_identity, identity_token=identity_token
2219 )
2220 return loading.get_from_identity(self, mapper, key, passive)
2222 @property
2223 @util.contextmanager
2224 def no_autoflush(self):
2225 """Return a context manager that disables autoflush.
2227 e.g.::
2229 with session.no_autoflush:
2231 some_object = SomeClass()
2232 session.add(some_object)
2233 # won't autoflush
2234 some_object.related_thing = session.query(SomeRelated).first()
2236 Operations that proceed within the ``with:`` block
2237 will not be subject to flushes occurring upon query
2238 access. This is useful when initializing a series
2239 of objects which involve existing database queries,
2240 where the uncompleted object should not yet be flushed.
2242 """
2243 autoflush = self.autoflush
2244 self.autoflush = False
2245 try:
2246 yield self
2247 finally:
2248 self.autoflush = autoflush
2250 def _autoflush(self):
2251 if self.autoflush and not self._flushing:
2252 try:
2253 self.flush()
2254 except sa_exc.StatementError as e:
2255 # note we are reraising StatementError as opposed to
2256 # raising FlushError with "chaining" to remain compatible
2257 # with code that catches StatementError, IntegrityError,
2258 # etc.
2259 e.add_detail(
2260 "raised as a result of Query-invoked autoflush; "
2261 "consider using a session.no_autoflush block if this "
2262 "flush is occurring prematurely"
2263 )
2264 util.raise_(e, with_traceback=sys.exc_info()[2])
2266 def refresh(self, instance, attribute_names=None, with_for_update=None):
2267 """Expire and refresh attributes on the given instance.
2269 The selected attributes will first be expired as they would when using
2270 :meth:`_orm.Session.expire`; then a SELECT statement will be issued to
2271 the database to refresh column-oriented attributes with the current
2272 value available in the current transaction.
2274 :func:`_orm.relationship` oriented attributes will also be immediately
2275 loaded if they were already eagerly loaded on the object, using the
2276 same eager loading strategy that they were loaded with originally.
2277 Unloaded relationship attributes will remain unloaded, as will
2278 relationship attributes that were originally lazy loaded.
2280 .. versionadded:: 1.4 - the :meth:`_orm.Session.refresh` method
2281 can also refresh eagerly loaded attributes.
2283 .. tip::
2285 While the :meth:`_orm.Session.refresh` method is capable of
2286 refreshing both column and relationship oriented attributes, its
2287 primary focus is on refreshing of local column-oriented attributes
2288 on a single instance. For more open ended "refresh" functionality,
2289 including the ability to refresh the attributes on many objects at
2290 once while having explicit control over relationship loader
2291 strategies, use the
2292 :ref:`populate existing <orm_queryguide_populate_existing>` feature
2293 instead.
2295 Note that a highly isolated transaction will return the same values as
2296 were previously read in that same transaction, regardless of changes
2297 in database state outside of that transaction. Refreshing
2298 attributes usually only makes sense at the start of a transaction
2299 where database rows have not yet been accessed.
2301 :param attribute_names: optional. An iterable collection of
2302 string attribute names indicating a subset of attributes to
2303 be refreshed.
2305 :param with_for_update: optional boolean ``True`` indicating FOR UPDATE
2306 should be used, or may be a dictionary containing flags to
2307 indicate a more specific set of FOR UPDATE flags for the SELECT;
2308 flags should match the parameters of
2309 :meth:`_query.Query.with_for_update`.
2310 Supersedes the :paramref:`.Session.refresh.lockmode` parameter.
2312 .. seealso::
2314 :ref:`session_expire` - introductory material
2316 :meth:`.Session.expire`
2318 :meth:`.Session.expire_all`
2320 :ref:`orm_queryguide_populate_existing` - allows any ORM query
2321 to refresh objects as they would be loaded normally.
2323 """
2324 try:
2325 state = attributes.instance_state(instance)
2326 except exc.NO_STATE as err:
2327 util.raise_(
2328 exc.UnmappedInstanceError(instance),
2329 replace_context=err,
2330 )
2332 self._expire_state(state, attribute_names)
2334 if with_for_update == {}:
2335 raise sa_exc.ArgumentError(
2336 "with_for_update should be the boolean value "
2337 "True, or a dictionary with options. "
2338 "A blank dictionary is ambiguous."
2339 )
2341 with_for_update = query.ForUpdateArg._from_argument(with_for_update)
2343 stmt = sql.select(object_mapper(instance))
2344 if (
2345 loading.load_on_ident(
2346 self,
2347 stmt,
2348 state.key,
2349 refresh_state=state,
2350 with_for_update=with_for_update,
2351 only_load_props=attribute_names,
2352 )
2353 is None
2354 ):
2355 raise sa_exc.InvalidRequestError(
2356 "Could not refresh instance '%s'" % instance_str(instance)
2357 )
2359 def expire_all(self):
2360 """Expires all persistent instances within this Session.
2362 When any attributes on a persistent instance is next accessed,
2363 a query will be issued using the
2364 :class:`.Session` object's current transactional context in order to
2365 load all expired attributes for the given instance. Note that
2366 a highly isolated transaction will return the same values as were
2367 previously read in that same transaction, regardless of changes
2368 in database state outside of that transaction.
2370 To expire individual objects and individual attributes
2371 on those objects, use :meth:`Session.expire`.
2373 The :class:`.Session` object's default behavior is to
2374 expire all state whenever the :meth:`Session.rollback`
2375 or :meth:`Session.commit` methods are called, so that new
2376 state can be loaded for the new transaction. For this reason,
2377 calling :meth:`Session.expire_all` should not be needed when
2378 autocommit is ``False``, assuming the transaction is isolated.
2380 .. seealso::
2382 :ref:`session_expire` - introductory material
2384 :meth:`.Session.expire`
2386 :meth:`.Session.refresh`
2388 :meth:`_orm.Query.populate_existing`
2390 """
2391 for state in self.identity_map.all_states():
2392 state._expire(state.dict, self.identity_map._modified)
2394 def expire(self, instance, attribute_names=None):
2395 """Expire the attributes on an instance.
2397 Marks the attributes of an instance as out of date. When an expired
2398 attribute is next accessed, a query will be issued to the
2399 :class:`.Session` object's current transactional context in order to
2400 load all expired attributes for the given instance. Note that
2401 a highly isolated transaction will return the same values as were
2402 previously read in that same transaction, regardless of changes
2403 in database state outside of that transaction.
2405 To expire all objects in the :class:`.Session` simultaneously,
2406 use :meth:`Session.expire_all`.
2408 The :class:`.Session` object's default behavior is to
2409 expire all state whenever the :meth:`Session.rollback`
2410 or :meth:`Session.commit` methods are called, so that new
2411 state can be loaded for the new transaction. For this reason,
2412 calling :meth:`Session.expire` only makes sense for the specific
2413 case that a non-ORM SQL statement was emitted in the current
2414 transaction.
2416 :param instance: The instance to be refreshed.
2417 :param attribute_names: optional list of string attribute names
2418 indicating a subset of attributes to be expired.
2420 .. seealso::
2422 :ref:`session_expire` - introductory material
2424 :meth:`.Session.expire`
2426 :meth:`.Session.refresh`
2428 :meth:`_orm.Query.populate_existing`
2430 """
2431 try:
2432 state = attributes.instance_state(instance)
2433 except exc.NO_STATE as err:
2434 util.raise_(
2435 exc.UnmappedInstanceError(instance),
2436 replace_context=err,
2437 )
2438 self._expire_state(state, attribute_names)
2440 def _expire_state(self, state, attribute_names):
2441 self._validate_persistent(state)
2442 if attribute_names:
2443 state._expire_attributes(state.dict, attribute_names)
2444 else:
2445 # pre-fetch the full cascade since the expire is going to
2446 # remove associations
2447 cascaded = list(
2448 state.manager.mapper.cascade_iterator("refresh-expire", state)
2449 )
2450 self._conditional_expire(state)
2451 for o, m, st_, dct_ in cascaded:
2452 self._conditional_expire(st_)
2454 def _conditional_expire(self, state, autoflush=None):
2455 """Expire a state if persistent, else expunge if pending"""
2457 if state.key:
2458 state._expire(state.dict, self.identity_map._modified)
2459 elif state in self._new:
2460 self._new.pop(state)
2461 state._detach(self)
2463 def expunge(self, instance):
2464 """Remove the `instance` from this ``Session``.
2466 This will free all internal references to the instance. Cascading
2467 will be applied according to the *expunge* cascade rule.
2469 """
2470 try:
2471 state = attributes.instance_state(instance)
2472 except exc.NO_STATE as err:
2473 util.raise_(
2474 exc.UnmappedInstanceError(instance),
2475 replace_context=err,
2476 )
2477 if state.session_id is not self.hash_key:
2478 raise sa_exc.InvalidRequestError(
2479 "Instance %s is not present in this Session" % state_str(state)
2480 )
2482 cascaded = list(
2483 state.manager.mapper.cascade_iterator("expunge", state)
2484 )
2485 self._expunge_states([state] + [st_ for o, m, st_, dct_ in cascaded])
2487 def _expunge_states(self, states, to_transient=False):
2488 for state in states:
2489 if state in self._new:
2490 self._new.pop(state)
2491 elif self.identity_map.contains_state(state):
2492 self.identity_map.safe_discard(state)
2493 self._deleted.pop(state, None)
2494 elif self._transaction:
2495 # state is "detached" from being deleted, but still present
2496 # in the transaction snapshot
2497 self._transaction._deleted.pop(state, None)
2498 statelib.InstanceState._detach_states(
2499 states, self, to_transient=to_transient
2500 )
2502 def _register_persistent(self, states):
2503 """Register all persistent objects from a flush.
2505 This is used both for pending objects moving to the persistent
2506 state as well as already persistent objects.
2508 """
2510 pending_to_persistent = self.dispatch.pending_to_persistent or None
2511 for state in states:
2512 mapper = _state_mapper(state)
2514 # prevent against last minute dereferences of the object
2515 obj = state.obj()
2516 if obj is not None:
2518 instance_key = mapper._identity_key_from_state(state)
2520 if (
2521 _none_set.intersection(instance_key[1])
2522 and not mapper.allow_partial_pks
2523 or _none_set.issuperset(instance_key[1])
2524 ):
2525 raise exc.FlushError(
2526 "Instance %s has a NULL identity key. If this is an "
2527 "auto-generated value, check that the database table "
2528 "allows generation of new primary key values, and "
2529 "that the mapped Column object is configured to "
2530 "expect these generated values. Ensure also that "
2531 "this flush() is not occurring at an inappropriate "
2532 "time, such as within a load() event."
2533 % state_str(state)
2534 )
2536 if state.key is None:
2537 state.key = instance_key
2538 elif state.key != instance_key:
2539 # primary key switch. use safe_discard() in case another
2540 # state has already replaced this one in the identity
2541 # map (see test/orm/test_naturalpks.py ReversePKsTest)
2542 self.identity_map.safe_discard(state)
2543 if state in self._transaction._key_switches:
2544 orig_key = self._transaction._key_switches[state][0]
2545 else:
2546 orig_key = state.key
2547 self._transaction._key_switches[state] = (
2548 orig_key,
2549 instance_key,
2550 )
2551 state.key = instance_key
2553 # there can be an existing state in the identity map
2554 # that is replaced when the primary keys of two instances
2555 # are swapped; see test/orm/test_naturalpks.py -> test_reverse
2556 old = self.identity_map.replace(state)
2557 if (
2558 old is not None
2559 and mapper._identity_key_from_state(old) == instance_key
2560 and old.obj() is not None
2561 ):
2562 util.warn(
2563 "Identity map already had an identity for %s, "
2564 "replacing it with newly flushed object. Are there "
2565 "load operations occurring inside of an event handler "
2566 "within the flush?" % (instance_key,)
2567 )
2568 state._orphaned_outside_of_session = False
2570 statelib.InstanceState._commit_all_states(
2571 ((state, state.dict) for state in states), self.identity_map
2572 )
2574 self._register_altered(states)
2576 if pending_to_persistent is not None:
2577 for state in states.intersection(self._new):
2578 pending_to_persistent(self, state)
2580 # remove from new last, might be the last strong ref
2581 for state in set(states).intersection(self._new):
2582 self._new.pop(state)
2584 def _register_altered(self, states):
2585 if self._transaction:
2586 for state in states:
2587 if state in self._new:
2588 self._transaction._new[state] = True
2589 else:
2590 self._transaction._dirty[state] = True
2592 def _remove_newly_deleted(self, states):
2593 persistent_to_deleted = self.dispatch.persistent_to_deleted or None
2594 for state in states:
2595 if self._transaction:
2596 self._transaction._deleted[state] = True
2598 if persistent_to_deleted is not None:
2599 # get a strong reference before we pop out of
2600 # self._deleted
2601 obj = state.obj() # noqa
2603 self.identity_map.safe_discard(state)
2604 self._deleted.pop(state, None)
2605 state._deleted = True
2606 # can't call state._detach() here, because this state
2607 # is still in the transaction snapshot and needs to be
2608 # tracked as part of that
2609 if persistent_to_deleted is not None:
2610 persistent_to_deleted(self, state)
2612 def add(self, instance, _warn=True):
2613 """Place an object into this :class:`_orm.Session`.
2615 Objects that are in the :term:`transient` state when passed to the
2616 :meth:`_orm.Session.add` method will move to the
2617 :term:`pending` state, until the next flush, at which point they
2618 will move to the :term:`persistent` state.
2620 Objects that are in the :term:`detached` state when passed to the
2621 :meth:`_orm.Session.add` method will move to the :term:`persistent`
2622 state directly.
2624 If the transaction used by the :class:`_orm.Session` is rolled back,
2625 objects which were transient when they were passed to
2626 :meth:`_orm.Session.add` will be moved back to the
2627 :term:`transient` state, and will no longer be present within this
2628 :class:`_orm.Session`.
2630 .. seealso::
2632 :meth:`_orm.Session.add_all`
2634 :ref:`session_adding` - at :ref:`session_basics`
2636 """
2637 if _warn and self._warn_on_events:
2638 self._flush_warning("Session.add()")
2640 try:
2641 state = attributes.instance_state(instance)
2642 except exc.NO_STATE as err:
2643 util.raise_(
2644 exc.UnmappedInstanceError(instance),
2645 replace_context=err,
2646 )
2648 self._save_or_update_state(state)
2650 def add_all(self, instances):
2651 """Add the given collection of instances to this :class:`_orm.Session`.
2653 See the documentation for :meth:`_orm.Session.add` for a general
2654 behavioral description.
2656 .. seealso::
2658 :meth:`_orm.Session.add`
2660 :ref:`session_adding` - at :ref:`session_basics`
2662 """
2664 if self._warn_on_events:
2665 self._flush_warning("Session.add_all()")
2667 for instance in instances:
2668 self.add(instance, _warn=False)
2670 def _save_or_update_state(self, state):
2671 state._orphaned_outside_of_session = False
2672 self._save_or_update_impl(state)
2674 mapper = _state_mapper(state)
2675 for o, m, st_, dct_ in mapper.cascade_iterator(
2676 "save-update", state, halt_on=self._contains_state
2677 ):
2678 self._save_or_update_impl(st_)
2680 def delete(self, instance):
2681 """Mark an instance as deleted.
2683 The object is assumed to be either :term:`persistent` or
2684 :term:`detached` when passed; after the method is called, the
2685 object will remain in the :term:`persistent` state until the next
2686 flush proceeds. During this time, the object will also be a member
2687 of the :attr:`_orm.Session.deleted` collection.
2689 When the next flush proceeds, the object will move to the
2690 :term:`deleted` state, indicating a ``DELETE`` statement was emitted
2691 for its row within the current transaction. When the transaction
2692 is successfully committed,
2693 the deleted object is moved to the :term:`detached` state and is
2694 no longer present within this :class:`_orm.Session`.
2696 .. seealso::
2698 :ref:`session_deleting` - at :ref:`session_basics`
2700 """
2701 if self._warn_on_events:
2702 self._flush_warning("Session.delete()")
2704 try:
2705 state = attributes.instance_state(instance)
2706 except exc.NO_STATE as err:
2707 util.raise_(
2708 exc.UnmappedInstanceError(instance),
2709 replace_context=err,
2710 )
2712 self._delete_impl(state, instance, head=True)
2714 def _delete_impl(self, state, obj, head):
2716 if state.key is None:
2717 if head:
2718 raise sa_exc.InvalidRequestError(
2719 "Instance '%s' is not persisted" % state_str(state)
2720 )
2721 else:
2722 return
2724 to_attach = self._before_attach(state, obj)
2726 if state in self._deleted:
2727 return
2729 self.identity_map.add(state)
2731 if to_attach:
2732 self._after_attach(state, obj)
2734 if head:
2735 # grab the cascades before adding the item to the deleted list
2736 # so that autoflush does not delete the item
2737 # the strong reference to the instance itself is significant here
2738 cascade_states = list(
2739 state.manager.mapper.cascade_iterator("delete", state)
2740 )
2742 self._deleted[state] = obj
2744 if head:
2745 for o, m, st_, dct_ in cascade_states:
2746 self._delete_impl(st_, o, False)
2748 def get(
2749 self,
2750 entity,
2751 ident,
2752 options=None,
2753 populate_existing=False,
2754 with_for_update=None,
2755 identity_token=None,
2756 execution_options=None,
2757 ):
2758 """Return an instance based on the given primary key identifier,
2759 or ``None`` if not found.
2761 E.g.::
2763 my_user = session.get(User, 5)
2765 some_object = session.get(VersionedFoo, (5, 10))
2767 some_object = session.get(
2768 VersionedFoo,
2769 {"id": 5, "version_id": 10}
2770 )
2772 .. versionadded:: 1.4 Added :meth:`_orm.Session.get`, which is moved
2773 from the now deprecated :meth:`_orm.Query.get` method.
2775 :meth:`_orm.Session.get` is special in that it provides direct
2776 access to the identity map of the :class:`.Session`.
2777 If the given primary key identifier is present
2778 in the local identity map, the object is returned
2779 directly from this collection and no SQL is emitted,
2780 unless the object has been marked fully expired.
2781 If not present,
2782 a SELECT is performed in order to locate the object.
2784 :meth:`_orm.Session.get` also will perform a check if
2785 the object is present in the identity map and
2786 marked as expired - a SELECT
2787 is emitted to refresh the object as well as to
2788 ensure that the row is still present.
2789 If not, :class:`~sqlalchemy.orm.exc.ObjectDeletedError` is raised.
2791 :param entity: a mapped class or :class:`.Mapper` indicating the
2792 type of entity to be loaded.
2794 :param ident: A scalar, tuple, or dictionary representing the
2795 primary key. For a composite (e.g. multiple column) primary key,
2796 a tuple or dictionary should be passed.
2798 For a single-column primary key, the scalar calling form is typically
2799 the most expedient. If the primary key of a row is the value "5",
2800 the call looks like::
2802 my_object = session.get(SomeClass, 5)
2804 The tuple form contains primary key values typically in
2805 the order in which they correspond to the mapped
2806 :class:`_schema.Table`
2807 object's primary key columns, or if the
2808 :paramref:`_orm.Mapper.primary_key` configuration parameter were
2809 used, in
2810 the order used for that parameter. For example, if the primary key
2811 of a row is represented by the integer
2812 digits "5, 10" the call would look like::
2814 my_object = session.get(SomeClass, (5, 10))
2816 The dictionary form should include as keys the mapped attribute names
2817 corresponding to each element of the primary key. If the mapped class
2818 has the attributes ``id``, ``version_id`` as the attributes which
2819 store the object's primary key value, the call would look like::
2821 my_object = session.get(SomeClass, {"id": 5, "version_id": 10})
2823 :param options: optional sequence of loader options which will be
2824 applied to the query, if one is emitted.
2826 :param populate_existing: causes the method to unconditionally emit
2827 a SQL query and refresh the object with the newly loaded data,
2828 regardless of whether or not the object is already present.
2830 :param with_for_update: optional boolean ``True`` indicating FOR UPDATE
2831 should be used, or may be a dictionary containing flags to
2832 indicate a more specific set of FOR UPDATE flags for the SELECT;
2833 flags should match the parameters of
2834 :meth:`_query.Query.with_for_update`.
2835 Supersedes the :paramref:`.Session.refresh.lockmode` parameter.
2837 :param execution_options: optional dictionary of execution options,
2838 which will be associated with the query execution if one is emitted.
2839 This dictionary can provide a subset of the options that are
2840 accepted by :meth:`_engine.Connection.execution_options`, and may
2841 also provide additional options understood only in an ORM context.
2843 .. versionadded:: 1.4.29
2845 .. seealso::
2847 :ref:`orm_queryguide_execution_options` - ORM-specific execution
2848 options
2850 :return: The object instance, or ``None``.
2852 """
2853 return self._get_impl(
2854 entity,
2855 ident,
2856 loading.load_on_pk_identity,
2857 options,
2858 populate_existing=populate_existing,
2859 with_for_update=with_for_update,
2860 identity_token=identity_token,
2861 execution_options=execution_options,
2862 )
2864 def _get_impl(
2865 self,
2866 entity,
2867 primary_key_identity,
2868 db_load_fn,
2869 options=None,
2870 populate_existing=False,
2871 with_for_update=None,
2872 identity_token=None,
2873 execution_options=None,
2874 ):
2876 # convert composite types to individual args
2877 if hasattr(primary_key_identity, "__composite_values__"):
2878 primary_key_identity = primary_key_identity.__composite_values__()
2880 mapper = inspect(entity)
2882 if not mapper or not mapper.is_mapper:
2883 raise sa_exc.ArgumentError(
2884 "Expected mapped class or mapper, got: %r" % entity
2885 )
2887 is_dict = isinstance(primary_key_identity, dict)
2888 if not is_dict:
2889 primary_key_identity = util.to_list(
2890 primary_key_identity, default=(None,)
2891 )
2893 if len(primary_key_identity) != len(mapper.primary_key):
2894 raise sa_exc.InvalidRequestError(
2895 "Incorrect number of values in identifier to formulate "
2896 "primary key for session.get(); primary key columns "
2897 "are %s" % ",".join("'%s'" % c for c in mapper.primary_key)
2898 )
2900 if is_dict:
2902 pk_synonyms = mapper._pk_synonyms
2904 if pk_synonyms:
2905 correct_keys = set(pk_synonyms).intersection(
2906 primary_key_identity
2907 )
2909 if correct_keys:
2910 primary_key_identity = dict(primary_key_identity)
2911 for k in correct_keys:
2912 primary_key_identity[
2913 pk_synonyms[k]
2914 ] = primary_key_identity[k]
2916 try:
2917 primary_key_identity = list(
2918 primary_key_identity[prop.key]
2919 for prop in mapper._identity_key_props
2920 )
2922 except KeyError as err:
2923 util.raise_(
2924 sa_exc.InvalidRequestError(
2925 "Incorrect names of values in identifier to formulate "
2926 "primary key for session.get(); primary key attribute "
2927 "names are %s (synonym names are also accepted)"
2928 % ",".join(
2929 "'%s'" % prop.key
2930 for prop in mapper._identity_key_props
2931 )
2932 ),
2933 replace_context=err,
2934 )
2936 if (
2937 not populate_existing
2938 and not mapper.always_refresh
2939 and with_for_update is None
2940 ):
2942 instance = self._identity_lookup(
2943 mapper, primary_key_identity, identity_token=identity_token
2944 )
2946 if instance is not None:
2947 # reject calls for id in identity map but class
2948 # mismatch.
2949 if not issubclass(instance.__class__, mapper.class_):
2950 return None
2951 return instance
2952 elif instance is attributes.PASSIVE_CLASS_MISMATCH:
2953 return None
2955 # set_label_style() not strictly necessary, however this will ensure
2956 # that tablename_colname style is used which at the moment is
2957 # asserted in a lot of unit tests :)
2959 load_options = context.QueryContext.default_load_options
2961 if populate_existing:
2962 load_options += {"_populate_existing": populate_existing}
2963 statement = sql.select(mapper).set_label_style(
2964 LABEL_STYLE_TABLENAME_PLUS_COL
2965 )
2966 if with_for_update is not None:
2967 statement._for_update_arg = query.ForUpdateArg._from_argument(
2968 with_for_update
2969 )
2971 if options:
2972 statement = statement.options(*options)
2973 if execution_options:
2974 statement = statement.execution_options(**execution_options)
2975 return db_load_fn(
2976 self,
2977 statement,
2978 primary_key_identity,
2979 load_options=load_options,
2980 )
2982 def merge(self, instance, load=True, options=None):
2983 """Copy the state of a given instance into a corresponding instance
2984 within this :class:`.Session`.
2986 :meth:`.Session.merge` examines the primary key attributes of the
2987 source instance, and attempts to reconcile it with an instance of the
2988 same primary key in the session. If not found locally, it attempts
2989 to load the object from the database based on primary key, and if
2990 none can be located, creates a new instance. The state of each
2991 attribute on the source instance is then copied to the target
2992 instance. The resulting target instance is then returned by the
2993 method; the original source instance is left unmodified, and
2994 un-associated with the :class:`.Session` if not already.
2996 This operation cascades to associated instances if the association is
2997 mapped with ``cascade="merge"``.
2999 See :ref:`unitofwork_merging` for a detailed discussion of merging.
3001 .. versionchanged:: 1.1 - :meth:`.Session.merge` will now reconcile
3002 pending objects with overlapping primary keys in the same way
3003 as persistent. See :ref:`change_3601` for discussion.
3005 :param instance: Instance to be merged.
3006 :param load: Boolean, when False, :meth:`.merge` switches into
3007 a "high performance" mode which causes it to forego emitting history
3008 events as well as all database access. This flag is used for
3009 cases such as transferring graphs of objects into a :class:`.Session`
3010 from a second level cache, or to transfer just-loaded objects
3011 into the :class:`.Session` owned by a worker thread or process
3012 without re-querying the database.
3014 The ``load=False`` use case adds the caveat that the given
3015 object has to be in a "clean" state, that is, has no pending changes
3016 to be flushed - even if the incoming object is detached from any
3017 :class:`.Session`. This is so that when
3018 the merge operation populates local attributes and
3019 cascades to related objects and
3020 collections, the values can be "stamped" onto the
3021 target object as is, without generating any history or attribute
3022 events, and without the need to reconcile the incoming data with
3023 any existing related objects or collections that might not
3024 be loaded. The resulting objects from ``load=False`` are always
3025 produced as "clean", so it is only appropriate that the given objects
3026 should be "clean" as well, else this suggests a mis-use of the
3027 method.
3028 :param options: optional sequence of loader options which will be
3029 applied to the :meth:`_orm.Session.get` method when the merge
3030 operation loads the existing version of the object from the database.
3032 .. versionadded:: 1.4.24
3035 .. seealso::
3037 :func:`.make_transient_to_detached` - provides for an alternative
3038 means of "merging" a single object into the :class:`.Session`
3040 """
3042 if self._warn_on_events:
3043 self._flush_warning("Session.merge()")
3045 _recursive = {}
3046 _resolve_conflict_map = {}
3048 if load:
3049 # flush current contents if we expect to load data
3050 self._autoflush()
3052 object_mapper(instance) # verify mapped
3053 autoflush = self.autoflush
3054 try:
3055 self.autoflush = False
3056 return self._merge(
3057 attributes.instance_state(instance),
3058 attributes.instance_dict(instance),
3059 load=load,
3060 options=options,
3061 _recursive=_recursive,
3062 _resolve_conflict_map=_resolve_conflict_map,
3063 )
3064 finally:
3065 self.autoflush = autoflush
3067 def _merge(
3068 self,
3069 state,
3070 state_dict,
3071 load=True,
3072 options=None,
3073 _recursive=None,
3074 _resolve_conflict_map=None,
3075 ):
3076 mapper = _state_mapper(state)
3077 if state in _recursive:
3078 return _recursive[state]
3080 new_instance = False
3081 key = state.key
3083 if key is None:
3084 if state in self._new:
3085 util.warn(
3086 "Instance %s is already pending in this Session yet is "
3087 "being merged again; this is probably not what you want "
3088 "to do" % state_str(state)
3089 )
3091 if not load:
3092 raise sa_exc.InvalidRequestError(
3093 "merge() with load=False option does not support "
3094 "objects transient (i.e. unpersisted) objects. flush() "
3095 "all changes on mapped instances before merging with "
3096 "load=False."
3097 )
3098 key = mapper._identity_key_from_state(state)
3099 key_is_persistent = attributes.NEVER_SET not in key[1] and (
3100 not _none_set.intersection(key[1])
3101 or (
3102 mapper.allow_partial_pks
3103 and not _none_set.issuperset(key[1])
3104 )
3105 )
3106 else:
3107 key_is_persistent = True
3109 if key in self.identity_map:
3110 try:
3111 merged = self.identity_map[key]
3112 except KeyError:
3113 # object was GC'ed right as we checked for it
3114 merged = None
3115 else:
3116 merged = None
3118 if merged is None:
3119 if key_is_persistent and key in _resolve_conflict_map:
3120 merged = _resolve_conflict_map[key]
3122 elif not load:
3123 if state.modified:
3124 raise sa_exc.InvalidRequestError(
3125 "merge() with load=False option does not support "
3126 "objects marked as 'dirty'. flush() all changes on "
3127 "mapped instances before merging with load=False."
3128 )
3129 merged = mapper.class_manager.new_instance()
3130 merged_state = attributes.instance_state(merged)
3131 merged_state.key = key
3132 self._update_impl(merged_state)
3133 new_instance = True
3135 elif key_is_persistent:
3136 merged = self.get(
3137 mapper.class_,
3138 key[1],
3139 identity_token=key[2],
3140 options=options,
3141 )
3143 if merged is None:
3144 merged = mapper.class_manager.new_instance()
3145 merged_state = attributes.instance_state(merged)
3146 merged_dict = attributes.instance_dict(merged)
3147 new_instance = True
3148 self._save_or_update_state(merged_state)
3149 else:
3150 merged_state = attributes.instance_state(merged)
3151 merged_dict = attributes.instance_dict(merged)
3153 _recursive[state] = merged
3154 _resolve_conflict_map[key] = merged
3156 # check that we didn't just pull the exact same
3157 # state out.
3158 if state is not merged_state:
3159 # version check if applicable
3160 if mapper.version_id_col is not None:
3161 existing_version = mapper._get_state_attr_by_column(
3162 state,
3163 state_dict,
3164 mapper.version_id_col,
3165 passive=attributes.PASSIVE_NO_INITIALIZE,
3166 )
3168 merged_version = mapper._get_state_attr_by_column(
3169 merged_state,
3170 merged_dict,
3171 mapper.version_id_col,
3172 passive=attributes.PASSIVE_NO_INITIALIZE,
3173 )
3175 if (
3176 existing_version is not attributes.PASSIVE_NO_RESULT
3177 and merged_version is not attributes.PASSIVE_NO_RESULT
3178 and existing_version != merged_version
3179 ):
3180 raise exc.StaleDataError(
3181 "Version id '%s' on merged state %s "
3182 "does not match existing version '%s'. "
3183 "Leave the version attribute unset when "
3184 "merging to update the most recent version."
3185 % (
3186 existing_version,
3187 state_str(merged_state),
3188 merged_version,
3189 )
3190 )
3192 merged_state.load_path = state.load_path
3193 merged_state.load_options = state.load_options
3195 # since we are copying load_options, we need to copy
3196 # the callables_ that would have been generated by those
3197 # load_options.
3198 # assumes that the callables we put in state.callables_
3199 # are not instance-specific (which they should not be)
3200 merged_state._copy_callables(state)
3202 for prop in mapper.iterate_properties:
3203 prop.merge(
3204 self,
3205 state,
3206 state_dict,
3207 merged_state,
3208 merged_dict,
3209 load,
3210 _recursive,
3211 _resolve_conflict_map,
3212 )
3214 if not load:
3215 # remove any history
3216 merged_state._commit_all(merged_dict, self.identity_map)
3217 merged_state.manager.dispatch._sa_event_merge_wo_load(
3218 merged_state, None
3219 )
3221 if new_instance:
3222 merged_state.manager.dispatch.load(merged_state, None)
3223 return merged
3225 def _validate_persistent(self, state):
3226 if not self.identity_map.contains_state(state):
3227 raise sa_exc.InvalidRequestError(
3228 "Instance '%s' is not persistent within this Session"
3229 % state_str(state)
3230 )
3232 def _save_impl(self, state):
3233 if state.key is not None:
3234 raise sa_exc.InvalidRequestError(
3235 "Object '%s' already has an identity - "
3236 "it can't be registered as pending" % state_str(state)
3237 )
3239 obj = state.obj()
3240 to_attach = self._before_attach(state, obj)
3241 if state not in self._new:
3242 self._new[state] = obj
3243 state.insert_order = len(self._new)
3244 if to_attach:
3245 self._after_attach(state, obj)
3247 def _update_impl(self, state, revert_deletion=False):
3248 if state.key is None:
3249 raise sa_exc.InvalidRequestError(
3250 "Instance '%s' is not persisted" % state_str(state)
3251 )
3253 if state._deleted:
3254 if revert_deletion:
3255 if not state._attached:
3256 return
3257 del state._deleted
3258 else:
3259 raise sa_exc.InvalidRequestError(
3260 "Instance '%s' has been deleted. "
3261 "Use the make_transient() "
3262 "function to send this object back "
3263 "to the transient state." % state_str(state)
3264 )
3266 obj = state.obj()
3268 # check for late gc
3269 if obj is None:
3270 return
3272 to_attach = self._before_attach(state, obj)
3274 self._deleted.pop(state, None)
3275 if revert_deletion:
3276 self.identity_map.replace(state)
3277 else:
3278 self.identity_map.add(state)
3280 if to_attach:
3281 self._after_attach(state, obj)
3282 elif revert_deletion:
3283 self.dispatch.deleted_to_persistent(self, state)
3285 def _save_or_update_impl(self, state):
3286 if state.key is None:
3287 self._save_impl(state)
3288 else:
3289 self._update_impl(state)
3291 def enable_relationship_loading(self, obj):
3292 """Associate an object with this :class:`.Session` for related
3293 object loading.
3295 .. warning::
3297 :meth:`.enable_relationship_loading` exists to serve special
3298 use cases and is not recommended for general use.
3300 Accesses of attributes mapped with :func:`_orm.relationship`
3301 will attempt to load a value from the database using this
3302 :class:`.Session` as the source of connectivity. The values
3303 will be loaded based on foreign key and primary key values
3304 present on this object - if not present, then those relationships
3305 will be unavailable.
3307 The object will be attached to this session, but will
3308 **not** participate in any persistence operations; its state
3309 for almost all purposes will remain either "transient" or
3310 "detached", except for the case of relationship loading.
3312 Also note that backrefs will often not work as expected.
3313 Altering a relationship-bound attribute on the target object
3314 may not fire off a backref event, if the effective value
3315 is what was already loaded from a foreign-key-holding value.
3317 The :meth:`.Session.enable_relationship_loading` method is
3318 similar to the ``load_on_pending`` flag on :func:`_orm.relationship`.
3319 Unlike that flag, :meth:`.Session.enable_relationship_loading` allows
3320 an object to remain transient while still being able to load
3321 related items.
3323 To make a transient object associated with a :class:`.Session`
3324 via :meth:`.Session.enable_relationship_loading` pending, add
3325 it to the :class:`.Session` using :meth:`.Session.add` normally.
3326 If the object instead represents an existing identity in the database,
3327 it should be merged using :meth:`.Session.merge`.
3329 :meth:`.Session.enable_relationship_loading` does not improve
3330 behavior when the ORM is used normally - object references should be
3331 constructed at the object level, not at the foreign key level, so
3332 that they are present in an ordinary way before flush()
3333 proceeds. This method is not intended for general use.
3335 .. seealso::
3337 :paramref:`_orm.relationship.load_on_pending` - this flag
3338 allows per-relationship loading of many-to-ones on items that
3339 are pending.
3341 :func:`.make_transient_to_detached` - allows for an object to
3342 be added to a :class:`.Session` without SQL emitted, which then
3343 will unexpire attributes on access.
3345 """
3346 try:
3347 state = attributes.instance_state(obj)
3348 except exc.NO_STATE as err:
3349 util.raise_(
3350 exc.UnmappedInstanceError(obj),
3351 replace_context=err,
3352 )
3354 to_attach = self._before_attach(state, obj)
3355 state._load_pending = True
3356 if to_attach:
3357 self._after_attach(state, obj)
3359 def _before_attach(self, state, obj):
3360 self._autobegin()
3362 if state.session_id == self.hash_key:
3363 return False
3365 if state.session_id and state.session_id in _sessions:
3366 raise sa_exc.InvalidRequestError(
3367 "Object '%s' is already attached to session '%s' "
3368 "(this is '%s')"
3369 % (state_str(state), state.session_id, self.hash_key)
3370 )
3372 self.dispatch.before_attach(self, state)
3374 return True
3376 def _after_attach(self, state, obj):
3377 state.session_id = self.hash_key
3378 if state.modified and state._strong_obj is None:
3379 state._strong_obj = obj
3380 self.dispatch.after_attach(self, state)
3382 if state.key:
3383 self.dispatch.detached_to_persistent(self, state)
3384 else:
3385 self.dispatch.transient_to_pending(self, state)
3387 def __contains__(self, instance):
3388 """Return True if the instance is associated with this session.
3390 The instance may be pending or persistent within the Session for a
3391 result of True.
3393 """
3394 try:
3395 state = attributes.instance_state(instance)
3396 except exc.NO_STATE as err:
3397 util.raise_(
3398 exc.UnmappedInstanceError(instance),
3399 replace_context=err,
3400 )
3401 return self._contains_state(state)
3403 def __iter__(self):
3404 """Iterate over all pending or persistent instances within this
3405 Session.
3407 """
3408 return iter(
3409 list(self._new.values()) + list(self.identity_map.values())
3410 )
3412 def _contains_state(self, state):
3413 return state in self._new or self.identity_map.contains_state(state)
3415 def flush(self, objects=None):
3416 """Flush all the object changes to the database.
3418 Writes out all pending object creations, deletions and modifications
3419 to the database as INSERTs, DELETEs, UPDATEs, etc. Operations are
3420 automatically ordered by the Session's unit of work dependency
3421 solver.
3423 Database operations will be issued in the current transactional
3424 context and do not affect the state of the transaction, unless an
3425 error occurs, in which case the entire transaction is rolled back.
3426 You may flush() as often as you like within a transaction to move
3427 changes from Python to the database's transaction buffer.
3429 For ``autocommit`` Sessions with no active manual transaction, flush()
3430 will create a transaction on the fly that surrounds the entire set of
3431 operations into the flush.
3433 :param objects: Optional; restricts the flush operation to operate
3434 only on elements that are in the given collection.
3436 This feature is for an extremely narrow set of use cases where
3437 particular objects may need to be operated upon before the
3438 full flush() occurs. It is not intended for general use.
3440 """
3442 if self._flushing:
3443 raise sa_exc.InvalidRequestError("Session is already flushing")
3445 if self._is_clean():
3446 return
3447 try:
3448 self._flushing = True
3449 self._flush(objects)
3450 finally:
3451 self._flushing = False
3453 def _flush_warning(self, method):
3454 util.warn(
3455 "Usage of the '%s' operation is not currently supported "
3456 "within the execution stage of the flush process. "
3457 "Results may not be consistent. Consider using alternative "
3458 "event listeners or connection-level operations instead." % method
3459 )
3461 def _is_clean(self):
3462 return (
3463 not self.identity_map.check_modified()
3464 and not self._deleted
3465 and not self._new
3466 )
3468 def _flush(self, objects=None):
3470 dirty = self._dirty_states
3471 if not dirty and not self._deleted and not self._new:
3472 self.identity_map._modified.clear()
3473 return
3475 flush_context = UOWTransaction(self)
3477 if self.dispatch.before_flush:
3478 self.dispatch.before_flush(self, flush_context, objects)
3479 # re-establish "dirty states" in case the listeners
3480 # added
3481 dirty = self._dirty_states
3483 deleted = set(self._deleted)
3484 new = set(self._new)
3486 dirty = set(dirty).difference(deleted)
3488 # create the set of all objects we want to operate upon
3489 if objects:
3490 # specific list passed in
3491 objset = set()
3492 for o in objects:
3493 try:
3494 state = attributes.instance_state(o)
3496 except exc.NO_STATE as err:
3497 util.raise_(
3498 exc.UnmappedInstanceError(o),
3499 replace_context=err,
3500 )
3501 objset.add(state)
3502 else:
3503 objset = None
3505 # store objects whose fate has been decided
3506 processed = set()
3508 # put all saves/updates into the flush context. detect top-level
3509 # orphans and throw them into deleted.
3510 if objset:
3511 proc = new.union(dirty).intersection(objset).difference(deleted)
3512 else:
3513 proc = new.union(dirty).difference(deleted)
3515 for state in proc:
3516 is_orphan = _state_mapper(state)._is_orphan(state)
3518 is_persistent_orphan = is_orphan and state.has_identity
3520 if (
3521 is_orphan
3522 and not is_persistent_orphan
3523 and state._orphaned_outside_of_session
3524 ):
3525 self._expunge_states([state])
3526 else:
3527 _reg = flush_context.register_object(
3528 state, isdelete=is_persistent_orphan
3529 )
3530 assert _reg, "Failed to add object to the flush context!"
3531 processed.add(state)
3533 # put all remaining deletes into the flush context.
3534 if objset:
3535 proc = deleted.intersection(objset).difference(processed)
3536 else:
3537 proc = deleted.difference(processed)
3538 for state in proc:
3539 _reg = flush_context.register_object(state, isdelete=True)
3540 assert _reg, "Failed to add object to the flush context!"
3542 if not flush_context.has_work:
3543 return
3545 flush_context.transaction = transaction = self.begin(_subtrans=True)
3546 try:
3547 self._warn_on_events = True
3548 try:
3549 flush_context.execute()
3550 finally:
3551 self._warn_on_events = False
3553 self.dispatch.after_flush(self, flush_context)
3555 flush_context.finalize_flush_changes()
3557 if not objects and self.identity_map._modified:
3558 len_ = len(self.identity_map._modified)
3560 statelib.InstanceState._commit_all_states(
3561 [
3562 (state, state.dict)
3563 for state in self.identity_map._modified
3564 ],
3565 instance_dict=self.identity_map,
3566 )
3567 util.warn(
3568 "Attribute history events accumulated on %d "
3569 "previously clean instances "
3570 "within inner-flush event handlers have been "
3571 "reset, and will not result in database updates. "
3572 "Consider using set_committed_value() within "
3573 "inner-flush event handlers to avoid this warning." % len_
3574 )
3576 # useful assertions:
3577 # if not objects:
3578 # assert not self.identity_map._modified
3579 # else:
3580 # assert self.identity_map._modified == \
3581 # self.identity_map._modified.difference(objects)
3583 self.dispatch.after_flush_postexec(self, flush_context)
3585 transaction.commit()
3587 except:
3588 with util.safe_reraise():
3589 transaction.rollback(_capture_exception=True)
3591 def bulk_save_objects(
3592 self,
3593 objects,
3594 return_defaults=False,
3595 update_changed_only=True,
3596 preserve_order=True,
3597 ):
3598 """Perform a bulk save of the given list of objects.
3600 The bulk save feature allows mapped objects to be used as the
3601 source of simple INSERT and UPDATE operations which can be more easily
3602 grouped together into higher performing "executemany"
3603 operations; the extraction of data from the objects is also performed
3604 using a lower-latency process that ignores whether or not attributes
3605 have actually been modified in the case of UPDATEs, and also ignores
3606 SQL expressions.
3608 The objects as given are not added to the session and no additional
3609 state is established on them. If the
3610 :paramref:`_orm.Session.bulk_save_objects.return_defaults` flag is set,
3611 then server-generated primary key values will be assigned to the
3612 returned objects, but **not server side defaults**; this is a
3613 limitation in the implementation. If stateful objects are desired,
3614 please use the standard :meth:`_orm.Session.add_all` approach or
3615 as an alternative newer mass-insert features such as
3616 :ref:`orm_dml_returning_objects`.
3618 .. legacy::
3620 The bulk save feature allows for a lower-latency INSERT/UPDATE
3621 of rows at the expense of most other unit-of-work features.
3622 Features such as object management, relationship handling,
3623 and SQL clause support are silently omitted in favor of raw
3624 INSERT/UPDATES of records.
3626 In SQLAlchemy 2.0, improved versions of the bulk insert/update
3627 methods are introduced, with clearer behavior and
3628 documentation, new capabilities, and much better performance.
3630 For 1.4 use, **please read the list of caveats at**
3631 :ref:`bulk_operations_caveats` **before using this method, and
3632 fully test and confirm the functionality of all code developed
3633 using these systems.**
3635 :param objects: a sequence of mapped object instances. The mapped
3636 objects are persisted as is, and are **not** associated with the
3637 :class:`.Session` afterwards.
3639 For each object, whether the object is sent as an INSERT or an
3640 UPDATE is dependent on the same rules used by the :class:`.Session`
3641 in traditional operation; if the object has the
3642 :attr:`.InstanceState.key`
3643 attribute set, then the object is assumed to be "detached" and
3644 will result in an UPDATE. Otherwise, an INSERT is used.
3646 In the case of an UPDATE, statements are grouped based on which
3647 attributes have changed, and are thus to be the subject of each
3648 SET clause. If ``update_changed_only`` is False, then all
3649 attributes present within each object are applied to the UPDATE
3650 statement, which may help in allowing the statements to be grouped
3651 together into a larger executemany(), and will also reduce the
3652 overhead of checking history on attributes.
3654 :param return_defaults: when True, rows that are missing values which
3655 generate defaults, namely integer primary key defaults and sequences,
3656 will be inserted **one at a time**, so that the primary key value
3657 is available. In particular this will allow joined-inheritance
3658 and other multi-table mappings to insert correctly without the need
3659 to provide primary key values ahead of time; however,
3660 :paramref:`.Session.bulk_save_objects.return_defaults` **greatly
3661 reduces the performance gains** of the method overall. It is strongly
3662 advised to please use the standard :meth:`_orm.Session.add_all`
3663 approach.
3665 :param update_changed_only: when True, UPDATE statements are rendered
3666 based on those attributes in each state that have logged changes.
3667 When False, all attributes present are rendered into the SET clause
3668 with the exception of primary key attributes.
3670 :param preserve_order: when True, the order of inserts and updates
3671 matches exactly the order in which the objects are given. When
3672 False, common types of objects are grouped into inserts
3673 and updates, to allow for more batching opportunities.
3675 .. versionadded:: 1.3
3677 .. seealso::
3679 :ref:`bulk_operations`
3681 :meth:`.Session.bulk_insert_mappings`
3683 :meth:`.Session.bulk_update_mappings`
3685 """
3687 obj_states = (attributes.instance_state(obj) for obj in objects)
3689 if not preserve_order:
3690 # the purpose of this sort is just so that common mappers
3691 # and persistence states are grouped together, so that groupby
3692 # will return a single group for a particular type of mapper.
3693 # it's not trying to be deterministic beyond that.
3694 obj_states = sorted(
3695 obj_states,
3696 key=lambda state: (id(state.mapper), state.key is not None),
3697 )
3699 def grouping_key(state):
3700 return (state.mapper, state.key is not None)
3702 for (mapper, isupdate), states in itertools.groupby(
3703 obj_states, grouping_key
3704 ):
3705 self._bulk_save_mappings(
3706 mapper,
3707 states,
3708 isupdate,
3709 True,
3710 return_defaults,
3711 update_changed_only,
3712 False,
3713 )
3715 def bulk_insert_mappings(
3716 self, mapper, mappings, return_defaults=False, render_nulls=False
3717 ):
3718 """Perform a bulk insert of the given list of mapping dictionaries.
3720 The bulk insert feature allows plain Python dictionaries to be used as
3721 the source of simple INSERT operations which can be more easily
3722 grouped together into higher performing "executemany"
3723 operations. Using dictionaries, there is no "history" or session
3724 state management features in use, reducing latency when inserting
3725 large numbers of simple rows.
3727 The values within the dictionaries as given are typically passed
3728 without modification into Core :meth:`_expression.Insert` constructs,
3729 after
3730 organizing the values within them across the tables to which
3731 the given mapper is mapped.
3733 .. versionadded:: 1.0.0
3735 .. legacy::
3737 The bulk insert feature allows for a lower-latency INSERT
3738 of rows at the expense of most other unit-of-work features.
3739 Features such as object management, relationship handling,
3740 and SQL clause support are silently omitted in favor of raw
3741 INSERT of records.
3743 In SQLAlchemy 2.0, improved versions of the bulk insert/update
3744 methods are introduced, with clearer behavior and
3745 documentation, new capabilities, and much better performance.
3747 For 1.4 use, **please read the list of caveats at**
3748 :ref:`bulk_operations_caveats` **before using this method, and
3749 fully test and confirm the functionality of all code developed
3750 using these systems.**
3752 :param mapper: a mapped class, or the actual :class:`_orm.Mapper`
3753 object,
3754 representing the single kind of object represented within the mapping
3755 list.
3757 :param mappings: a sequence of dictionaries, each one containing the
3758 state of the mapped row to be inserted, in terms of the attribute
3759 names on the mapped class. If the mapping refers to multiple tables,
3760 such as a joined-inheritance mapping, each dictionary must contain all
3761 keys to be populated into all tables.
3763 :param return_defaults: when True, rows that are missing values which
3764 generate defaults, namely integer primary key defaults and sequences,
3765 will be inserted **one at a time**, so that the primary key value
3766 is available. In particular this will allow joined-inheritance
3767 and other multi-table mappings to insert correctly without the need
3768 to provide primary
3769 key values ahead of time; however,
3770 :paramref:`.Session.bulk_insert_mappings.return_defaults`
3771 **greatly reduces the performance gains** of the method overall.
3772 If the rows
3773 to be inserted only refer to a single table, then there is no
3774 reason this flag should be set as the returned default information
3775 is not used.
3777 :param render_nulls: When True, a value of ``None`` will result
3778 in a NULL value being included in the INSERT statement, rather
3779 than the column being omitted from the INSERT. This allows all
3780 the rows being INSERTed to have the identical set of columns which
3781 allows the full set of rows to be batched to the DBAPI. Normally,
3782 each column-set that contains a different combination of NULL values
3783 than the previous row must omit a different series of columns from
3784 the rendered INSERT statement, which means it must be emitted as a
3785 separate statement. By passing this flag, the full set of rows
3786 are guaranteed to be batchable into one batch; the cost however is
3787 that server-side defaults which are invoked by an omitted column will
3788 be skipped, so care must be taken to ensure that these are not
3789 necessary.
3791 .. warning::
3793 When this flag is set, **server side default SQL values will
3794 not be invoked** for those columns that are inserted as NULL;
3795 the NULL value will be sent explicitly. Care must be taken
3796 to ensure that no server-side default functions need to be
3797 invoked for the operation as a whole.
3799 .. versionadded:: 1.1
3801 .. seealso::
3803 :ref:`bulk_operations`
3805 :meth:`.Session.bulk_save_objects`
3807 :meth:`.Session.bulk_update_mappings`
3809 """
3810 self._bulk_save_mappings(
3811 mapper,
3812 mappings,
3813 False,
3814 False,
3815 return_defaults,
3816 False,
3817 render_nulls,
3818 )
3820 def bulk_update_mappings(self, mapper, mappings):
3821 """Perform a bulk update of the given list of mapping dictionaries.
3823 The bulk update feature allows plain Python dictionaries to be used as
3824 the source of simple UPDATE operations which can be more easily
3825 grouped together into higher performing "executemany"
3826 operations. Using dictionaries, there is no "history" or session
3827 state management features in use, reducing latency when updating
3828 large numbers of simple rows.
3830 .. versionadded:: 1.0.0
3832 .. legacy::
3834 The bulk update feature allows for a lower-latency UPDATE
3835 of rows at the expense of most other unit-of-work features.
3836 Features such as object management, relationship handling,
3837 and SQL clause support are silently omitted in favor of raw
3838 UPDATES of records.
3840 In SQLAlchemy 2.0, improved versions of the bulk insert/update
3841 methods are introduced, with clearer behavior and
3842 documentation, new capabilities, and much better performance.
3844 For 1.4 use, **please read the list of caveats at**
3845 :ref:`bulk_operations_caveats` **before using this method, and
3846 fully test and confirm the functionality of all code developed
3847 using these systems.**
3849 :param mapper: a mapped class, or the actual :class:`_orm.Mapper`
3850 object,
3851 representing the single kind of object represented within the mapping
3852 list.
3854 :param mappings: a sequence of dictionaries, each one containing the
3855 state of the mapped row to be updated, in terms of the attribute names
3856 on the mapped class. If the mapping refers to multiple tables, such
3857 as a joined-inheritance mapping, each dictionary may contain keys
3858 corresponding to all tables. All those keys which are present and
3859 are not part of the primary key are applied to the SET clause of the
3860 UPDATE statement; the primary key values, which are required, are
3861 applied to the WHERE clause.
3864 .. seealso::
3866 :ref:`bulk_operations`
3868 :meth:`.Session.bulk_insert_mappings`
3870 :meth:`.Session.bulk_save_objects`
3872 """
3873 self._bulk_save_mappings(
3874 mapper, mappings, True, False, False, False, False
3875 )
3877 def _bulk_save_mappings(
3878 self,
3879 mapper,
3880 mappings,
3881 isupdate,
3882 isstates,
3883 return_defaults,
3884 update_changed_only,
3885 render_nulls,
3886 ):
3887 mapper = _class_to_mapper(mapper)
3888 self._flushing = True
3890 transaction = self.begin(_subtrans=True)
3891 try:
3892 if isupdate:
3893 persistence._bulk_update(
3894 mapper,
3895 mappings,
3896 transaction,
3897 isstates,
3898 update_changed_only,
3899 )
3900 else:
3901 persistence._bulk_insert(
3902 mapper,
3903 mappings,
3904 transaction,
3905 isstates,
3906 return_defaults,
3907 render_nulls,
3908 )
3909 transaction.commit()
3911 except:
3912 with util.safe_reraise():
3913 transaction.rollback(_capture_exception=True)
3914 finally:
3915 self._flushing = False
3917 def is_modified(self, instance, include_collections=True):
3918 r"""Return ``True`` if the given instance has locally
3919 modified attributes.
3921 This method retrieves the history for each instrumented
3922 attribute on the instance and performs a comparison of the current
3923 value to its previously committed value, if any.
3925 It is in effect a more expensive and accurate
3926 version of checking for the given instance in the
3927 :attr:`.Session.dirty` collection; a full test for
3928 each attribute's net "dirty" status is performed.
3930 E.g.::
3932 return session.is_modified(someobject)
3934 A few caveats to this method apply:
3936 * Instances present in the :attr:`.Session.dirty` collection may
3937 report ``False`` when tested with this method. This is because
3938 the object may have received change events via attribute mutation,
3939 thus placing it in :attr:`.Session.dirty`, but ultimately the state
3940 is the same as that loaded from the database, resulting in no net
3941 change here.
3942 * Scalar attributes may not have recorded the previously set
3943 value when a new value was applied, if the attribute was not loaded,
3944 or was expired, at the time the new value was received - in these
3945 cases, the attribute is assumed to have a change, even if there is
3946 ultimately no net change against its database value. SQLAlchemy in
3947 most cases does not need the "old" value when a set event occurs, so
3948 it skips the expense of a SQL call if the old value isn't present,
3949 based on the assumption that an UPDATE of the scalar value is
3950 usually needed, and in those few cases where it isn't, is less
3951 expensive on average than issuing a defensive SELECT.
3953 The "old" value is fetched unconditionally upon set only if the
3954 attribute container has the ``active_history`` flag set to ``True``.
3955 This flag is set typically for primary key attributes and scalar
3956 object references that are not a simple many-to-one. To set this
3957 flag for any arbitrary mapped column, use the ``active_history``
3958 argument with :func:`.column_property`.
3960 :param instance: mapped instance to be tested for pending changes.
3961 :param include_collections: Indicates if multivalued collections
3962 should be included in the operation. Setting this to ``False`` is a
3963 way to detect only local-column based properties (i.e. scalar columns
3964 or many-to-one foreign keys) that would result in an UPDATE for this
3965 instance upon flush.
3967 """
3968 state = object_state(instance)
3970 if not state.modified:
3971 return False
3973 dict_ = state.dict
3975 for attr in state.manager.attributes:
3976 if (
3977 not include_collections
3978 and hasattr(attr.impl, "get_collection")
3979 ) or not hasattr(attr.impl, "get_history"):
3980 continue
3982 (added, unchanged, deleted) = attr.impl.get_history(
3983 state, dict_, passive=attributes.NO_CHANGE
3984 )
3986 if added or deleted:
3987 return True
3988 else:
3989 return False
3991 @property
3992 def is_active(self):
3993 """True if this :class:`.Session` not in "partial rollback" state.
3995 .. versionchanged:: 1.4 The :class:`_orm.Session` no longer begins
3996 a new transaction immediately, so this attribute will be False
3997 when the :class:`_orm.Session` is first instantiated.
3999 "partial rollback" state typically indicates that the flush process
4000 of the :class:`_orm.Session` has failed, and that the
4001 :meth:`_orm.Session.rollback` method must be emitted in order to
4002 fully roll back the transaction.
4004 If this :class:`_orm.Session` is not in a transaction at all, the
4005 :class:`_orm.Session` will autobegin when it is first used, so in this
4006 case :attr:`_orm.Session.is_active` will return True.
4008 Otherwise, if this :class:`_orm.Session` is within a transaction,
4009 and that transaction has not been rolled back internally, the
4010 :attr:`_orm.Session.is_active` will also return True.
4012 .. seealso::
4014 :ref:`faq_session_rollback`
4016 :meth:`_orm.Session.in_transaction`
4018 """
4019 if self.autocommit:
4020 return (
4021 self._transaction is not None and self._transaction.is_active
4022 )
4023 else:
4024 return self._transaction is None or self._transaction.is_active
4026 identity_map = None
4027 """A mapping of object identities to objects themselves.
4029 Iterating through ``Session.identity_map.values()`` provides
4030 access to the full set of persistent objects (i.e., those
4031 that have row identity) currently in the session.
4033 .. seealso::
4035 :func:`.identity_key` - helper function to produce the keys used
4036 in this dictionary.
4038 """
4040 @property
4041 def _dirty_states(self):
4042 """The set of all persistent states considered dirty.
4044 This method returns all states that were modified including
4045 those that were possibly deleted.
4047 """
4048 return self.identity_map._dirty_states()
4050 @property
4051 def dirty(self):
4052 """The set of all persistent instances considered dirty.
4054 E.g.::
4056 some_mapped_object in session.dirty
4058 Instances are considered dirty when they were modified but not
4059 deleted.
4061 Note that this 'dirty' calculation is 'optimistic'; most
4062 attribute-setting or collection modification operations will
4063 mark an instance as 'dirty' and place it in this set, even if
4064 there is no net change to the attribute's value. At flush
4065 time, the value of each attribute is compared to its
4066 previously saved value, and if there's no net change, no SQL
4067 operation will occur (this is a more expensive operation so
4068 it's only done at flush time).
4070 To check if an instance has actionable net changes to its
4071 attributes, use the :meth:`.Session.is_modified` method.
4073 """
4074 return util.IdentitySet(
4075 [
4076 state.obj()
4077 for state in self._dirty_states
4078 if state not in self._deleted
4079 ]
4080 )
4082 @property
4083 def deleted(self):
4084 "The set of all instances marked as 'deleted' within this ``Session``"
4086 return util.IdentitySet(list(self._deleted.values()))
4088 @property
4089 def new(self):
4090 "The set of all instances marked as 'new' within this ``Session``."
4092 return util.IdentitySet(list(self._new.values()))
4095class sessionmaker(_SessionClassMethods):
4096 """A configurable :class:`.Session` factory.
4098 The :class:`.sessionmaker` factory generates new
4099 :class:`.Session` objects when called, creating them given
4100 the configurational arguments established here.
4102 e.g.::
4104 from sqlalchemy import create_engine
4105 from sqlalchemy.orm import sessionmaker
4107 # an Engine, which the Session will use for connection
4108 # resources
4109 engine = create_engine('postgresql://scott:tiger@localhost/')
4111 Session = sessionmaker(engine)
4113 with Session() as session:
4114 session.add(some_object)
4115 session.add(some_other_object)
4116 session.commit()
4118 Context manager use is optional; otherwise, the returned
4119 :class:`_orm.Session` object may be closed explicitly via the
4120 :meth:`_orm.Session.close` method. Using a
4121 ``try:/finally:`` block is optional, however will ensure that the close
4122 takes place even if there are database errors::
4124 session = Session()
4125 try:
4126 session.add(some_object)
4127 session.add(some_other_object)
4128 session.commit()
4129 finally:
4130 session.close()
4132 :class:`.sessionmaker` acts as a factory for :class:`_orm.Session`
4133 objects in the same way as an :class:`_engine.Engine` acts as a factory
4134 for :class:`_engine.Connection` objects. In this way it also includes
4135 a :meth:`_orm.sessionmaker.begin` method, that provides a context
4136 manager which both begins and commits a transaction, as well as closes
4137 out the :class:`_orm.Session` when complete, rolling back the transaction
4138 if any errors occur::
4140 Session = sessionmaker(engine)
4142 with Session.begin() as session:
4143 session.add(some_object)
4144 session.add(some_other_object)
4145 # commits transaction, closes session
4147 .. versionadded:: 1.4
4149 When calling upon :class:`_orm.sessionmaker` to construct a
4150 :class:`_orm.Session`, keyword arguments may also be passed to the
4151 method; these arguments will override that of the globally configured
4152 parameters. Below we use a :class:`_orm.sessionmaker` bound to a certain
4153 :class:`_engine.Engine` to produce a :class:`_orm.Session` that is instead
4154 bound to a specific :class:`_engine.Connection` procured from that engine::
4156 Session = sessionmaker(engine)
4158 # bind an individual session to a connection
4160 with engine.connect() as connection:
4161 with Session(bind=connection) as session:
4162 # work with session
4164 The class also includes a method :meth:`_orm.sessionmaker.configure`, which
4165 can be used to specify additional keyword arguments to the factory, which
4166 will take effect for subsequent :class:`.Session` objects generated. This
4167 is usually used to associate one or more :class:`_engine.Engine` objects
4168 with an existing
4169 :class:`.sessionmaker` factory before it is first used::
4171 # application starts, sessionmaker does not have
4172 # an engine bound yet
4173 Session = sessionmaker()
4175 # ... later, when an engine URL is read from a configuration
4176 # file or other events allow the engine to be created
4177 engine = create_engine('sqlite:///foo.db')
4178 Session.configure(bind=engine)
4180 sess = Session()
4181 # work with session
4183 .. seealso::
4185 :ref:`session_getting` - introductory text on creating
4186 sessions using :class:`.sessionmaker`.
4188 """
4190 def __init__(
4191 self,
4192 bind=None,
4193 class_=Session,
4194 autoflush=True,
4195 autocommit=False,
4196 expire_on_commit=True,
4197 info=None,
4198 **kw
4199 ):
4200 r"""Construct a new :class:`.sessionmaker`.
4202 All arguments here except for ``class_`` correspond to arguments
4203 accepted by :class:`.Session` directly. See the
4204 :meth:`.Session.__init__` docstring for more details on parameters.
4206 :param bind: a :class:`_engine.Engine` or other :class:`.Connectable`
4207 with
4208 which newly created :class:`.Session` objects will be associated.
4209 :param class\_: class to use in order to create new :class:`.Session`
4210 objects. Defaults to :class:`.Session`.
4211 :param autoflush: The autoflush setting to use with newly created
4212 :class:`.Session` objects.
4213 :param autocommit: The autocommit setting to use with newly created
4214 :class:`.Session` objects.
4215 :param expire_on_commit=True: the
4216 :paramref:`_orm.Session.expire_on_commit` setting to use
4217 with newly created :class:`.Session` objects.
4219 :param info: optional dictionary of information that will be available
4220 via :attr:`.Session.info`. Note this dictionary is *updated*, not
4221 replaced, when the ``info`` parameter is specified to the specific
4222 :class:`.Session` construction operation.
4224 :param \**kw: all other keyword arguments are passed to the
4225 constructor of newly created :class:`.Session` objects.
4227 """
4228 kw["bind"] = bind
4229 kw["autoflush"] = autoflush
4230 kw["autocommit"] = autocommit
4231 kw["expire_on_commit"] = expire_on_commit
4232 if info is not None:
4233 kw["info"] = info
4234 self.kw = kw
4235 # make our own subclass of the given class, so that
4236 # events can be associated with it specifically.
4237 self.class_ = type(class_.__name__, (class_,), {})
4239 def begin(self):
4240 """Produce a context manager that both provides a new
4241 :class:`_orm.Session` as well as a transaction that commits.
4244 e.g.::
4246 Session = sessionmaker(some_engine)
4248 with Session.begin() as session:
4249 session.add(some_object)
4251 # commits transaction, closes session
4253 .. versionadded:: 1.4
4256 """
4258 session = self()
4259 return session._maker_context_manager()
4261 def __call__(self, **local_kw):
4262 """Produce a new :class:`.Session` object using the configuration
4263 established in this :class:`.sessionmaker`.
4265 In Python, the ``__call__`` method is invoked on an object when
4266 it is "called" in the same way as a function::
4268 Session = sessionmaker()
4269 session = Session() # invokes sessionmaker.__call__()
4271 """
4272 for k, v in self.kw.items():
4273 if k == "info" and "info" in local_kw:
4274 d = v.copy()
4275 d.update(local_kw["info"])
4276 local_kw["info"] = d
4277 else:
4278 local_kw.setdefault(k, v)
4279 return self.class_(**local_kw)
4281 def configure(self, **new_kw):
4282 """(Re)configure the arguments for this sessionmaker.
4284 e.g.::
4286 Session = sessionmaker()
4288 Session.configure(bind=create_engine('sqlite://'))
4289 """
4290 self.kw.update(new_kw)
4292 def __repr__(self):
4293 return "%s(class_=%r, %s)" % (
4294 self.__class__.__name__,
4295 self.class_.__name__,
4296 ", ".join("%s=%r" % (k, v) for k, v in self.kw.items()),
4297 )
4300def close_all_sessions():
4301 """Close all sessions in memory.
4303 This function consults a global registry of all :class:`.Session` objects
4304 and calls :meth:`.Session.close` on them, which resets them to a clean
4305 state.
4307 This function is not for general use but may be useful for test suites
4308 within the teardown scheme.
4310 .. versionadded:: 1.3
4312 """
4314 for sess in _sessions.values():
4315 sess.close()
4318def make_transient(instance):
4319 """Alter the state of the given instance so that it is :term:`transient`.
4321 .. note::
4323 :func:`.make_transient` is a special-case function for
4324 advanced use cases only.
4326 The given mapped instance is assumed to be in the :term:`persistent` or
4327 :term:`detached` state. The function will remove its association with any
4328 :class:`.Session` as well as its :attr:`.InstanceState.identity`. The
4329 effect is that the object will behave as though it were newly constructed,
4330 except retaining any attribute / collection values that were loaded at the
4331 time of the call. The :attr:`.InstanceState.deleted` flag is also reset
4332 if this object had been deleted as a result of using
4333 :meth:`.Session.delete`.
4335 .. warning::
4337 :func:`.make_transient` does **not** "unexpire" or otherwise eagerly
4338 load ORM-mapped attributes that are not currently loaded at the time
4339 the function is called. This includes attributes which:
4341 * were expired via :meth:`.Session.expire`
4343 * were expired as the natural effect of committing a session
4344 transaction, e.g. :meth:`.Session.commit`
4346 * are normally :term:`lazy loaded` but are not currently loaded
4348 * are "deferred" via :ref:`deferred` and are not yet loaded
4350 * were not present in the query which loaded this object, such as that
4351 which is common in joined table inheritance and other scenarios.
4353 After :func:`.make_transient` is called, unloaded attributes such
4354 as those above will normally resolve to the value ``None`` when
4355 accessed, or an empty collection for a collection-oriented attribute.
4356 As the object is transient and un-associated with any database
4357 identity, it will no longer retrieve these values.
4359 .. seealso::
4361 :func:`.make_transient_to_detached`
4363 """
4364 state = attributes.instance_state(instance)
4365 s = _state_session(state)
4366 if s:
4367 s._expunge_states([state])
4369 # remove expired state
4370 state.expired_attributes.clear()
4372 # remove deferred callables
4373 if state.callables:
4374 del state.callables
4376 if state.key:
4377 del state.key
4378 if state._deleted:
4379 del state._deleted
4382def make_transient_to_detached(instance):
4383 """Make the given transient instance :term:`detached`.
4385 .. note::
4387 :func:`.make_transient_to_detached` is a special-case function for
4388 advanced use cases only.
4390 All attribute history on the given instance
4391 will be reset as though the instance were freshly loaded
4392 from a query. Missing attributes will be marked as expired.
4393 The primary key attributes of the object, which are required, will be made
4394 into the "key" of the instance.
4396 The object can then be added to a session, or merged
4397 possibly with the load=False flag, at which point it will look
4398 as if it were loaded that way, without emitting SQL.
4400 This is a special use case function that differs from a normal
4401 call to :meth:`.Session.merge` in that a given persistent state
4402 can be manufactured without any SQL calls.
4404 .. seealso::
4406 :func:`.make_transient`
4408 :meth:`.Session.enable_relationship_loading`
4410 """
4411 state = attributes.instance_state(instance)
4412 if state.session_id or state.key:
4413 raise sa_exc.InvalidRequestError("Given object must be transient")
4414 state.key = state.mapper._identity_key_from_state(state)
4415 if state._deleted:
4416 del state._deleted
4417 state._commit_all(state.dict)
4418 state._expire_attributes(state.dict, state.unloaded_expirable)
4421def object_session(instance):
4422 """Return the :class:`.Session` to which the given instance belongs.
4424 This is essentially the same as the :attr:`.InstanceState.session`
4425 accessor. See that attribute for details.
4427 """
4429 try:
4430 state = attributes.instance_state(instance)
4431 except exc.NO_STATE as err:
4432 util.raise_(
4433 exc.UnmappedInstanceError(instance),
4434 replace_context=err,
4435 )
4436 else:
4437 return _state_session(state)
4440_new_sessionid = util.counter()