1# orm/session.py
2# Copyright (C) 2005-2024 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."""
8
9
10import itertools
11import sys
12import weakref
13
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
42
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]
53
54_sessions = weakref.WeakValueDictionary()
55"""Weak-referencing dictionary of :class:`.Session` objects.
56"""
57
58statelib._sessions = _sessions
59
60
61def _state_session(state):
62 """Given an :class:`.InstanceState`, return the :class:`.Session`
63 associated, if any.
64 """
65 return state.session
66
67
68class _SessionClassMethods(object):
69 """Class-level methods for :class:`.Session`, :class:`.sessionmaker`."""
70
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."""
80
81 close_all_sessions()
82
83 @classmethod
84 @util.preload_module("sqlalchemy.orm.util")
85 def identity_key(cls, *args, **kwargs):
86 """Return an identity key.
87
88 This is an alias of :func:`.util.identity_key`.
89
90 """
91 return util.preloaded.orm_util.identity_key(*args, **kwargs)
92
93 @classmethod
94 def object_session(cls, instance):
95 """Return the :class:`.Session` to which an object belongs.
96
97 This is an alias of :func:`.object_session`.
98
99 """
100
101 return object_session(instance)
102
103
104ACTIVE = util.symbol("ACTIVE")
105PREPARED = util.symbol("PREPARED")
106COMMITTED = util.symbol("COMMITTED")
107DEACTIVE = util.symbol("DEACTIVE")
108CLOSED = util.symbol("CLOSED")
109
110
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.
114
115 .. versionadded:: 1.4
116
117 .. seealso::
118
119 :ref:`session_execute_events` - top level documentation on how
120 to use :meth:`_orm.SessionEvents.do_orm_execute`
121
122 """
123
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 )
136
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)
157
158 def _remaining_events(self):
159 return self._events_todo[self._starting_event_idx + 1 :]
160
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.
171
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.
179
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.
187
188 :param statement: optional statement to be invoked, in place of the
189 statement currently represented by :attr:`.ORMExecuteState.statement`.
190
191 :param params: optional dictionary of parameters which will be merged
192 into the existing :attr:`.ORMExecuteState.parameters` of this
193 :class:`.ORMExecuteState`.
194
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`.
199
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`.
204
205 :return: a :class:`_engine.Result` object with ORM-level results.
206
207 .. seealso::
208
209 :ref:`do_orm_execute_re_executing` - background and examples on the
210 appropriate usage of :meth:`_orm.ORMExecuteState.invoke_statement`.
211
212
213 """
214
215 if statement is None:
216 statement = self.statement
217
218 _bind_arguments = dict(self.bind_arguments)
219 if bind_arguments:
220 _bind_arguments.update(bind_arguments)
221 _bind_arguments["_sa_skip_events"] = True
222
223 if params:
224 _params = dict(self.parameters)
225 _params.update(params)
226 else:
227 _params = self.parameters
228
229 _execution_options = self.local_execution_options
230 if execution_options:
231 _execution_options = _execution_options.union(execution_options)
232
233 return self.session.execute(
234 statement,
235 _params,
236 _execution_options,
237 _bind_arguments,
238 _parent_execute_state=self,
239 )
240
241 @property
242 def bind_mapper(self):
243 """Return the :class:`_orm.Mapper` that is the primary "bind" mapper.
244
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.
254
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.
258
259 .. versionadded:: 1.4.0b2
260
261 .. seealso::
262
263 :attr:`_orm.ORMExecuteState.all_mappers`
264
265
266 """
267 return self.bind_arguments.get("mapper", None)
268
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.
273
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.
278
279 .. versionadded:: 1.4.0b2
280
281 .. seealso::
282
283 :attr:`_orm.ORMExecuteState.bind_mapper`
284
285
286
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 []
305
306 @property
307 def is_orm_statement(self):
308 """return True if the operation is an ORM statement.
309
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.
315
316 """
317 return self._compile_state_cls is not None
318
319 @property
320 def is_select(self):
321 """return True if this is a SELECT operation."""
322 return self.statement.is_select
323
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
328
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
333
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
338
339 @property
340 def _is_crud(self):
341 return isinstance(self.statement, (dml.Update, dml.Delete))
342
343 def update_execution_options(self, **opts):
344 # TODO: no coverage
345 self.local_execution_options = self.local_execution_options.union(opts)
346
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
358
359 @property
360 def lazy_loaded_from(self):
361 """An :class:`.InstanceState` that is using this statement execution
362 for a lazy load operation.
363
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.
370
371 """
372 return self.load_options._lazy_loaded_from
373
374 @property
375 def loader_strategy_path(self):
376 """Return the :class:`.PathRegistry` for the current load path.
377
378 This object represents the "path" in a query along relationships
379 when a particular object or collection is being loaded.
380
381 """
382 opts = self._orm_compile_options()
383 if opts is not None:
384 return opts._current_path
385 else:
386 return None
387
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.
392
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.
398
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.
404
405 .. versionadded:: 1.4.0b2
406
407 .. seealso::
408
409 :attr:`_orm.ORMExecuteState.is_relationship_load`
410
411 """
412 opts = self._orm_compile_options()
413 return opts is not None and opts._for_refresh_state
414
415 @property
416 def is_relationship_load(self):
417 """Return True if this load is loading objects on behalf of a
418 relationship.
419
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.
424
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.
429
430 .. seealso::
431
432 :attr:`_orm.ORMExecuteState.is_column_load`
433
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
440
441 @property
442 def load_options(self):
443 """Return the load_options that will be used for this execution."""
444
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 )
453
454 @property
455 def update_delete_options(self):
456 """Return the update_delete_options that will be used for this
457 execution."""
458
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 )
468
469 @property
470 def user_defined_options(self):
471 """The sequence of :class:`.UserDefinedOptions` that have been
472 associated with the statement being invoked.
473
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 ]
480
481
482class SessionTransaction(TransactionalContext):
483 """A :class:`.Session`-level transaction.
484
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.
490
491 Documentation on interacting with :class:`_orm.SessionTransaction` is
492 at: :ref:`unitofwork_transaction`.
493
494
495 .. versionchanged:: 1.4 The scoping and API methods to work with the
496 :class:`_orm.SessionTransaction` object directly have been simplified.
497
498 .. seealso::
499
500 :ref:`unitofwork_transaction`
501
502 :meth:`.Session.begin`
503
504 :meth:`.Session.begin_nested`
505
506 :meth:`.Session.rollback`
507
508 :meth:`.Session.commit`
509
510 :meth:`.Session.in_transaction`
511
512 :meth:`.Session.in_nested_transaction`
513
514 :meth:`.Session.get_transaction`
515
516 :meth:`.Session.get_nested_transaction`
517
518
519 """
520
521 _rollback_exception = None
522
523 def __init__(
524 self,
525 session,
526 parent=None,
527 nested=False,
528 autobegin=False,
529 ):
530 TransactionalContext._trans_ctx_check(session)
531
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 )
544
545 self._take_snapshot(autobegin=autobegin)
546
547 # make sure transaction is assigned before we call the
548 # dispatch
549 self.session._transaction = self
550
551 self.session.dispatch.after_transaction_create(self.session, self)
552
553 @property
554 def parent(self):
555 """The parent :class:`.SessionTransaction` of this
556 :class:`.SessionTransaction`.
557
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.
565
566 .. versionadded:: 1.0.16 - use ._parent for previous versions
567
568 """
569 return self._parent
570
571 nested = False
572 """Indicates if this is a nested, or SAVEPOINT, transaction.
573
574 When :attr:`.SessionTransaction.nested` is True, it is expected
575 that :attr:`.SessionTransaction.parent` will be True as well.
576
577 """
578
579 @property
580 def is_active(self):
581 return self.session is not None and self._state is ACTIVE
582
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)
621
622 @property
623 def _is_transaction_boundary(self):
624 return self.nested or not self._parent
625
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)
630
631 def _begin(self, nested=False):
632 self._assert_active()
633 return SessionTransaction(self.session, self, nested=nested)
634
635 def _iterate_self_and_parents(self, upto=None):
636
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
650
651 return result
652
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
660
661 if not autobegin and not self.session._flushing:
662 self.session.flush()
663
664 self._new = weakref.WeakKeyDictionary()
665 self._deleted = weakref.WeakKeyDictionary()
666 self._dirty = weakref.WeakKeyDictionary()
667 self._key_switches = weakref.WeakKeyDictionary()
668
669 def _restore_snapshot(self, dirty_only=False):
670 """Restore the restoration state taken before a transaction began.
671
672 Corresponds to a rollback.
673
674 """
675 assert self._is_transaction_boundary
676
677 to_expunge = set(self._new).union(self.session._new)
678 self.session._expunge_states(to_expunge, to_transient=True)
679
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)
684
685 # restore the old key
686 s.key = oldkey
687
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)
691
692 for s in set(self._deleted).union(self.session._deleted):
693 self.session._update_impl(s, revert_deletion=True)
694
695 assert not self.session._deleted
696
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)
700
701 def _remove_snapshot(self):
702 """Remove the restoration state taken before a transaction began.
703
704 Corresponds to a commit.
705
706 """
707 assert self._is_transaction_boundary
708
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)
712
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)
722
723 def _connection_for_bind(self, bind, execution_options):
724 self._assert_active()
725
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]
733
734 local_connect = False
735 should_commit = True
736
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
752
753 try:
754 if execution_options:
755 conn = conn.execution_options(**execution_options)
756
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)
779
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
788
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()
796
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)
801
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()
806
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 )
818
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()
826
827 self._state = PREPARED
828
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()
833
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()
840
841 self._state = COMMITTED
842 self.session.dispatch.after_commit(self.session)
843
844 self._remove_snapshot()
845
846 self.close()
847
848 if _to_root and self._parent:
849 return self._parent.commit(_to_root=True)
850
851 return self._parent
852
853 def rollback(self, _capture_exception=False, _to_root=False):
854 self._assert_active(prepared_ok=True, rollback_ok=True)
855
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()
860
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()
869
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
883
884 sess = self.session
885
886 if not rollback_err and not sess._is_clean():
887
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)
896
897 self.close()
898
899 if self._parent and _capture_exception:
900 self._parent._rollback_exception = sys.exc_info()[1]
901
902 if rollback_err:
903 util.raise_(rollback_err[1], with_traceback=rollback_err[2])
904
905 sess.dispatch.after_soft_rollback(sess, self)
906
907 if _to_root and self._parent:
908 return self._parent.rollback(_to_root=True)
909 return self._parent
910
911 def close(self, invalidate=False):
912 if self.nested:
913 self.session._nested_transaction = (
914 self._previous_nested_transaction
915 )
916
917 self.session._transaction = self._parent
918
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()
929
930 self._state = CLOSED
931 self.session.dispatch.after_transaction_end(self.session, self)
932
933 self.session = None
934 self._connections = None
935
936 def _get_subject(self):
937 return self.session
938
939 def _transaction_is_active(self):
940 return self._state is ACTIVE
941
942 def _transaction_is_closed(self):
943 return self._state is CLOSED
944
945 def _rollback_can_be_called(self):
946 return self._state not in (COMMITTED, CLOSED)
947
948
949class Session(_SessionClassMethods):
950 """Manages persistence operations for ORM-mapped objects.
951
952 The Session's usage paradigm is described at :doc:`/orm/session`.
953
954
955 """
956
957 _is_asyncio = False
958
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.
984
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.
988
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.
999
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.
1008
1009 .. seealso::
1010
1011 :ref:`session_flushing` - additional background on autoflush
1012
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.
1018
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::
1036
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 })
1043
1044 .. seealso::
1045
1046 :ref:`session_partitioning`
1047
1048 :meth:`.Session.bind_mapper`
1049
1050 :meth:`.Session.bind_table`
1051
1052 :meth:`.Session.get_bind`
1053
1054
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``.
1060
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.
1066
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.
1071
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.
1076
1077 .. seealso::
1078
1079 :ref:`session_committing`
1080
1081 :param future: if True, use 2.0 style transactional and engine
1082 behavior. Future mode includes the following behaviors:
1083
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
1089
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.
1093
1094 * The behavior of the :paramref:`_orm.relationship.cascade_backrefs`
1095 flag on a :func:`_orm.relationship` will always assume
1096 "False" behavior.
1097
1098 .. versionadded:: 1.4
1099
1100 .. seealso::
1101
1102 :ref:`migration_20_toplevel`
1103
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`.
1110
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`.
1114
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.
1123
1124 """
1125 self.identity_map = identity.WeakInstanceDict()
1126
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
1140
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
1149
1150 self.twophase = twophase
1151 self._query_cls = query_cls if query_cls else query.Query
1152 if info:
1153 self.info.update(info)
1154
1155 if binds is not None:
1156 for key, bind in binds.items():
1157 self._add_bind(key, bind)
1158
1159 _sessions[self.hash_key] = self
1160
1161 # used by sqlalchemy.engine.util.TransactionalContext
1162 _trans_context_manager = None
1163
1164 connection_callable = None
1165
1166 def __enter__(self):
1167 return self
1168
1169 def __exit__(self, type_, value, traceback):
1170 self.close()
1171
1172 @util.contextmanager
1173 def _maker_context_manager(self):
1174 with self:
1175 with self.begin():
1176 yield self
1177
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`.
1189
1190 May be None if no transaction has begun yet.
1191
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.
1195
1196
1197 """
1198 return self._legacy_transaction()
1199
1200 def _legacy_transaction(self):
1201 if not self.future:
1202 self._autobegin()
1203 return self._transaction
1204
1205 def in_transaction(self):
1206 """Return True if this :class:`_orm.Session` has begun a transaction.
1207
1208 .. versionadded:: 1.4
1209
1210 .. seealso::
1211
1212 :attr:`_orm.Session.is_active`
1213
1214
1215 """
1216 return self._transaction is not None
1217
1218 def in_nested_transaction(self):
1219 """Return True if this :class:`_orm.Session` has begun a nested
1220 transaction, e.g. SAVEPOINT.
1221
1222 .. versionadded:: 1.4
1223
1224 """
1225 return self._nested_transaction is not None
1226
1227 def get_transaction(self):
1228 """Return the current root transaction in progress, if any.
1229
1230 .. versionadded:: 1.4
1231
1232 """
1233 trans = self._transaction
1234 while trans is not None and trans._parent is not None:
1235 trans = trans._parent
1236 return trans
1237
1238 def get_nested_transaction(self):
1239 """Return the current nested transaction in progress, if any.
1240
1241 .. versionadded:: 1.4
1242
1243 """
1244
1245 return self._nested_transaction
1246
1247 @util.memoized_property
1248 def info(self):
1249 """A user-modifiable dictionary.
1250
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.
1256
1257 """
1258 return {}
1259
1260 def _autobegin(self):
1261 if not self.autocommit and self._transaction is None:
1262
1263 trans = SessionTransaction(self, autobegin=True)
1264 assert self._transaction is trans
1265 return True
1266
1267 return False
1268
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.
1282
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.
1288
1289 When used to begin the outermost transaction, an error is raised
1290 if this :class:`.Session` is already inside of a transaction.
1291
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`.
1296
1297 :param subtransactions: if True, indicates that this
1298 :meth:`~.Session.begin` can create a "subtransaction".
1299
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.
1305
1306 .. seealso::
1307
1308 :ref:`session_autobegin`
1309
1310 :ref:`unitofwork_transaction`
1311
1312 :meth:`.Session.begin_nested`
1313
1314
1315 """
1316
1317 if subtransactions and self.future:
1318 raise NotImplementedError(
1319 "subtransactions are not implemented in future "
1320 "Session objects."
1321 )
1322
1323 if self._autobegin():
1324 if not subtransactions and not nested and not _subtrans:
1325 return self._transaction
1326
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
1340
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
1349
1350 return self._transaction # needed for __enter__/__exit__ hook
1351
1352 def begin_nested(self):
1353 """Begin a "nested" transaction on this Session, e.g. SAVEPOINT.
1354
1355 The target database(s) and associated drivers must support SQL
1356 SAVEPOINT for this method to function correctly.
1357
1358 For documentation on SAVEPOINT
1359 transactions, please see :ref:`session_begin_nested`.
1360
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.
1365
1366 .. seealso::
1367
1368 :ref:`session_begin_nested`
1369
1370 :ref:`pysqlite_serializable` - special workarounds required
1371 with the SQLite driver in order for SAVEPOINT to work
1372 correctly.
1373
1374 """
1375 return self.begin(nested=True)
1376
1377 def rollback(self):
1378 """Rollback the current transaction in progress.
1379
1380 If no transaction is in progress, this method is a pass-through.
1381
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.
1385
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.
1391
1392 .. seealso::
1393
1394 :ref:`session_rollback`
1395
1396 :ref:`unitofwork_transaction`
1397
1398 """
1399 if self._transaction is None:
1400 pass
1401 else:
1402 self._transaction.rollback(_to_root=self.future)
1403
1404 def commit(self):
1405 """Flush pending changes and commit the current transaction.
1406
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.
1415
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.
1423
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.
1428
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.
1433
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.
1440
1441 .. seealso::
1442
1443 :ref:`session_committing`
1444
1445 :ref:`unitofwork_transaction`
1446
1447 :ref:`asyncio_orm_avoid_lazyloads`
1448
1449 """
1450 if self._transaction is None:
1451 if not self._autobegin():
1452 raise sa_exc.InvalidRequestError("No transaction is begun.")
1453
1454 self._transaction.commit(_to_root=self.future)
1455
1456 def prepare(self):
1457 """Prepare the current transaction in progress for two phase commit.
1458
1459 If no transaction is in progress, this method raises an
1460 :exc:`~sqlalchemy.exc.InvalidRequestError`.
1461
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.
1465
1466 """
1467 if self._transaction is None:
1468 if not self._autobegin():
1469 raise sa_exc.InvalidRequestError("No transaction is begun.")
1470
1471 self._transaction.prepare()
1472
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.
1482
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).
1490
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`.
1495
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.
1499
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`.
1503
1504 :param bind:
1505 deprecated; use bind_arguments
1506
1507 :param mapper:
1508 deprecated; use bind_arguments
1509
1510 :param clause:
1511 deprecated; use bind_arguments
1512
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.
1519
1520 .. deprecated:: 1.4 this parameter is deprecated and will be removed
1521 in SQLAlchemy 2.0
1522
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.
1528
1529 .. seealso::
1530
1531 :ref:`session_transaction_isolation`
1532
1533 :param \**kw:
1534 deprecated; use bind_arguments
1535
1536 """
1537
1538 if not bind_arguments:
1539 bind_arguments = kw
1540
1541 bind = bind_arguments.pop("bind", None)
1542 if bind is None:
1543 bind = self.get_bind(**bind_arguments)
1544
1545 return self._connection_for_bind(
1546 bind,
1547 close_with_result=close_with_result,
1548 execution_options=execution_options,
1549 )
1550
1551 def _connection_for_bind(self, engine, execution_options=None, **kw):
1552 TransactionalContext._trans_ctx_check(self)
1553
1554 if self._transaction is not None or self._autobegin():
1555 return self._transaction._connection_for_bind(
1556 engine, execution_options
1557 )
1558
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
1565
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.
1577
1578 Returns a :class:`_engine.Result` object representing
1579 results of the statement execution.
1580
1581 E.g.::
1582
1583 from sqlalchemy import select
1584 result = session.execute(
1585 select(User).where(User.id == 5)
1586 )
1587
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`.
1591
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.
1595
1596 :param statement:
1597 An executable statement (i.e. an :class:`.Executable` expression
1598 such as :func:`_expression.select`).
1599
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.
1606
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.
1612
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.
1617
1618 :param mapper:
1619 deprecated; use the bind_arguments dictionary
1620
1621 :param bind:
1622 deprecated; use the bind_arguments dictionary
1623
1624 :param \**kw:
1625 deprecated; use the bind_arguments dictionary
1626
1627 :return: a :class:`_engine.Result` object.
1628
1629
1630 """
1631 statement = coercions.expect(roles.StatementRole, statement)
1632
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)
1647
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
1658
1659 execution_options = util.coerce_to_immutabledict(execution_options)
1660
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 )
1678
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]
1685
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
1701
1702 statement = orm_exec_state.statement
1703 execution_options = orm_exec_state.local_execution_options
1704
1705 bind = self.get_bind(**bind_arguments)
1706
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)
1718
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 )
1728
1729 return result
1730
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.
1740
1741 Usage and parameters are the same as that of
1742 :meth:`_orm.Session.execute`; the return result is a scalar Python
1743 value.
1744
1745 """
1746
1747 return self.execute(
1748 statement,
1749 params=params,
1750 execution_options=execution_options,
1751 bind_arguments=bind_arguments,
1752 **kw
1753 ).scalar()
1754
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.
1764
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.
1769
1770 :return: a :class:`_result.ScalarResult` object
1771
1772 .. versionadded:: 1.4.24 Added :meth:`_orm.Session.scalars`
1773
1774 .. versionadded:: 1.4.26 Added :meth:`_orm.scoped_session.scalars`
1775
1776 """
1777
1778 return self.execute(
1779 statement,
1780 params=params,
1781 execution_options=execution_options,
1782 bind_arguments=bind_arguments,
1783 **kw
1784 ).scalars()
1785
1786 def close(self):
1787 """Close out the transactional resources and ORM objects used by this
1788 :class:`_orm.Session`.
1789
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.
1796
1797 .. tip::
1798
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.
1804
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.
1809
1810 .. seealso::
1811
1812 :ref:`session_closing` - detail on the semantics of
1813 :meth:`_orm.Session.close`
1814
1815 """
1816 self._close_impl(invalidate=False)
1817
1818 def invalidate(self):
1819 """Close this Session, using connection invalidation.
1820
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).
1827
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.
1830
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::
1834
1835 import gevent
1836
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
1847
1848 The method additionally does everything that :meth:`_orm.Session.close`
1849 does, including that all ORM objects are expunged.
1850
1851 """
1852 self._close_impl(invalidate=True)
1853
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)
1859
1860 def expunge_all(self):
1861 """Remove all object instances from this ``Session``.
1862
1863 This is equivalent to calling ``expunge(obj)`` on all objects in this
1864 ``Session``.
1865
1866 """
1867
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 = {}
1873
1874 statelib.InstanceState._detach_states(all_states, self)
1875
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 )
1900
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`.
1905
1906 The given entity is added to a lookup used by the
1907 :meth:`.Session.get_bind` method.
1908
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.
1913
1914 :param bind: an :class:`_engine.Engine` or :class:`_engine.Connection`
1915 object.
1916
1917 .. seealso::
1918
1919 :ref:`session_partitioning`
1920
1921 :paramref:`.Session.binds`
1922
1923 :meth:`.Session.bind_table`
1924
1925
1926 """
1927 self._add_bind(mapper, bind)
1928
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`.
1933
1934 The given :class:`_schema.Table` is added to a lookup used by the
1935 :meth:`.Session.get_bind` method.
1936
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.
1941
1942 :param bind: an :class:`_engine.Engine` or :class:`_engine.Connection`
1943 object.
1944
1945 .. seealso::
1946
1947 :ref:`session_partitioning`
1948
1949 :paramref:`.Session.binds`
1950
1951 :meth:`.Session.bind_mapper`
1952
1953
1954 """
1955 self._add_bind(table, bind)
1956
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.
1966
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`.
1970
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.
1974
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.
1980
1981 The order of resolution is:
1982
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.
2001
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`.
2006
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.
2017
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`.
2027
2028 .. seealso::
2029
2030 :ref:`session_partitioning`
2031
2032 :paramref:`.Session.binds`
2033
2034 :meth:`.Session.bind_mapper`
2035
2036 :meth:`.Session.bind_table`
2037
2038 """
2039
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
2048
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 )
2061
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
2074
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
2085
2086 if clause is not None:
2087 plugin_subject = clause._propagate_attrs.get(
2088 "plugin_subject", None
2089 )
2090
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]
2095
2096 for obj in visitors.iterate(clause):
2097 if obj in self.__binds:
2098 return self.__binds[obj]
2099
2100 # none of the __binds matched, but we have a fallback bind.
2101 # return that
2102 if self.bind:
2103 return self.bind
2104
2105 # now we are in legacy territory. looking for "bind" on tables
2106 # that are via bound metadata. this goes away in 2.0.
2107
2108 future_msg = ""
2109 future_code = ""
2110
2111 if mapper and clause is None:
2112 clause = mapper.persist_selectable
2113
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
2130
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
2147
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")
2153
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 )
2159
2160 def query(self, *entities, **kwargs):
2161 """Return a new :class:`_query.Query` object corresponding to this
2162 :class:`_orm.Session`.
2163
2164 """
2165
2166 return self._query_cls(entities, self, **kwargs)
2167
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.
2177
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).
2182
2183 e.g.::
2184
2185 obj = session._identity_lookup(inspect(SomeClass), (1, ))
2186
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).
2203
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.
2208
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.
2213
2214
2215 """
2216
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)
2221
2222 @property
2223 @util.contextmanager
2224 def no_autoflush(self):
2225 """Return a context manager that disables autoflush.
2226
2227 e.g.::
2228
2229 with session.no_autoflush:
2230
2231 some_object = SomeClass()
2232 session.add(some_object)
2233 # won't autoflush
2234 some_object.related_thing = session.query(SomeRelated).first()
2235
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.
2241
2242 """
2243 autoflush = self.autoflush
2244 self.autoflush = False
2245 try:
2246 yield self
2247 finally:
2248 self.autoflush = autoflush
2249
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])
2265
2266 def refresh(self, instance, attribute_names=None, with_for_update=None):
2267 """Expire and refresh attributes on the given instance.
2268
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.
2273
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.
2279
2280 .. versionadded:: 1.4 - the :meth:`_orm.Session.refresh` method
2281 can also refresh eagerly loaded attributes.
2282
2283 .. tip::
2284
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.
2294
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.
2300
2301 :param attribute_names: optional. An iterable collection of
2302 string attribute names indicating a subset of attributes to
2303 be refreshed.
2304
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.
2311
2312 .. seealso::
2313
2314 :ref:`session_expire` - introductory material
2315
2316 :meth:`.Session.expire`
2317
2318 :meth:`.Session.expire_all`
2319
2320 :ref:`orm_queryguide_populate_existing` - allows any ORM query
2321 to refresh objects as they would be loaded normally.
2322
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 )
2331
2332 self._expire_state(state, attribute_names)
2333
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 )
2340
2341 with_for_update = query.ForUpdateArg._from_argument(with_for_update)
2342
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 )
2358
2359 def expire_all(self):
2360 """Expires all persistent instances within this Session.
2361
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.
2369
2370 To expire individual objects and individual attributes
2371 on those objects, use :meth:`Session.expire`.
2372
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.
2379
2380 .. seealso::
2381
2382 :ref:`session_expire` - introductory material
2383
2384 :meth:`.Session.expire`
2385
2386 :meth:`.Session.refresh`
2387
2388 :meth:`_orm.Query.populate_existing`
2389
2390 """
2391 for state in self.identity_map.all_states():
2392 state._expire(state.dict, self.identity_map._modified)
2393
2394 def expire(self, instance, attribute_names=None):
2395 """Expire the attributes on an instance.
2396
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.
2404
2405 To expire all objects in the :class:`.Session` simultaneously,
2406 use :meth:`Session.expire_all`.
2407
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.
2415
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.
2419
2420 .. seealso::
2421
2422 :ref:`session_expire` - introductory material
2423
2424 :meth:`.Session.expire`
2425
2426 :meth:`.Session.refresh`
2427
2428 :meth:`_orm.Query.populate_existing`
2429
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)
2439
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_)
2453
2454 def _conditional_expire(self, state, autoflush=None):
2455 """Expire a state if persistent, else expunge if pending"""
2456
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)
2462
2463 def expunge(self, instance):
2464 """Remove the `instance` from this ``Session``.
2465
2466 This will free all internal references to the instance. Cascading
2467 will be applied according to the *expunge* cascade rule.
2468
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 )
2481
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])
2486
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 )
2501
2502 def _register_persistent(self, states):
2503 """Register all persistent objects from a flush.
2504
2505 This is used both for pending objects moving to the persistent
2506 state as well as already persistent objects.
2507
2508 """
2509
2510 pending_to_persistent = self.dispatch.pending_to_persistent or None
2511 for state in states:
2512 mapper = _state_mapper(state)
2513
2514 # prevent against last minute dereferences of the object
2515 obj = state.obj()
2516 if obj is not None:
2517
2518 instance_key = mapper._identity_key_from_state(state)
2519
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 )
2535
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
2552
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
2569
2570 statelib.InstanceState._commit_all_states(
2571 ((state, state.dict) for state in states), self.identity_map
2572 )
2573
2574 self._register_altered(states)
2575
2576 if pending_to_persistent is not None:
2577 for state in states.intersection(self._new):
2578 pending_to_persistent(self, state)
2579
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)
2583
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
2591
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
2597
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
2602
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)
2611
2612 def add(self, instance, _warn=True):
2613 """Place an object into this :class:`_orm.Session`.
2614
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.
2619
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.
2623
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`.
2629
2630 .. seealso::
2631
2632 :meth:`_orm.Session.add_all`
2633
2634 :ref:`session_adding` - at :ref:`session_basics`
2635
2636 """
2637 if _warn and self._warn_on_events:
2638 self._flush_warning("Session.add()")
2639
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 )
2647
2648 self._save_or_update_state(state)
2649
2650 def add_all(self, instances):
2651 """Add the given collection of instances to this :class:`_orm.Session`.
2652
2653 See the documentation for :meth:`_orm.Session.add` for a general
2654 behavioral description.
2655
2656 .. seealso::
2657
2658 :meth:`_orm.Session.add`
2659
2660 :ref:`session_adding` - at :ref:`session_basics`
2661
2662 """
2663
2664 if self._warn_on_events:
2665 self._flush_warning("Session.add_all()")
2666
2667 for instance in instances:
2668 self.add(instance, _warn=False)
2669
2670 def _save_or_update_state(self, state):
2671 state._orphaned_outside_of_session = False
2672 self._save_or_update_impl(state)
2673
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_)
2679
2680 def delete(self, instance):
2681 """Mark an instance as deleted.
2682
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.
2688
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`.
2695
2696 .. seealso::
2697
2698 :ref:`session_deleting` - at :ref:`session_basics`
2699
2700 """
2701 if self._warn_on_events:
2702 self._flush_warning("Session.delete()")
2703
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 )
2711
2712 self._delete_impl(state, instance, head=True)
2713
2714 def _delete_impl(self, state, obj, head):
2715
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
2723
2724 to_attach = self._before_attach(state, obj)
2725
2726 if state in self._deleted:
2727 return
2728
2729 self.identity_map.add(state)
2730
2731 if to_attach:
2732 self._after_attach(state, obj)
2733
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 )
2741
2742 self._deleted[state] = obj
2743
2744 if head:
2745 for o, m, st_, dct_ in cascade_states:
2746 self._delete_impl(st_, o, False)
2747
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.
2760
2761 E.g.::
2762
2763 my_user = session.get(User, 5)
2764
2765 some_object = session.get(VersionedFoo, (5, 10))
2766
2767 some_object = session.get(
2768 VersionedFoo,
2769 {"id": 5, "version_id": 10}
2770 )
2771
2772 .. versionadded:: 1.4 Added :meth:`_orm.Session.get`, which is moved
2773 from the now deprecated :meth:`_orm.Query.get` method.
2774
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.
2783
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.
2790
2791 :param entity: a mapped class or :class:`.Mapper` indicating the
2792 type of entity to be loaded.
2793
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.
2797
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::
2801
2802 my_object = session.get(SomeClass, 5)
2803
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::
2813
2814 my_object = session.get(SomeClass, (5, 10))
2815
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::
2820
2821 my_object = session.get(SomeClass, {"id": 5, "version_id": 10})
2822
2823 :param options: optional sequence of loader options which will be
2824 applied to the query, if one is emitted.
2825
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.
2829
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.
2836
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.
2842
2843 .. versionadded:: 1.4.29
2844
2845 .. seealso::
2846
2847 :ref:`orm_queryguide_execution_options` - ORM-specific execution
2848 options
2849
2850 :return: The object instance, or ``None``.
2851
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 )
2863
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 ):
2875
2876 # convert composite types to individual args
2877 if hasattr(primary_key_identity, "__composite_values__"):
2878 primary_key_identity = primary_key_identity.__composite_values__()
2879
2880 mapper = inspect(entity)
2881
2882 if not mapper or not mapper.is_mapper:
2883 raise sa_exc.ArgumentError(
2884 "Expected mapped class or mapper, got: %r" % entity
2885 )
2886
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 )
2892
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 )
2899
2900 if is_dict:
2901
2902 pk_synonyms = mapper._pk_synonyms
2903
2904 if pk_synonyms:
2905 correct_keys = set(pk_synonyms).intersection(
2906 primary_key_identity
2907 )
2908
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]
2915
2916 try:
2917 primary_key_identity = list(
2918 primary_key_identity[prop.key]
2919 for prop in mapper._identity_key_props
2920 )
2921
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 )
2935
2936 if (
2937 not populate_existing
2938 and not mapper.always_refresh
2939 and with_for_update is None
2940 ):
2941
2942 instance = self._identity_lookup(
2943 mapper, primary_key_identity, identity_token=identity_token
2944 )
2945
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
2954
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 :)
2958
2959 load_options = context.QueryContext.default_load_options
2960
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 )
2970
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 )
2981
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`.
2985
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.
2995
2996 This operation cascades to associated instances if the association is
2997 mapped with ``cascade="merge"``.
2998
2999 See :ref:`unitofwork_merging` for a detailed discussion of merging.
3000
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.
3004
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.
3013
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.
3031
3032 .. versionadded:: 1.4.24
3033
3034
3035 .. seealso::
3036
3037 :func:`.make_transient_to_detached` - provides for an alternative
3038 means of "merging" a single object into the :class:`.Session`
3039
3040 """
3041
3042 if self._warn_on_events:
3043 self._flush_warning("Session.merge()")
3044
3045 _recursive = {}
3046 _resolve_conflict_map = {}
3047
3048 if load:
3049 # flush current contents if we expect to load data
3050 self._autoflush()
3051
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
3066
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]
3079
3080 new_instance = False
3081 key = state.key
3082
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 )
3090
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
3108
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
3117
3118 if merged is None:
3119 if key_is_persistent and key in _resolve_conflict_map:
3120 merged = _resolve_conflict_map[key]
3121
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
3134
3135 elif key_is_persistent:
3136 merged = self.get(
3137 mapper.class_,
3138 key[1],
3139 identity_token=key[2],
3140 options=options,
3141 )
3142
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)
3152
3153 _recursive[state] = merged
3154 _resolve_conflict_map[key] = merged
3155
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 )
3167
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 )
3174
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 )
3191
3192 merged_state.load_path = state.load_path
3193 merged_state.load_options = state.load_options
3194
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)
3201
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 )
3213
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 )
3220
3221 if new_instance:
3222 merged_state.manager.dispatch.load(merged_state, None)
3223 return merged
3224
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 )
3231
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 )
3238
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)
3246
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 )
3252
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 )
3265
3266 obj = state.obj()
3267
3268 # check for late gc
3269 if obj is None:
3270 return
3271
3272 to_attach = self._before_attach(state, obj)
3273
3274 self._deleted.pop(state, None)
3275 if revert_deletion:
3276 self.identity_map.replace(state)
3277 else:
3278 self.identity_map.add(state)
3279
3280 if to_attach:
3281 self._after_attach(state, obj)
3282 elif revert_deletion:
3283 self.dispatch.deleted_to_persistent(self, state)
3284
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)
3290
3291 def enable_relationship_loading(self, obj):
3292 """Associate an object with this :class:`.Session` for related
3293 object loading.
3294
3295 .. warning::
3296
3297 :meth:`.enable_relationship_loading` exists to serve special
3298 use cases and is not recommended for general use.
3299
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.
3306
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.
3311
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.
3316
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.
3322
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`.
3328
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.
3334
3335 .. seealso::
3336
3337 :paramref:`_orm.relationship.load_on_pending` - this flag
3338 allows per-relationship loading of many-to-ones on items that
3339 are pending.
3340
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.
3344
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 )
3353
3354 to_attach = self._before_attach(state, obj)
3355 state._load_pending = True
3356 if to_attach:
3357 self._after_attach(state, obj)
3358
3359 def _before_attach(self, state, obj):
3360 self._autobegin()
3361
3362 if state.session_id == self.hash_key:
3363 return False
3364
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 )
3371
3372 self.dispatch.before_attach(self, state)
3373
3374 return True
3375
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)
3381
3382 if state.key:
3383 self.dispatch.detached_to_persistent(self, state)
3384 else:
3385 self.dispatch.transient_to_pending(self, state)
3386
3387 def __contains__(self, instance):
3388 """Return True if the instance is associated with this session.
3389
3390 The instance may be pending or persistent within the Session for a
3391 result of True.
3392
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)
3402
3403 def __iter__(self):
3404 """Iterate over all pending or persistent instances within this
3405 Session.
3406
3407 """
3408 return iter(
3409 list(self._new.values()) + list(self.identity_map.values())
3410 )
3411
3412 def _contains_state(self, state):
3413 return state in self._new or self.identity_map.contains_state(state)
3414
3415 def flush(self, objects=None):
3416 """Flush all the object changes to the database.
3417
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.
3422
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.
3428
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.
3432
3433 :param objects: Optional; restricts the flush operation to operate
3434 only on elements that are in the given collection.
3435
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.
3439
3440 """
3441
3442 if self._flushing:
3443 raise sa_exc.InvalidRequestError("Session is already flushing")
3444
3445 if self._is_clean():
3446 return
3447 try:
3448 self._flushing = True
3449 self._flush(objects)
3450 finally:
3451 self._flushing = False
3452
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 )
3460
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 )
3467
3468 def _flush(self, objects=None):
3469
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
3474
3475 flush_context = UOWTransaction(self)
3476
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
3482
3483 deleted = set(self._deleted)
3484 new = set(self._new)
3485
3486 dirty = set(dirty).difference(deleted)
3487
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)
3495
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
3504
3505 # store objects whose fate has been decided
3506 processed = set()
3507
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)
3514
3515 for state in proc:
3516 is_orphan = _state_mapper(state)._is_orphan(state)
3517
3518 is_persistent_orphan = is_orphan and state.has_identity
3519
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)
3532
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!"
3541
3542 if not flush_context.has_work:
3543 return
3544
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
3552
3553 self.dispatch.after_flush(self, flush_context)
3554
3555 flush_context.finalize_flush_changes()
3556
3557 if not objects and self.identity_map._modified:
3558 len_ = len(self.identity_map._modified)
3559
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 )
3575
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)
3582
3583 self.dispatch.after_flush_postexec(self, flush_context)
3584
3585 transaction.commit()
3586
3587 except:
3588 with util.safe_reraise():
3589 transaction.rollback(_capture_exception=True)
3590
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.
3599
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.
3607
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`.
3617
3618 .. legacy::
3619
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.
3625
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.
3629
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.**
3634
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.
3638
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.
3645
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.
3653
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.
3664
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.
3669
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.
3674
3675 .. versionadded:: 1.3
3676
3677 .. seealso::
3678
3679 :ref:`bulk_operations`
3680
3681 :meth:`.Session.bulk_insert_mappings`
3682
3683 :meth:`.Session.bulk_update_mappings`
3684
3685 """
3686
3687 obj_states = (attributes.instance_state(obj) for obj in objects)
3688
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 )
3698
3699 def grouping_key(state):
3700 return (state.mapper, state.key is not None)
3701
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 )
3714
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.
3719
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.
3726
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.
3732
3733 .. versionadded:: 1.0.0
3734
3735 .. legacy::
3736
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.
3742
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.
3746
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.**
3751
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.
3756
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.
3762
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.
3776
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.
3790
3791 .. warning::
3792
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.
3798
3799 .. versionadded:: 1.1
3800
3801 .. seealso::
3802
3803 :ref:`bulk_operations`
3804
3805 :meth:`.Session.bulk_save_objects`
3806
3807 :meth:`.Session.bulk_update_mappings`
3808
3809 """
3810 self._bulk_save_mappings(
3811 mapper,
3812 mappings,
3813 False,
3814 False,
3815 return_defaults,
3816 False,
3817 render_nulls,
3818 )
3819
3820 def bulk_update_mappings(self, mapper, mappings):
3821 """Perform a bulk update of the given list of mapping dictionaries.
3822
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.
3829
3830 .. versionadded:: 1.0.0
3831
3832 .. legacy::
3833
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.
3839
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.
3843
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.**
3848
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.
3853
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.
3862
3863
3864 .. seealso::
3865
3866 :ref:`bulk_operations`
3867
3868 :meth:`.Session.bulk_insert_mappings`
3869
3870 :meth:`.Session.bulk_save_objects`
3871
3872 """
3873 self._bulk_save_mappings(
3874 mapper, mappings, True, False, False, False, False
3875 )
3876
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
3889
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()
3910
3911 except:
3912 with util.safe_reraise():
3913 transaction.rollback(_capture_exception=True)
3914 finally:
3915 self._flushing = False
3916
3917 def is_modified(self, instance, include_collections=True):
3918 r"""Return ``True`` if the given instance has locally
3919 modified attributes.
3920
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.
3924
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.
3929
3930 E.g.::
3931
3932 return session.is_modified(someobject)
3933
3934 A few caveats to this method apply:
3935
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.
3952
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`.
3959
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.
3966
3967 """
3968 state = object_state(instance)
3969
3970 if not state.modified:
3971 return False
3972
3973 dict_ = state.dict
3974
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
3981
3982 (added, unchanged, deleted) = attr.impl.get_history(
3983 state, dict_, passive=attributes.NO_CHANGE
3984 )
3985
3986 if added or deleted:
3987 return True
3988 else:
3989 return False
3990
3991 @property
3992 def is_active(self):
3993 """True if this :class:`.Session` not in "partial rollback" state.
3994
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.
3998
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.
4003
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.
4007
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.
4011
4012 .. seealso::
4013
4014 :ref:`faq_session_rollback`
4015
4016 :meth:`_orm.Session.in_transaction`
4017
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
4025
4026 identity_map = None
4027 """A mapping of object identities to objects themselves.
4028
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.
4032
4033 .. seealso::
4034
4035 :func:`.identity_key` - helper function to produce the keys used
4036 in this dictionary.
4037
4038 """
4039
4040 @property
4041 def _dirty_states(self):
4042 """The set of all persistent states considered dirty.
4043
4044 This method returns all states that were modified including
4045 those that were possibly deleted.
4046
4047 """
4048 return self.identity_map._dirty_states()
4049
4050 @property
4051 def dirty(self):
4052 """The set of all persistent instances considered dirty.
4053
4054 E.g.::
4055
4056 some_mapped_object in session.dirty
4057
4058 Instances are considered dirty when they were modified but not
4059 deleted.
4060
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).
4069
4070 To check if an instance has actionable net changes to its
4071 attributes, use the :meth:`.Session.is_modified` method.
4072
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 )
4081
4082 @property
4083 def deleted(self):
4084 "The set of all instances marked as 'deleted' within this ``Session``"
4085
4086 return util.IdentitySet(list(self._deleted.values()))
4087
4088 @property
4089 def new(self):
4090 "The set of all instances marked as 'new' within this ``Session``."
4091
4092 return util.IdentitySet(list(self._new.values()))
4093
4094
4095class sessionmaker(_SessionClassMethods):
4096 """A configurable :class:`.Session` factory.
4097
4098 The :class:`.sessionmaker` factory generates new
4099 :class:`.Session` objects when called, creating them given
4100 the configurational arguments established here.
4101
4102 e.g.::
4103
4104 from sqlalchemy import create_engine
4105 from sqlalchemy.orm import sessionmaker
4106
4107 # an Engine, which the Session will use for connection
4108 # resources
4109 engine = create_engine('postgresql://scott:tiger@localhost/')
4110
4111 Session = sessionmaker(engine)
4112
4113 with Session() as session:
4114 session.add(some_object)
4115 session.add(some_other_object)
4116 session.commit()
4117
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::
4123
4124 session = Session()
4125 try:
4126 session.add(some_object)
4127 session.add(some_other_object)
4128 session.commit()
4129 finally:
4130 session.close()
4131
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::
4139
4140 Session = sessionmaker(engine)
4141
4142 with Session.begin() as session:
4143 session.add(some_object)
4144 session.add(some_other_object)
4145 # commits transaction, closes session
4146
4147 .. versionadded:: 1.4
4148
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::
4155
4156 Session = sessionmaker(engine)
4157
4158 # bind an individual session to a connection
4159
4160 with engine.connect() as connection:
4161 with Session(bind=connection) as session:
4162 # work with session
4163
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::
4170
4171 # application starts, sessionmaker does not have
4172 # an engine bound yet
4173 Session = sessionmaker()
4174
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)
4179
4180 sess = Session()
4181 # work with session
4182
4183 .. seealso::
4184
4185 :ref:`session_getting` - introductory text on creating
4186 sessions using :class:`.sessionmaker`.
4187
4188 """
4189
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`.
4201
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.
4205
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.
4218
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.
4223
4224 :param \**kw: all other keyword arguments are passed to the
4225 constructor of newly created :class:`.Session` objects.
4226
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_,), {})
4238
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.
4242
4243
4244 e.g.::
4245
4246 Session = sessionmaker(some_engine)
4247
4248 with Session.begin() as session:
4249 session.add(some_object)
4250
4251 # commits transaction, closes session
4252
4253 .. versionadded:: 1.4
4254
4255
4256 """
4257
4258 session = self()
4259 return session._maker_context_manager()
4260
4261 def __call__(self, **local_kw):
4262 """Produce a new :class:`.Session` object using the configuration
4263 established in this :class:`.sessionmaker`.
4264
4265 In Python, the ``__call__`` method is invoked on an object when
4266 it is "called" in the same way as a function::
4267
4268 Session = sessionmaker()
4269 session = Session() # invokes sessionmaker.__call__()
4270
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)
4280
4281 def configure(self, **new_kw):
4282 """(Re)configure the arguments for this sessionmaker.
4283
4284 e.g.::
4285
4286 Session = sessionmaker()
4287
4288 Session.configure(bind=create_engine('sqlite://'))
4289 """
4290 self.kw.update(new_kw)
4291
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 )
4298
4299
4300def close_all_sessions():
4301 """Close all sessions in memory.
4302
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.
4306
4307 This function is not for general use but may be useful for test suites
4308 within the teardown scheme.
4309
4310 .. versionadded:: 1.3
4311
4312 """
4313
4314 for sess in _sessions.values():
4315 sess.close()
4316
4317
4318def make_transient(instance):
4319 """Alter the state of the given instance so that it is :term:`transient`.
4320
4321 .. note::
4322
4323 :func:`.make_transient` is a special-case function for
4324 advanced use cases only.
4325
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`.
4334
4335 .. warning::
4336
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:
4340
4341 * were expired via :meth:`.Session.expire`
4342
4343 * were expired as the natural effect of committing a session
4344 transaction, e.g. :meth:`.Session.commit`
4345
4346 * are normally :term:`lazy loaded` but are not currently loaded
4347
4348 * are "deferred" via :ref:`deferred` and are not yet loaded
4349
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.
4352
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.
4358
4359 .. seealso::
4360
4361 :func:`.make_transient_to_detached`
4362
4363 """
4364 state = attributes.instance_state(instance)
4365 s = _state_session(state)
4366 if s:
4367 s._expunge_states([state])
4368
4369 # remove expired state
4370 state.expired_attributes.clear()
4371
4372 # remove deferred callables
4373 if state.callables:
4374 del state.callables
4375
4376 if state.key:
4377 del state.key
4378 if state._deleted:
4379 del state._deleted
4380
4381
4382def make_transient_to_detached(instance):
4383 """Make the given transient instance :term:`detached`.
4384
4385 .. note::
4386
4387 :func:`.make_transient_to_detached` is a special-case function for
4388 advanced use cases only.
4389
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.
4395
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.
4399
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.
4403
4404 .. seealso::
4405
4406 :func:`.make_transient`
4407
4408 :meth:`.Session.enable_relationship_loading`
4409
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)
4419
4420
4421def object_session(instance):
4422 """Return the :class:`.Session` to which the given instance belongs.
4423
4424 This is essentially the same as the :attr:`.InstanceState.session`
4425 accessor. See that attribute for details.
4426
4427 """
4428
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)
4438
4439
4440_new_sessionid = util.counter()