aboutsummaryrefslogtreecommitdiffstats
path: root/lib/python2.7/site-packages/SQLAlchemy-0.7.0-py2.7-linux-x86_64.egg/sqlalchemy/orm/session.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/python2.7/site-packages/SQLAlchemy-0.7.0-py2.7-linux-x86_64.egg/sqlalchemy/orm/session.py')
-rwxr-xr-xlib/python2.7/site-packages/SQLAlchemy-0.7.0-py2.7-linux-x86_64.egg/sqlalchemy/orm/session.py1725
1 files changed, 0 insertions, 1725 deletions
diff --git a/lib/python2.7/site-packages/SQLAlchemy-0.7.0-py2.7-linux-x86_64.egg/sqlalchemy/orm/session.py b/lib/python2.7/site-packages/SQLAlchemy-0.7.0-py2.7-linux-x86_64.egg/sqlalchemy/orm/session.py
deleted file mode 100755
index 8f8770a3..00000000
--- a/lib/python2.7/site-packages/SQLAlchemy-0.7.0-py2.7-linux-x86_64.egg/sqlalchemy/orm/session.py
+++ /dev/null
@@ -1,1725 +0,0 @@
-# orm/session.py
-# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
-#
-# This module is part of SQLAlchemy and is released under
-# the MIT License: http://www.opensource.org/licenses/mit-license.php
-
-"""Provides the Session class and related utilities."""
-
-import weakref
-from itertools import chain
-from sqlalchemy import util, sql, engine, log, exc as sa_exc
-from sqlalchemy.sql import util as sql_util, expression
-from sqlalchemy.orm import (
- SessionExtension, attributes, exc, query, unitofwork, util as mapperutil, state
- )
-from sqlalchemy.orm.util import object_mapper as _object_mapper
-from sqlalchemy.orm.util import class_mapper as _class_mapper
-from sqlalchemy.orm.util import (
- _class_to_mapper, _state_mapper,
- )
-from sqlalchemy.orm.mapper import Mapper, _none_set
-from sqlalchemy.orm.unitofwork import UOWTransaction
-from sqlalchemy.orm import identity
-from sqlalchemy import event
-from sqlalchemy.orm.events import SessionEvents
-
-import sys
-
-__all__ = ['Session', 'SessionTransaction', 'SessionExtension']
-
-
-def sessionmaker(bind=None, class_=None, autoflush=True, autocommit=False,
- expire_on_commit=True, **kwargs):
- """Generate a custom-configured :class:`~sqlalchemy.orm.session.Session` class.
-
- The returned object is a subclass of ``Session``, which, when instantiated
- with no arguments, uses the keyword arguments configured here as its
- constructor arguments.
-
- It is intended that the `sessionmaker()` function be called within the
- global scope of an application, and the returned class be made available
- to the rest of the application as the single class used to instantiate
- sessions.
-
- e.g.::
-
- # global scope
- Session = sessionmaker(autoflush=False)
-
- # later, in a local scope, create and use a session:
- sess = Session()
-
- Any keyword arguments sent to the constructor itself will override the
- "configured" keywords::
-
- Session = sessionmaker()
-
- # bind an individual session to a connection
- sess = Session(bind=connection)
-
- The class also includes a special classmethod ``configure()``, which
- allows additional configurational options to take place after the custom
- ``Session`` class has been generated. This is useful particularly for
- defining the specific ``Engine`` (or engines) to which new instances of
- ``Session`` should be bound::
-
- Session = sessionmaker()
- Session.configure(bind=create_engine('sqlite:///foo.db'))
-
- sess = Session()
-
- For options, see the constructor options for :class:`.Session`.
-
- """
- kwargs['bind'] = bind
- kwargs['autoflush'] = autoflush
- kwargs['autocommit'] = autocommit
- kwargs['expire_on_commit'] = expire_on_commit
-
- if class_ is None:
- class_ = Session
-
- class Sess(object):
- def __init__(self, **local_kwargs):
- for k in kwargs:
- local_kwargs.setdefault(k, kwargs[k])
- super(Sess, self).__init__(**local_kwargs)
-
- @classmethod
- def configure(self, **new_kwargs):
- """(Re)configure the arguments for this sessionmaker.
-
- e.g.::
-
- Session = sessionmaker()
-
- Session.configure(bind=create_engine('sqlite://'))
- """
- kwargs.update(new_kwargs)
-
-
- return type("Session", (Sess, class_), {})
-
-
-class SessionTransaction(object):
- """A Session-level transaction.
-
- This corresponds to one or more :class:`~sqlalchemy.engine.Transaction`
- instances behind the scenes, with one ``Transaction`` per ``Engine`` in
- use.
-
- Direct usage of ``SessionTransaction`` is not necessary as of SQLAlchemy
- 0.4; use the ``begin()`` and ``commit()`` methods on ``Session`` itself.
-
- The ``SessionTransaction`` object is **not** thread-safe.
-
- .. index::
- single: thread safety; SessionTransaction
-
- """
-
- _rollback_exception = None
-
- def __init__(self, session, parent=None, nested=False):
- self.session = session
- self._connections = {}
- self._parent = parent
- self.nested = nested
- self._active = True
- self._prepared = False
- if not parent and nested:
- raise sa_exc.InvalidRequestError(
- "Can't start a SAVEPOINT transaction when no existing "
- "transaction is in progress")
-
- if self.session._enable_transaction_accounting:
- self._take_snapshot()
-
- @property
- def is_active(self):
- return self.session is not None and self._active
-
- def _assert_is_active(self):
- self._assert_is_open()
- if not self._active:
- if self._rollback_exception:
- raise sa_exc.InvalidRequestError(
- "This Session's transaction has been rolled back "
- "due to a previous exception during flush."
- " To begin a new transaction with this Session, "
- "first issue Session.rollback()."
- " Original exception was: %s"
- % self._rollback_exception
- )
- else:
- raise sa_exc.InvalidRequestError(
- "This Session's transaction has been rolled back "
- "by a nested rollback() call. To begin a new "
- "transaction, issue Session.rollback() first."
- )
-
- def _assert_is_open(self, error_msg="The transaction is closed"):
- if self.session is None:
- raise sa_exc.ResourceClosedError(error_msg)
-
- @property
- def _is_transaction_boundary(self):
- return self.nested or not self._parent
-
- def connection(self, bindkey, **kwargs):
- self._assert_is_active()
- engine = self.session.get_bind(bindkey, **kwargs)
- return self._connection_for_bind(engine)
-
- def _begin(self, nested=False):
- self._assert_is_active()
- return SessionTransaction(
- self.session, self, nested=nested)
-
- def _iterate_parents(self, upto=None):
- if self._parent is upto:
- return (self,)
- else:
- if self._parent is None:
- raise sa_exc.InvalidRequestError(
- "Transaction %s is not on the active transaction list" % (
- upto))
- return (self,) + self._parent._iterate_parents(upto)
-
- def _take_snapshot(self):
- if not self._is_transaction_boundary:
- self._new = self._parent._new
- self._deleted = self._parent._deleted
- return
-
- if not self.session._flushing:
- self.session.flush()
-
- self._new = weakref.WeakKeyDictionary()
- self._deleted = weakref.WeakKeyDictionary()
-
- def _restore_snapshot(self):
- assert self._is_transaction_boundary
-
- for s in set(self._new).union(self.session._new):
- self.session._expunge_state(s)
- if s.key:
- del s.key
-
- for s in set(self._deleted).union(self.session._deleted):
- if s.deleted:
- #assert s in self._deleted
- del s.deleted
- self.session._update_impl(s)
-
- assert not self.session._deleted
-
- for s in self.session.identity_map.all_states():
- s.expire(s.dict, self.session.identity_map._modified)
-
- def _remove_snapshot(self):
- assert self._is_transaction_boundary
-
- if not self.nested and self.session.expire_on_commit:
- for s in self.session.identity_map.all_states():
- s.expire(s.dict, self.session.identity_map._modified)
-
- def _connection_for_bind(self, bind):
- self._assert_is_active()
-
- if bind in self._connections:
- return self._connections[bind][0]
-
- if self._parent:
- conn = self._parent._connection_for_bind(bind)
- if not self.nested:
- return conn
- else:
- if isinstance(bind, engine.Connection):
- conn = bind
- if conn.engine in self._connections:
- raise sa_exc.InvalidRequestError(
- "Session already has a Connection associated for the "
- "given Connection's Engine")
- else:
- conn = bind.contextual_connect()
-
- if self.session.twophase and self._parent is None:
- transaction = conn.begin_twophase()
- elif self.nested:
- transaction = conn.begin_nested()
- else:
- transaction = conn.begin()
-
- self._connections[conn] = self._connections[conn.engine] = \
- (conn, transaction, conn is not bind)
- self.session.dispatch.after_begin(self.session, self, conn)
- return conn
-
- def prepare(self):
- if self._parent is not None or not self.session.twophase:
- raise sa_exc.InvalidRequestError(
- "Only root two phase transactions of can be prepared")
- self._prepare_impl()
-
- def _prepare_impl(self):
- self._assert_is_active()
- if self._parent is None or self.nested:
- self.session.dispatch.before_commit(self.session)
-
- stx = self.session.transaction
- if stx is not self:
- for subtransaction in stx._iterate_parents(upto=self):
- subtransaction.commit()
-
- if not self.session._flushing:
- self.session.flush()
-
- if self._parent is None and self.session.twophase:
- try:
- for t in set(self._connections.values()):
- t[1].prepare()
- except:
- self.rollback()
- raise
-
- self._deactivate()
- self._prepared = True
-
- def commit(self):
- self._assert_is_open()
- if not self._prepared:
- self._prepare_impl()
-
- if self._parent is None or self.nested:
- for t in set(self._connections.values()):
- t[1].commit()
-
- self.session.dispatch.after_commit(self.session)
-
- if self.session._enable_transaction_accounting:
- self._remove_snapshot()
-
- self.close()
- return self._parent
-
- def rollback(self, _capture_exception=False):
- self._assert_is_open()
-
- stx = self.session.transaction
- if stx is not self:
- for subtransaction in stx._iterate_parents(upto=self):
- subtransaction.close()
-
- if self.is_active or self._prepared:
- for transaction in self._iterate_parents():
- if transaction._parent is None or transaction.nested:
- transaction._rollback_impl()
- transaction._deactivate()
- break
- else:
- transaction._deactivate()
-
- self.close()
- if self._parent and _capture_exception:
- self._parent._rollback_exception = sys.exc_info()[1]
- return self._parent
-
- def _rollback_impl(self):
- for t in set(self._connections.values()):
- t[1].rollback()
-
- if self.session._enable_transaction_accounting:
- self._restore_snapshot()
-
- self.session.dispatch.after_rollback(self.session)
-
- def _deactivate(self):
- self._active = False
-
- def close(self):
- self.session.transaction = self._parent
- if self._parent is None:
- for connection, transaction, autoclose in \
- set(self._connections.values()):
- if autoclose:
- connection.close()
- else:
- transaction.close()
- if not self.session.autocommit:
- self.session.begin()
- self._deactivate()
- self.session = None
- self._connections = None
-
- def __enter__(self):
- return self
-
- def __exit__(self, type, value, traceback):
- self._assert_is_open("Cannot end transaction context. The transaction "
- "was closed from within the context")
- if self.session.transaction is None:
- return
- if type is None:
- try:
- self.commit()
- except:
- self.rollback()
- raise
- else:
- self.rollback()
-
-class Session(object):
- """Manages persistence operations for ORM-mapped objects.
-
- The Session's usage paradigm is described at :ref:`session_toplevel`.
-
-
- """
-
- public_methods = (
- '__contains__', '__iter__', 'add', 'add_all', 'begin', 'begin_nested',
- 'close', 'commit', 'connection', 'delete', 'execute', 'expire',
- 'expire_all', 'expunge', 'expunge_all', 'flush', 'get_bind',
- 'is_modified',
- 'merge', 'query', 'refresh', 'rollback',
- 'scalar')
-
-
- def __init__(self, bind=None, autoflush=True, expire_on_commit=True,
- _enable_transaction_accounting=True,
- autocommit=False, twophase=False,
- weak_identity_map=True, binds=None, extension=None,
- query_cls=query.Query):
- """Construct a new Session.
-
- See also the :func:`.sessionmaker` function which is used to
- generate a :class:`.Session`-producing callable with a given
- set of arguments.
-
- :param autocommit: Defaults to ``False``. When ``True``, the ``Session``
- does not keep a persistent transaction running, and will acquire
- connections from the engine on an as-needed basis, returning them
- immediately after their use. Flushes will begin and commit (or possibly
- rollback) their own transaction if no transaction is present. When using
- this mode, the `session.begin()` method may be used to begin a
- transaction explicitly.
-
- Leaving it on its default value of ``False`` means that the ``Session``
- will acquire a connection and begin a transaction the first time it is
- used, which it will maintain persistently until ``rollback()``,
- ``commit()``, or ``close()`` is called. When the transaction is released
- by any of these methods, the ``Session`` is ready for the next usage,
- which will again acquire and maintain a new connection/transaction.
-
- :param autoflush: When ``True``, all query operations will issue a
- ``flush()`` call to this ``Session`` before proceeding. This is a
- convenience feature so that ``flush()`` need not be called repeatedly
- in order for database queries to retrieve results. It's typical that
- ``autoflush`` is used in conjunction with ``autocommit=False``. In this
- scenario, explicit calls to ``flush()`` are rarely needed; you usually
- only need to call ``commit()`` (which flushes) to finalize changes.
-
- :param bind: An optional ``Engine`` or ``Connection`` to which this
- ``Session`` should be bound. When specified, all SQL operations
- performed by this session will execute via this connectable.
-
- :param binds: An optional dictionary which contains more granular "bind"
- information than the ``bind`` parameter provides. This dictionary can
- map individual ``Table`` instances as well as ``Mapper`` instances to
- individual ``Engine`` or ``Connection`` objects. Operations which
- proceed relative to a particular ``Mapper`` will consult this
- dictionary for the direct ``Mapper`` instance as well as the mapper's
- ``mapped_table`` attribute in order to locate an connectable to use.
- The full resolution is described in the ``get_bind()`` method of
- ``Session``. Usage looks like::
-
- Session = sessionmaker(binds={
- SomeMappedClass: create_engine('postgresql://engine1'),
- somemapper: create_engine('postgresql://engine2'),
- some_table: create_engine('postgresql://engine3'),
- })
-
- Also see the :meth:`.Session.bind_mapper` and :meth:`.Session.bind_table` methods.
-
- :param \class_: Specify an alternate class other than
- ``sqlalchemy.orm.session.Session`` which should be used by the returned
- class. This is the only argument that is local to the
- ``sessionmaker()`` function, and is not sent directly to the
- constructor for ``Session``.
-
- :param _enable_transaction_accounting: Defaults to ``True``. A
- legacy-only flag which when ``False`` disables *all* 0.5-style object
- accounting on transaction boundaries, including auto-expiry of
- instances on rollback and commit, maintenance of the "new" and
- "deleted" lists upon rollback, and autoflush of pending changes upon
- begin(), all of which are interdependent.
-
- :param expire_on_commit: Defaults to ``True``. When ``True``, all
- instances will be fully expired after each ``commit()``, so that all
- attribute/object access subsequent to a completed transaction will load
- from the most recent database state.
-
- :param extension: An optional
- :class:`~.SessionExtension` instance, or a list
- of such instances, which will receive pre- and post- commit and flush
- events, as well as a post-rollback event. **Deprecated.**
- Please see :class:`.SessionEvents`.
-
- :param query_cls: Class which should be used to create new Query objects,
- as returned by the ``query()`` method. Defaults to
- :class:`~sqlalchemy.orm.query.Query`.
-
- :param twophase: When ``True``, all transactions will be started as
- a "two phase" transaction, i.e. using the "two phase" semantics
- of the database in use along with an XID. During a ``commit()``,
- after ``flush()`` has been issued for all attached databases, the
- ``prepare()`` method on each database's ``TwoPhaseTransaction`` will
- be called. This allows each database to roll back the entire
- transaction, before each transaction is committed.
-
- :param weak_identity_map: Defaults to ``True`` - when set to
- ``False``, objects placed in the :class:`.Session` will be
- strongly referenced until explicitly removed or the
- :class:`.Session` is closed. **Deprecated** - this option
- is obsolete.
-
- """
-
- if weak_identity_map:
- self._identity_cls = identity.WeakInstanceDict
- else:
- util.warn_deprecated("weak_identity_map=False is deprecated. "
- "This feature is not needed.")
- self._identity_cls = identity.StrongInstanceDict
- self.identity_map = self._identity_cls()
-
- self._new = {} # InstanceState->object, strong refs object
- self._deleted = {} # same
- self.bind = bind
- self.__binds = {}
- self._flushing = False
- self.transaction = None
- self.hash_key = id(self)
- self.autoflush = autoflush
- self.autocommit = autocommit
- self.expire_on_commit = expire_on_commit
- self._enable_transaction_accounting = _enable_transaction_accounting
- self.twophase = twophase
- self._query_cls = query_cls
-
- if extension:
- for ext in util.to_list(extension):
- SessionExtension._adapt_listener(self, ext)
-
- if binds is not None:
- for mapperortable, bind in binds.iteritems():
- if isinstance(mapperortable, (type, Mapper)):
- self.bind_mapper(mapperortable, bind)
- else:
- self.bind_table(mapperortable, bind)
-
- if not self.autocommit:
- self.begin()
- _sessions[self.hash_key] = self
-
- dispatch = event.dispatcher(SessionEvents)
-
- connection_callable = None
-
- def begin(self, subtransactions=False, nested=False):
- """Begin a transaction on this Session.
-
- If this Session is already within a transaction, either a plain
- transaction or nested transaction, an error is raised, unless
- ``subtransactions=True`` or ``nested=True`` is specified.
-
- The ``subtransactions=True`` flag indicates that this :meth:`~.Session.begin`
- can create a subtransaction if a transaction is already in progress.
- For documentation on subtransactions, please see :ref:`session_subtransactions`.
-
- The ``nested`` flag begins a SAVEPOINT transaction and is equivalent
- to calling :meth:`~.Session.begin_nested`. For documentation on SAVEPOINT
- transactions, please see :ref:`session_begin_nested`.
-
- """
- if self.transaction is not None:
- if subtransactions or nested:
- self.transaction = self.transaction._begin(
- nested=nested)
- else:
- raise sa_exc.InvalidRequestError(
- "A transaction is already begun. Use subtransactions=True "
- "to allow subtransactions.")
- else:
- self.transaction = SessionTransaction(
- self, nested=nested)
- return self.transaction # needed for __enter__/__exit__ hook
-
- def begin_nested(self):
- """Begin a `nested` transaction on this Session.
-
- The target database(s) must support SQL SAVEPOINTs or a
- SQLAlchemy-supported vendor implementation of the idea.
-
- For documentation on SAVEPOINT
- transactions, please see :ref:`session_begin_nested`.
-
- """
- return self.begin(nested=True)
-
- def rollback(self):
- """Rollback the current transaction in progress.
-
- If no transaction is in progress, this method is a pass-through.
-
- This method rolls back the current transaction or nested transaction
- regardless of subtransactions being in effect. All subtransactions up
- to the first real transaction are closed. Subtransactions occur when
- begin() is called multiple times.
-
- """
- if self.transaction is None:
- pass
- else:
- self.transaction.rollback()
-
- def commit(self):
- """Flush pending changes and commit the current transaction.
-
- If no transaction is in progress, this method raises an
- InvalidRequestError.
-
- By default, the :class:`.Session` also expires all database
- loaded state on all ORM-managed attributes after transaction commit.
- This so that subsequent operations load the most recent
- data from the database. This behavior can be disabled using
- the ``expire_on_commit=False`` option to :func:`.sessionmaker` or
- the :class:`.Session` constructor.
-
- If a subtransaction is in effect (which occurs when begin() is called
- multiple times), the subtransaction will be closed, and the next call
- to ``commit()`` will operate on the enclosing transaction.
-
- For a session configured with autocommit=False, a new transaction will
- be begun immediately after the commit, but note that the newly begun
- transaction does *not* use any connection resources until the first
- SQL is actually emitted.
-
- """
- if self.transaction is None:
- if not self.autocommit:
- self.begin()
- else:
- raise sa_exc.InvalidRequestError("No transaction is begun.")
-
- self.transaction.commit()
-
- def prepare(self):
- """Prepare the current transaction in progress for two phase commit.
-
- If no transaction is in progress, this method raises an
- InvalidRequestError.
-
- Only root transactions of two phase sessions can be prepared. If the
- current transaction is not such, an InvalidRequestError is raised.
-
- """
- if self.transaction is None:
- if not self.autocommit:
- self.begin()
- else:
- raise sa_exc.InvalidRequestError("No transaction is begun.")
-
- self.transaction.prepare()
-
- def connection(self, mapper=None, clause=None,
- bind=None,
- close_with_result=False,
- **kw):
- """Return a :class:`.Connection` object corresponding to this
- :class:`.Session` object's transactional state.
-
- If this :class:`.Session` is configured with ``autocommit=False``,
- either the :class:`.Connection` corresponding to the current transaction
- is returned, or if no transaction is in progress, a new one is begun
- and the :class:`.Connection` returned.
-
- Alternatively, if this :class:`.Session` is configured with ``autocommit=True``,
- an ad-hoc :class:`.Connection` is returned using :meth:`.Engine.contextual_connect`
- on the underlying :class:`.Engine`.
-
- Ambiguity in multi-bind or unbound :class:`.Session` objects can be resolved through
- any of the optional keyword arguments. This ultimately makes usage of the
- :meth:`.get_bind` method for resolution.
-
- :param bind:
- Optional :class:`.Engine` to be used as the bind. If
- this engine is already involved in an ongoing transaction,
- that connection will be used. This argument takes precedence
- over ``mapper``, ``clause``.
-
- :param mapper:
- Optional :func:`.mapper` mapped class, used to identify
- the appropriate bind. This argument takes precedence over
- ``clause``.
-
- :param clause:
- A :class:`.ClauseElement` (i.e. :func:`~.sql.expression.select`,
- :func:`~.sql.expression.text`,
- etc.) which will be used to locate a bind, if a bind
- cannot otherwise be identified.
-
- :param close_with_result: Passed to :meth:`Engine.connect`, indicating
- the :class:`.Connection` should be considered "single use", automatically
- closing when the first result set is closed. This flag only has
- an effect if this :class:`.Session` is configured with ``autocommit=True``
- and does not already have a transaction in progress.
-
- :param \**kw:
- Additional keyword arguments are sent to :meth:`get_bind()`,
- allowing additional arguments to be passed to custom
- implementations of :meth:`get_bind`.
-
- """
- if bind is None:
- bind = self.get_bind(mapper, clause=clause, **kw)
-
- return self._connection_for_bind(bind,
- close_with_result=close_with_result)
-
- def _connection_for_bind(self, engine, **kwargs):
- if self.transaction is not None:
- return self.transaction._connection_for_bind(engine)
- else:
- return engine.contextual_connect(**kwargs)
-
- def execute(self, clause, params=None, mapper=None, bind=None, **kw):
- """Execute a clause within the current transaction.
-
- Returns a :class:`.ResultProxy` representing
- results of the statement execution, in the same manner as that of an
- :class:`.Engine` or
- :class:`.Connection`.
-
- :meth:`~.Session.execute` accepts any executable clause construct, such
- as :func:`~.sql.expression.select`,
- :func:`~.sql.expression.insert`,
- :func:`~.sql.expression.update`,
- :func:`~.sql.expression.delete`, and
- :func:`~.sql.expression.text`, and additionally accepts
- plain strings that represent SQL statements. If a plain string is
- passed, it is first converted to a
- :func:`~.sql.expression.text` construct, which here means
- that bind parameters should be specified using the format ``:param``.
-
- The statement is executed within the current transactional context of
- this :class:`.Session`, using the same behavior as that of
- the :meth:`.Session.connection` method to determine the active
- :class:`.Connection`. The ``close_with_result`` flag is
- set to ``True`` so that an ``autocommit=True`` :class:`.Session`
- with no active transaction will produce a result that auto-closes
- the underlying :class:`.Connection`.
-
- :param clause:
- A :class:`.ClauseElement` (i.e. :func:`~.sql.expression.select`,
- :func:`~.sql.expression.text`, etc.) or string SQL statement to be executed. The clause
- will also be used to locate a bind, if this :class:`.Session`
- is not bound to a single engine already, and the ``mapper``
- and ``bind`` arguments are not passed.
-
- :param params:
- Optional dictionary of bind names mapped to values.
-
- :param mapper:
- Optional :func:`.mapper` or mapped class, used to identify
- the appropriate bind. This argument takes precedence over
- ``clause`` when locating a bind.
-
- :param bind:
- Optional :class:`.Engine` to be used as the bind. If
- this engine is already involved in an ongoing transaction,
- that connection will be used. This argument takes
- precedence over ``mapper`` and ``clause`` when locating
- a bind.
-
- :param \**kw:
- Additional keyword arguments are sent to :meth:`get_bind()`,
- allowing additional arguments to be passed to custom
- implementations of :meth:`get_bind`.
-
- """
- clause = expression._literal_as_text(clause)
-
- if bind is None:
- bind = self.get_bind(mapper, clause=clause, **kw)
-
- return self._connection_for_bind(bind, close_with_result=True).execute(
- clause, params or {})
-
- def scalar(self, clause, params=None, mapper=None, bind=None, **kw):
- """Like :meth:`~.Session.execute` but return a scalar result."""
-
- return self.execute(clause, params=params, mapper=mapper, bind=bind, **kw).scalar()
-
- def close(self):
- """Close this Session.
-
- This clears all items and ends any transaction in progress.
-
- If this session were created with ``autocommit=False``, a new
- transaction is immediately begun. Note that this new transaction does
- not use any connection resources until they are first needed.
-
- """
- self.expunge_all()
- if self.transaction is not None:
- for transaction in self.transaction._iterate_parents():
- transaction.close()
-
- @classmethod
- def close_all(cls):
- """Close *all* sessions in memory."""
-
- for sess in _sessions.values():
- sess.close()
-
- def expunge_all(self):
- """Remove all object instances from this ``Session``.
-
- This is equivalent to calling ``expunge(obj)`` on all objects in this
- ``Session``.
-
- """
- for state in self.identity_map.all_states() + list(self._new):
- state.detach()
-
- self.identity_map = self._identity_cls()
- self._new = {}
- self._deleted = {}
-
- # TODO: need much more test coverage for bind_mapper() and similar !
- # TODO: + crystalize + document resolution order vis. bind_mapper/bind_table
-
- def bind_mapper(self, mapper, bind):
- """Bind operations for a mapper to a Connectable.
-
- mapper
- A mapper instance or mapped class
-
- bind
- Any Connectable: a ``Engine`` or ``Connection``.
-
- All subsequent operations involving this mapper will use the given
- `bind`.
-
- """
- if isinstance(mapper, type):
- mapper = _class_mapper(mapper)
-
- self.__binds[mapper.base_mapper] = bind
- for t in mapper._all_tables:
- self.__binds[t] = bind
-
- def bind_table(self, table, bind):
- """Bind operations on a Table to a Connectable.
-
- table
- A ``Table`` instance
-
- bind
- Any Connectable: a ``Engine`` or ``Connection``.
-
- All subsequent operations involving this ``Table`` will use the
- given `bind`.
-
- """
- self.__binds[table] = bind
-
- def get_bind(self, mapper, clause=None):
- """Return an engine corresponding to the given arguments.
-
- All arguments are optional.
-
- mapper
- Optional, a ``Mapper`` or mapped class
-
- clause
- Optional, A ClauseElement (i.e. select(), text(), etc.)
-
- """
- if mapper is clause is None:
- if self.bind:
- return self.bind
- else:
- raise sa_exc.UnboundExecutionError(
- "This session is not bound to a single Engine or "
- "Connection, and no context was provided to locate "
- "a binding.")
-
- c_mapper = mapper is not None and _class_to_mapper(mapper) or None
-
- # manually bound?
- if self.__binds:
- if c_mapper:
- if c_mapper.base_mapper in self.__binds:
- return self.__binds[c_mapper.base_mapper]
- elif c_mapper.mapped_table in self.__binds:
- return self.__binds[c_mapper.mapped_table]
- if clause is not None:
- for t in sql_util.find_tables(clause, include_crud=True):
- if t in self.__binds:
- return self.__binds[t]
-
- if self.bind:
- return self.bind
-
- if isinstance(clause, sql.expression.ClauseElement) and clause.bind:
- return clause.bind
-
- if c_mapper and c_mapper.mapped_table.bind:
- return c_mapper.mapped_table.bind
-
- context = []
- if mapper is not None:
- context.append('mapper %s' % c_mapper)
- if clause is not None:
- context.append('SQL expression')
-
- raise sa_exc.UnboundExecutionError(
- "Could not locate a bind configured on %s or this Session" % (
- ', '.join(context)))
-
- def query(self, *entities, **kwargs):
- """Return a new ``Query`` object corresponding to this ``Session``."""
-
- return self._query_cls(entities, self, **kwargs)
-
- def _autoflush(self):
- if self.autoflush and not self._flushing:
- self.flush()
-
- def _finalize_loaded(self, states):
- for state, dict_ in states.items():
- state.commit_all(dict_, self.identity_map)
-
- def refresh(self, instance, attribute_names=None, lockmode=None):
- """Expire and refresh the attributes on the given instance.
-
- A query will be issued to the database and all attributes will be
- refreshed with their current database value.
-
- Lazy-loaded relational attributes will remain lazily loaded, so that
- the instance-wide refresh operation will be followed immediately by
- the lazy load of that attribute.
-
- Eagerly-loaded relational attributes will eagerly load within the
- single refresh operation.
-
- Note that a highly isolated transaction will return the same values as
- were previously read in that same transaction, regardless of changes
- in database state outside of that transaction - usage of
- :meth:`~Session.refresh` usually only makes sense if non-ORM SQL
- statement were emitted in the ongoing transaction, or if autocommit
- mode is turned on.
-
- :param attribute_names: optional. An iterable collection of
- string attribute names indicating a subset of attributes to
- be refreshed.
-
- :param lockmode: Passed to the :class:`~sqlalchemy.orm.query.Query`
- as used by :meth:`~sqlalchemy.orm.query.Query.with_lockmode`.
-
- """
- try:
- state = attributes.instance_state(instance)
- except exc.NO_STATE:
- raise exc.UnmappedInstanceError(instance)
-
- self._expire_state(state, attribute_names)
-
- if self.query(_object_mapper(instance))._load_on_ident(
- state.key, refresh_state=state,
- lockmode=lockmode,
- only_load_props=attribute_names) is None:
- raise sa_exc.InvalidRequestError(
- "Could not refresh instance '%s'" %
- mapperutil.instance_str(instance))
-
- def expire_all(self):
- """Expires all persistent instances within this Session.
-
- When any attributes on a persistent instance is next accessed,
- a query will be issued using the
- :class:`.Session` object's current transactional context in order to
- load all expired attributes for the given instance. Note that
- a highly isolated transaction will return the same values as were
- previously read in that same transaction, regardless of changes
- in database state outside of that transaction.
-
- To expire individual objects and individual attributes
- on those objects, use :meth:`Session.expire`.
-
- The :class:`.Session` object's default behavior is to
- expire all state whenever the :meth:`Session.rollback`
- or :meth:`Session.commit` methods are called, so that new
- state can be loaded for the new transaction. For this reason,
- calling :meth:`Session.expire_all` should not be needed when
- autocommit is ``False``, assuming the transaction is isolated.
-
- """
- for state in self.identity_map.all_states():
- state.expire(state.dict, self.identity_map._modified)
-
- def expire(self, instance, attribute_names=None):
- """Expire the attributes on an instance.
-
- Marks the attributes of an instance as out of date. When an expired
- attribute is next accessed, a query will be issued to the
- :class:`.Session` object's current transactional context in order to
- load all expired attributes for the given instance. Note that
- a highly isolated transaction will return the same values as were
- previously read in that same transaction, regardless of changes
- in database state outside of that transaction.
-
- To expire all objects in the :class:`.Session` simultaneously,
- use :meth:`Session.expire_all`.
-
- The :class:`.Session` object's default behavior is to
- expire all state whenever the :meth:`Session.rollback`
- or :meth:`Session.commit` methods are called, so that new
- state can be loaded for the new transaction. For this reason,
- calling :meth:`Session.expire` only makes sense for the specific
- case that a non-ORM SQL statement was emitted in the current
- transaction.
-
- :param instance: The instance to be refreshed.
- :param attribute_names: optional list of string attribute names
- indicating a subset of attributes to be expired.
-
- """
- try:
- state = attributes.instance_state(instance)
- except exc.NO_STATE:
- raise exc.UnmappedInstanceError(instance)
- self._expire_state(state, attribute_names)
-
- def _expire_state(self, state, attribute_names):
- self._validate_persistent(state)
- if attribute_names:
- state.expire_attributes(state.dict, attribute_names)
- else:
- # pre-fetch the full cascade since the expire is going to
- # remove associations
- cascaded = list(state.manager.mapper.cascade_iterator(
- 'refresh-expire', state))
- self._conditional_expire(state)
- for o, m, st_, dct_ in cascaded:
- self._conditional_expire(st_)
-
- def _conditional_expire(self, state):
- """Expire a state if persistent, else expunge if pending"""
-
- if state.key:
- state.expire(state.dict, self.identity_map._modified)
- elif state in self._new:
- self._new.pop(state)
- state.detach()
-
- @util.deprecated("0.7", "The non-weak-referencing identity map "
- "feature is no longer needed.")
- def prune(self):
- """Remove unreferenced instances cached in the identity map.
-
- Note that this method is only meaningful if "weak_identity_map" is set
- to False. The default weak identity map is self-pruning.
-
- Removes any object in this Session's identity map that is not
- referenced in user code, modified, new or scheduled for deletion.
- Returns the number of objects pruned.
-
- """
- return self.identity_map.prune()
-
- def expunge(self, instance):
- """Remove the `instance` from this ``Session``.
-
- This will free all internal references to the instance. Cascading
- will be applied according to the *expunge* cascade rule.
-
- """
- try:
- state = attributes.instance_state(instance)
- except exc.NO_STATE:
- raise exc.UnmappedInstanceError(instance)
- if state.session_id is not self.hash_key:
- raise sa_exc.InvalidRequestError(
- "Instance %s is not present in this Session" %
- mapperutil.state_str(state))
-
- cascaded = list(state.manager.mapper.cascade_iterator(
- 'expunge', state))
- self._expunge_state(state)
- for o, m, st_, dct_ in cascaded:
- self._expunge_state(st_)
-
- def _expunge_state(self, state):
- if state in self._new:
- self._new.pop(state)
- state.detach()
- elif self.identity_map.contains_state(state):
- self.identity_map.discard(state)
- self._deleted.pop(state, None)
- state.detach()
-
- def _register_newly_persistent(self, state):
- mapper = _state_mapper(state)
-
- # prevent against last minute dereferences of the object
- obj = state.obj()
- if obj is not None:
-
- instance_key = mapper._identity_key_from_state(state)
-
- if _none_set.issubset(instance_key[1]) and \
- not mapper.allow_partial_pks or \
- _none_set.issuperset(instance_key[1]):
- raise exc.FlushError('Instance %s has a NULL identity '
- 'key. Check if this flush is occurring at an '
- 'inappropriate time, such as during a load '
- 'operation.' % mapperutil.state_str(state))
-
- if state.key is None:
- state.key = instance_key
- elif state.key != instance_key:
- # primary key switch. use discard() in case another
- # state has already replaced this one in the identity
- # map (see test/orm/test_naturalpks.py ReversePKsTest)
- self.identity_map.discard(state)
- state.key = instance_key
-
- self.identity_map.replace(state)
- state.commit_all(state.dict, self.identity_map)
-
- # remove from new last, might be the last strong ref
- if state in self._new:
- if self._enable_transaction_accounting and self.transaction:
- self.transaction._new[state] = True
- self._new.pop(state)
-
- def _remove_newly_deleted(self, state):
- if self._enable_transaction_accounting and self.transaction:
- self.transaction._deleted[state] = True
-
- self.identity_map.discard(state)
- self._deleted.pop(state, None)
- state.deleted = True
-
- def add(self, instance):
- """Place an object in the ``Session``.
-
- Its state will be persisted to the database on the next flush
- operation.
-
- Repeated calls to ``add()`` will be ignored. The opposite of ``add()``
- is ``expunge()``.
-
- """
- try:
- state = attributes.instance_state(instance)
- except exc.NO_STATE:
- raise exc.UnmappedInstanceError(instance)
-
- self._save_or_update_state(state)
-
- def add_all(self, instances):
- """Add the given collection of instances to this ``Session``."""
-
- for instance in instances:
- self.add(instance)
-
- def _save_or_update_state(self, state):
- self._save_or_update_impl(state)
-
- mapper = _state_mapper(state)
- for o, m, st_, dct_ in mapper.cascade_iterator(
- 'save-update',
- state,
- halt_on=self._contains_state):
- self._save_or_update_impl(st_)
-
- def delete(self, instance):
- """Mark an instance as deleted.
-
- The database delete operation occurs upon ``flush()``.
-
- """
- try:
- state = attributes.instance_state(instance)
- except exc.NO_STATE:
- raise exc.UnmappedInstanceError(instance)
-
- if state.key is None:
- raise sa_exc.InvalidRequestError(
- "Instance '%s' is not persisted" %
- mapperutil.state_str(state))
-
- if state in self._deleted:
- return
-
- # ensure object is attached to allow the
- # cascade operation to load deferred attributes
- # and collections
- self._attach(state)
-
- # grab the cascades before adding the item to the deleted list
- # so that autoflush does not delete the item
- # the strong reference to the instance itself is significant here
- cascade_states = list(state.manager.mapper.cascade_iterator(
- 'delete', state))
-
- self._deleted[state] = state.obj()
- self.identity_map.add(state)
-
- for o, m, st_, dct_ in cascade_states:
- self._delete_impl(st_)
-
- def merge(self, instance, load=True, **kw):
- """Copy the state an instance onto the persistent instance with the
- same identifier.
-
- If there is no persistent instance currently associated with the
- session, it will be loaded. Return the persistent instance. If the
- given instance is unsaved, save a copy of and return it as a newly
- persistent instance. The given instance does not become associated
- with the session.
-
- This operation cascades to associated instances if the association is
- mapped with ``cascade="merge"``.
-
- See :ref:`unitofwork_merging` for a detailed discussion of merging.
-
- """
- if 'dont_load' in kw:
- load = not kw['dont_load']
- util.warn_deprecated('dont_load=True has been renamed to '
- 'load=False.')
-
- _recursive = {}
-
- if load:
- # flush current contents if we expect to load data
- self._autoflush()
-
- _object_mapper(instance) # verify mapped
- autoflush = self.autoflush
- try:
- self.autoflush = False
- return self._merge(
- attributes.instance_state(instance),
- attributes.instance_dict(instance),
- load=load, _recursive=_recursive)
- finally:
- self.autoflush = autoflush
-
- def _merge(self, state, state_dict, load=True, _recursive=None):
- mapper = _state_mapper(state)
- if state in _recursive:
- return _recursive[state]
-
- new_instance = False
- key = state.key
-
- if key is None:
- if not load:
- raise sa_exc.InvalidRequestError(
- "merge() with load=False option does not support "
- "objects transient (i.e. unpersisted) objects. flush() "
- "all changes on mapped instances before merging with "
- "load=False.")
- key = mapper._identity_key_from_state(state)
-
- if key in self.identity_map:
- merged = self.identity_map[key]
-
- elif not load:
- if state.modified:
- raise sa_exc.InvalidRequestError(
- "merge() with load=False option does not support "
- "objects marked as 'dirty'. flush() all changes on "
- "mapped instances before merging with load=False.")
- merged = mapper.class_manager.new_instance()
- merged_state = attributes.instance_state(merged)
- merged_state.key = key
- self._update_impl(merged_state)
- new_instance = True
-
- elif not _none_set.issubset(key[1]) or \
- (mapper.allow_partial_pks and
- not _none_set.issuperset(key[1])):
- merged = self.query(mapper.class_).get(key[1])
- else:
- merged = None
-
- if merged is None:
- merged = mapper.class_manager.new_instance()
- merged_state = attributes.instance_state(merged)
- merged_dict = attributes.instance_dict(merged)
- new_instance = True
- self._save_or_update_state(merged_state)
- else:
- merged_state = attributes.instance_state(merged)
- merged_dict = attributes.instance_dict(merged)
-
- _recursive[state] = merged
-
- # check that we didn't just pull the exact same
- # state out.
- if state is not merged_state:
- # version check if applicable
- if mapper.version_id_col is not None:
- existing_version = mapper._get_state_attr_by_column(
- state,
- state_dict,
- mapper.version_id_col,
- passive=attributes.PASSIVE_NO_INITIALIZE)
-
- merged_version = mapper._get_state_attr_by_column(
- merged_state,
- merged_dict,
- mapper.version_id_col,
- passive=attributes.PASSIVE_NO_INITIALIZE)
-
- if existing_version is not attributes.PASSIVE_NO_RESULT and \
- merged_version is not attributes.PASSIVE_NO_RESULT and \
- existing_version != merged_version:
- raise exc.StaleDataError(
- "Version id '%s' on merged state %s "
- "does not match existing version '%s'. "
- "Leave the version attribute unset when "
- "merging to update the most recent version."
- % (
- existing_version,
- mapperutil.state_str(merged_state),
- merged_version
- ))
-
- merged_state.load_path = state.load_path
- merged_state.load_options = state.load_options
-
- for prop in mapper.iterate_properties:
- prop.merge(self, state, state_dict,
- merged_state, merged_dict,
- load, _recursive)
-
- if not load:
- # remove any history
- merged_state.commit_all(merged_dict, self.identity_map)
-
- if new_instance:
- merged_state.manager.dispatch.load(merged_state, None)
- return merged
-
- @classmethod
- def identity_key(cls, *args, **kwargs):
- return mapperutil.identity_key(*args, **kwargs)
-
- @classmethod
- def object_session(cls, instance):
- """Return the ``Session`` to which an object belongs."""
-
- return object_session(instance)
-
- def _validate_persistent(self, state):
- if not self.identity_map.contains_state(state):
- raise sa_exc.InvalidRequestError(
- "Instance '%s' is not persistent within this Session" %
- mapperutil.state_str(state))
-
- def _save_impl(self, state):
- if state.key is not None:
- raise sa_exc.InvalidRequestError(
- "Object '%s' already has an identity - it can't be registered "
- "as pending" % mapperutil.state_str(state))
-
- self._attach(state)
- if state not in self._new:
- self._new[state] = state.obj()
- state.insert_order = len(self._new)
-
- def _update_impl(self, state):
- if (self.identity_map.contains_state(state) and
- state not in self._deleted):
- return
-
- if state.key is None:
- raise sa_exc.InvalidRequestError(
- "Instance '%s' is not persisted" %
- mapperutil.state_str(state))
-
- if state.deleted:
- raise sa_exc.InvalidRequestError(
- "Instance '%s' has been deleted. Use the make_transient() "
- "function to send this object back to the transient state." %
- mapperutil.state_str(state)
- )
- self._attach(state)
- self._deleted.pop(state, None)
- self.identity_map.add(state)
-
- def _save_or_update_impl(self, state):
- if state.key is None:
- self._save_impl(state)
- else:
- self._update_impl(state)
-
- def _delete_impl(self, state):
- if state in self._deleted:
- return
-
- if state.key is None:
- return
-
- self._attach(state)
- self._deleted[state] = state.obj()
- self.identity_map.add(state)
-
- def _attach(self, state):
- if state.key and \
- state.key in self.identity_map and \
- not self.identity_map.contains_state(state):
- raise sa_exc.InvalidRequestError("Can't attach instance "
- "%s; another instance with key %s is already "
- "present in this session."
- % (mapperutil.state_str(state), state.key))
-
- if state.session_id and state.session_id is not self.hash_key:
- raise sa_exc.InvalidRequestError(
- "Object '%s' is already attached to session '%s' "
- "(this is '%s')" % (mapperutil.state_str(state),
- state.session_id, self.hash_key))
-
- if state.session_id != self.hash_key:
- state.session_id = self.hash_key
- if self.dispatch.after_attach:
- self.dispatch.after_attach(self, state.obj())
-
- def __contains__(self, instance):
- """Return True if the instance is associated with this session.
-
- The instance may be pending or persistent within the Session for a
- result of True.
-
- """
- try:
- state = attributes.instance_state(instance)
- except exc.NO_STATE:
- raise exc.UnmappedInstanceError(instance)
- return self._contains_state(state)
-
- def __iter__(self):
- """Iterate over all pending or persistent instances within this Session."""
-
- return iter(list(self._new.values()) + self.identity_map.values())
-
- def _contains_state(self, state):
- return state in self._new or self.identity_map.contains_state(state)
-
- def flush(self, objects=None):
- """Flush all the object changes to the database.
-
- Writes out all pending object creations, deletions and modifications
- to the database as INSERTs, DELETEs, UPDATEs, etc. Operations are
- automatically ordered by the Session's unit of work dependency
- solver..
-
- Database operations will be issued in the current transactional
- context and do not affect the state of the transaction, unless an
- error occurs, in which case the entire transaction is rolled back.
- You may flush() as often as you like within a transaction to move
- changes from Python to the database's transaction buffer.
-
- For ``autocommit`` Sessions with no active manual transaction, flush()
- will create a transaction on the fly that surrounds the entire set of
- operations int the flush.
-
- objects
- Optional; a list or tuple collection. Restricts the flush operation
- to only these objects, rather than all pending changes.
- Deprecated - this flag prevents the session from properly maintaining
- accounting among inter-object relations and can cause invalid results.
-
- """
-
- if objects:
- util.warn_deprecated(
- "The 'objects' argument to session.flush() is deprecated; "
- "Please do not add objects to the session which should not "
- "yet be persisted.")
-
- if self._flushing:
- raise sa_exc.InvalidRequestError("Session is already flushing")
-
- try:
- self._flushing = True
- self._flush(objects)
- finally:
- self._flushing = False
-
- def _flush(self, objects=None):
- if (not self.identity_map.check_modified() and
- not self._deleted and not self._new):
- return
-
- dirty = self._dirty_states
- if not dirty and not self._deleted and not self._new:
- self.identity_map._modified.clear()
- return
-
- flush_context = UOWTransaction(self)
-
- if self.dispatch.before_flush:
- self.dispatch.before_flush(self, flush_context, objects)
- # re-establish "dirty states" in case the listeners
- # added
- dirty = self._dirty_states
-
- deleted = set(self._deleted)
- new = set(self._new)
-
- dirty = set(dirty).difference(deleted)
-
- # create the set of all objects we want to operate upon
- if objects:
- # specific list passed in
- objset = set()
- for o in objects:
- try:
- state = attributes.instance_state(o)
- except exc.NO_STATE:
- raise exc.UnmappedInstanceError(o)
- objset.add(state)
- else:
- objset = None
-
- # store objects whose fate has been decided
- processed = set()
-
- # put all saves/updates into the flush context. detect top-level
- # orphans and throw them into deleted.
- if objset:
- proc = new.union(dirty).intersection(objset).difference(deleted)
- else:
- proc = new.union(dirty).difference(deleted)
-
- for state in proc:
- is_orphan = _state_mapper(state)._is_orphan(state) and state.has_identity
- flush_context.register_object(state, isdelete=is_orphan)
- processed.add(state)
-
- # put all remaining deletes into the flush context.
- if objset:
- proc = deleted.intersection(objset).difference(processed)
- else:
- proc = deleted.difference(processed)
- for state in proc:
- flush_context.register_object(state, isdelete=True)
-
- if not flush_context.has_work:
- return
-
- flush_context.transaction = transaction = self.begin(
- subtransactions=True)
- try:
- flush_context.execute()
-
- self.dispatch.after_flush(self, flush_context)
-
- flush_context.finalize_flush_changes()
-
- # useful assertions:
- #if not objects:
- # assert not self.identity_map._modified
- #else:
- # assert self.identity_map._modified == \
- # self.identity_map._modified.difference(objects)
-
- self.dispatch.after_flush_postexec(self, flush_context)
-
- transaction.commit()
-
- except:
- transaction.rollback(_capture_exception=True)
- raise
-
-
- def is_modified(self, instance, include_collections=True,
- passive=attributes.PASSIVE_OFF):
- """Return ``True`` if instance has modified attributes.
-
- This method retrieves a history instance for each instrumented
- attribute on the instance and performs a comparison of the current
- value to its previously committed value.
-
- ``include_collections`` indicates if multivalued collections should be
- included in the operation. Setting this to False is a way to detect
- only local-column based properties (i.e. scalar columns or many-to-one
- foreign keys) that would result in an UPDATE for this instance upon
- flush.
-
- The ``passive`` flag indicates if unloaded attributes and collections
- should not be loaded in the course of performing this test.
- Allowed values include :attr:`.PASSIVE_OFF`, :attr:`.PASSIVE_NO_INITIALIZE`.
-
- A few caveats to this method apply:
-
- * Instances present in the 'dirty' collection may result in a value
- of ``False`` when tested with this method. This because while
- the object may have received attribute set events, there may be
- no net changes on its state.
- * Scalar attributes may not have recorded the "previously" set
- value when a new value was applied, if the attribute was not loaded,
- or was expired, at the time the new value was received - in these
- cases, the attribute is assumed to have a change, even if there is
- ultimately no net change against its database value. SQLAlchemy in
- most cases does not need the "old" value when a set event occurs, so
- it skips the expense of a SQL call if the old value isn't present,
- based on the assumption that an UPDATE of the scalar value is
- usually needed, and in those few cases where it isn't, is less
- expensive on average than issuing a defensive SELECT.
-
- The "old" value is fetched unconditionally only if the attribute
- container has the "active_history" flag set to ``True``. This flag
- is set typically for primary key attributes and scalar references
- that are not a simple many-to-one.
-
- """
- try:
- state = attributes.instance_state(instance)
- except exc.NO_STATE:
- raise exc.UnmappedInstanceError(instance)
- dict_ = state.dict
- if passive is True:
- passive = attributes.PASSIVE_NO_INITIALIZE
- elif passive is False:
- passive = attributes.PASSIVE_OFF
- for attr in state.manager.attributes:
- if \
- (
- not include_collections and
- hasattr(attr.impl, 'get_collection')
- ) or not hasattr(attr.impl, 'get_history'):
- continue
-
- (added, unchanged, deleted) = \
- attr.impl.get_history(state, dict_, passive=passive)
-
- if added or deleted:
- return True
- return False
-
- @property
- def is_active(self):
- """True if this Session has an active transaction."""
-
- return self.transaction and self.transaction.is_active
-
- @property
- def _dirty_states(self):
- """The set of all persistent states considered dirty.
-
- This method returns all states that were modified including
- those that were possibly deleted.
-
- """
- return self.identity_map._dirty_states()
-
- @property
- def dirty(self):
- """The set of all persistent instances considered dirty.
-
- Instances are considered dirty when they were modified but not
- deleted.
-
- Note that this 'dirty' calculation is 'optimistic'; most
- attribute-setting or collection modification operations will
- mark an instance as 'dirty' and place it in this set, even if
- there is no net change to the attribute's value. At flush
- time, the value of each attribute is compared to its
- previously saved value, and if there's no net change, no SQL
- operation will occur (this is a more expensive operation so
- it's only done at flush time).
-
- To check if an instance has actionable net changes to its
- attributes, use the is_modified() method.
-
- """
- return util.IdentitySet(
- [state.obj()
- for state in self._dirty_states
- if state not in self._deleted])
-
- @property
- def deleted(self):
- "The set of all instances marked as 'deleted' within this ``Session``"
-
- return util.IdentitySet(self._deleted.values())
-
- @property
- def new(self):
- "The set of all instances marked as 'new' within this ``Session``."
-
- return util.IdentitySet(self._new.values())
-
-_sessions = weakref.WeakValueDictionary()
-
-def make_transient(instance):
- """Make the given instance 'transient'.
-
- This will remove its association with any
- session and additionally will remove its "identity key",
- such that it's as though the object were newly constructed,
- except retaining its values. It also resets the
- "deleted" flag on the state if this object
- had been explicitly deleted by its session.
-
- Attributes which were "expired" or deferred at the
- instance level are reverted to undefined, and
- will not trigger any loads.
-
- """
- state = attributes.instance_state(instance)
- s = _state_session(state)
- if s:
- s._expunge_state(state)
-
- # remove expired state and
- # deferred callables
- state.callables.clear()
- if state.key:
- del state.key
- if state.deleted:
- del state.deleted
-
-def object_session(instance):
- """Return the ``Session`` to which instance belongs.
-
- If the instance is not a mapped instance, an error is raised.
-
- """
-
- try:
- return _state_session(attributes.instance_state(instance))
- except exc.NO_STATE:
- raise exc.UnmappedInstanceError(instance)
-
-
-def _state_session(state):
- if state.session_id:
- try:
- return _sessions[state.session_id]
- except KeyError:
- pass
- return None
-