diff options
Diffstat (limited to 'lib/python2.7/site-packages/SQLAlchemy-0.7.0-py2.7-linux-x86_64.egg/sqlalchemy/orm/session.py')
-rwxr-xr-x | lib/python2.7/site-packages/SQLAlchemy-0.7.0-py2.7-linux-x86_64.egg/sqlalchemy/orm/session.py | 1725 |
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 - |