__all__ = ['Session', 'SessionTransaction', 'SessionExtension']
-def sessionmaker(bind=None, class_=None, autoflush=True, autocommit=False, autoexpire=True, **kwargs):
+
+def sessionmaker(bind=None, class_=None, autoflush=True, autocommit=False,
+ autoexpire=True, **kwargs):
"""Generate a custom-configured [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.
+ 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.
+ 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.::
# 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::
+ 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::
+ 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()
Options:
-
- 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.
-
- autoexpire
- When ``True``, all instances will be fully expired after each ``rollback()``
- and after each ``commit()``, so that all attribute/object access subsequent
- to a completed transaction will load from the most recent database state.
-
- 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.
- 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.
-
- 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::
-
- sess = Session(binds={
- SomeMappedClass : create_engine('postgres://engine1'),
- somemapper : create_engine('postgres://engine2'),
- some_table : create_engine('postgres://engine3'),
- })
-
- Also see the ``bind_mapper()`` and ``bind_table()`` methods.
-
- \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``.
-
- echo_uow
- When ``True``, configure Python logging to dump all unit-of-work
- transactions. This is the equivalent of
- ``logging.getLogger('sqlalchemy.orm.unitofwork').setLevel(logging.DEBUG)``.
-
- extension
- An optional [sqlalchemy.orm.session#SessionExtension] instance, which will receive
- pre- and post- commit and flush events, as well as a post-rollback event. User-
- defined code may be placed within these hooks using a user-defined subclass
- of ``SessionExtension``.
-
- twophase
- When ``True``, all transactions will be started using
- [sqlalchemy.engine_TwoPhaseTransaction]. 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.
-
- weak_identity_map
- When set to the default value of ``False``, a weak-referencing map is used;
- instances which are not externally referenced will be garbage collected
- immediately. For dereferenced instances which have pending changes present,
- the attribute management system will create a temporary strong-reference to
- the object which lasts until the changes are flushed to the database, at which
- point it's again dereferenced. Alternatively, when using the value ``True``,
- the identity map uses a regular Python dictionary to store instances. The
- session will maintain all instances present until they are removed using
- expunge(), clear(), or purge().
-
+ 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.
+
+ autoexpire
+ When ``True``, all instances will be fully expired after each
+ ``rollback()`` and after each ``commit()``, so that all attribute/object
+ access subsequent to a completed transaction will load from the most
+ recent database state.
+
+ 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.
+
+ 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.
+
+ 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::
+
+ sess = Session(binds={
+ SomeMappedClass: create_engine('postgres://engine1'),
+ somemapper: create_engine('postgres://engine2'),
+ some_table: create_engine('postgres://engine3'),
+ })
+
+ Also see the ``bind_mapper()`` and ``bind_table()`` methods.
+
+ \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``.
+
+ echo_uow
+ When ``True``, configure Python logging to dump all unit-of-work
+ transactions. This is the equivalent of
+ ``logging.getLogger('sqlalchemy.orm.unitofwork').setLevel(logging.DEBUG)``.
+
+ extension
+ An optional [sqlalchemy.orm.session#SessionExtension] instance, which
+ will receive pre- and post- commit and flush events, as well as a
+ post-rollback event. User- defined code may be placed within these
+ hooks using a user-defined subclass of ``SessionExtension``.
+
+ twophase
+ When ``True``, all transactions will be started using
+ [sqlalchemy.engine_TwoPhaseTransaction]. 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.
+
+ weak_identity_map
+ When set to the default value of ``False``, a weak-referencing map is
+ used; instances which are not externally referenced will be garbage
+ collected immediately. For dereferenced instances which have pending
+ changes present, the attribute management system will create a temporary
+ strong-reference to the object which lasts until the changes are flushed
+ to the database, at which point it's again dereferenced. Alternatively,
+ when using the value ``True``, the identity map uses a regular Python
+ dictionary to store instances. The session will maintain all instances
+ present until they are removed using expunge(), clear(), or purge().
+
"""
-
if 'transactional' in kwargs:
- util.warn_deprecated("The 'transactional' argument to sessionmaker() is deprecated; use autocommit=True|False instead.")
+ util.warn_deprecated(
+ "The 'transactional' argument to sessionmaker() is deprecated; "
+ "use autocommit=True|False instead.")
autocommit = not kwargs.pop('transactional')
-
+
kwargs['bind'] = bind
kwargs['autoflush'] = autoflush
kwargs['autocommit'] = autocommit
super(Sess, self).__init__(**local_kwargs)
def configure(self, **new_kwargs):
- """(re)configure the arguments for this sessionmaker.
+ """(Re)configure the arguments for this sessionmaker.
+
+ e.g.::
- e.g.
Session = sessionmaker()
+
Session.configure(bind=create_engine('sqlite://'))
"""
-
kwargs.update(new_kwargs)
configure = classmethod(configure)
s = type.__new__(type, "Session", (Sess, class_), {})
class SessionTransaction(object):
- """Represents a Session-level Transaction.
+ """A Session-level transaction.
This corresponds to one or more [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.
+ 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** threadsafe.
+
"""
def __init__(self, session, parent=None, autoflush=True, nested=False):
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")
+ raise sa_exc.InvalidRequestError(
+ "Can't start a SAVEPOINT transaction when no existing "
+ "transaction is in progress")
self._take_snapshot()
def is_active(self):
return self.session is not None and self._active
is_active = property(is_active)
-
+
def _assert_is_active(self):
self._assert_is_open()
if not self._active:
- raise sa_exc.InvalidRequestError("The transaction is inactive due to a rollback in a subtransaction. Issue rollback() to cancel the transaction.")
+ raise sa_exc.InvalidRequestError(
+ "The transaction is inactive due to a rollback in a "
+ "subtransaction. Issue rollback() to cancel the transaction.")
def _assert_is_open(self):
if self.session is None:
raise sa_exc.InvalidRequestError("The transaction is closed")
-
+
def _is_transaction_boundary(self):
return self.nested or not self._parent
_is_transaction_boundary = property(_is_transaction_boundary)
-
+
def connection(self, bindkey, **kwargs):
self._assert_is_active()
engine = self.session.get_bind(bindkey, **kwargs)
def _begin(self, autoflush=True, nested=False):
self._assert_is_active()
- return SessionTransaction(self.session, self, autoflush=autoflush, nested=nested)
+ return SessionTransaction(
+ self.session, self, autoflush=autoflush, 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)
+ 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 self.autoflush:
self.session.flush()
-
+
self._new = weakref.WeakKeyDictionary()
self._deleted = weakref.WeakKeyDictionary()
-
+
def _restore_snapshot(self):
assert self._is_transaction_boundary
-
+
for s in util.Set(self._deleted).union(self.session._deleted):
self.session._update_impl(s)
-
+
assert not self.session._deleted
-
+
for s in util.Set(self._new).union(self.session._new):
self.session._expunge_state(s)
-
+
for s in self.session.identity_map.all_states():
_expire_state(s, None)
-
+
def _remove_snapshot(self):
assert self._is_transaction_boundary
if not self.nested and self.session.autoexpire:
for s in self.session.identity_map.all_states():
_expire_state(s, None)
-
+
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:
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")
+ raise sa_exc.InvalidRequestError(
+ "Session already has a Connection associated for the "
+ "given Connection's Engine")
else:
conn = bind.contextual_connect()
transaction = conn.begin_nested()
else:
transaction = conn.begin()
-
- self._connections[conn] = self._connections[conn.engine] = (conn, transaction, conn is not bind)
+
+ self._connections[conn] = self._connections[conn.engine] = \
+ (conn, transaction, conn is not bind)
if self.session.extension is not None:
self.session.extension.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")
+ 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.session.extension is not None and (self._parent is None or self.nested):
+ if (self.session.extension is not None and
+ (self._parent is None or self.nested)):
self.session.extension.before_commit(self.session)
-
- if self.session.transaction is not self:
- for subtransaction in self.session.transaction._iterate_parents(upto=self):
+
+ stx = self.session.transaction
+ if stx is not self:
+ for subtransaction in stx._iterate_parents(upto=self):
subtransaction.commit()
-
+
if self.autoflush:
self.session.flush()
-
+
if self._parent is None and self.session.twophase:
try:
for t in util.Set(self._connections.values()):
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 util.Set(self._connections.values()):
t[1].commit()
if self.session.extension is not None:
self.session.extension.after_commit(self.session)
-
+
self._remove_snapshot()
-
+
self.close()
return self._parent
-
+
def rollback(self):
self._assert_is_open()
-
- if self.session.transaction is not self:
- for subtransaction in self.session.transaction._iterate_parents(upto=self):
+
+ 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:
self.close()
return self._parent
-
+
def _rollback_impl(self):
for t in util.Set(self._connections.values()):
t[1].rollback()
self._deactivate()
self.session = None
self._connections = None
-
+
def __enter__(self):
return self
self.rollback()
class Session(object):
- """Encapsulates a set of objects being operated upon within an object-relational operation.
+ """Manages persistence operations for ORM-mapped objects.
- The Session is the front end to SQLAlchemy's **Unit of Work** implementation. The concept
- behind Unit of Work is to track modifications to a field of objects, and then be able to
- flush those changes to the database in a single operation.
+ The Session is the front end to SQLAlchemy's **Unit of Work**
+ implementation. The concept behind Unit of Work is to track modifications
+ to a field of objects, and then be able to flush those changes to the
+ database in a single operation.
SQLAlchemy's unit of work includes these functions:
- * The ability to track in-memory changes on scalar- and collection-based object
- attributes, such that database persistence operations can be assembled based on those
- changes.
-
- * The ability to organize individual SQL queries and population of newly generated
- primary and foreign key-holding attributes during a persist operation such that
- referential integrity is maintained at all times.
-
- * The ability to maintain insert ordering against the order in which new instances were
- added to the session.
-
- * an Identity Map, which is a dictionary keying instances to their unique primary key
- identity. This ensures that only one copy of a particular entity is ever present
- within the session, even if repeated load operations for the same entity occur. This
- allows many parts of an application to get a handle to a particular object without
- any chance of modifications going to two different places.
-
- When dealing with instances of mapped classes, an instance may be *attached* to a
- particular Session, else it is *unattached* . An instance also may or may not correspond
- to an actual row in the database. These conditions break up into four distinct states:
-
- * *Transient* - an instance that's not in a session, and is not saved to the database;
- i.e. it has no database identity. The only relationship such an object has to the ORM
- is that its class has a ``mapper()`` associated with it.
-
- * *Pending* - when you ``add()`` a transient instance, it becomes pending. It still
- wasn't actually flushed to the database yet, but it will be when the next flush
- occurs.
-
- * *Persistent* - An instance which is present in the session and has a record in the
- database. You get persistent instances by either flushing so that the pending
- instances become persistent, or by querying the database for existing instances (or
- moving persistent instances from other sessions into your local session).
-
- * *Detached* - an instance which has a record in the database, but is not in any
- session. Theres nothing wrong with this, and you can use objects normally when
- they're detached, **except** they will not be able to issue any SQL in order to load
- collections or attributes which are not yet loaded, or were marked as "expired".
-
- The session methods which control instance state include ``add()``, ``delete()``,
- ``merge()``, and ``expunge()``.
-
- The Session object is generally **not** threadsafe. A session which is set to ``autocommit``
- and is only read from may be used by concurrent threads if it's acceptable that some object
- instances may be loaded twice.
-
- The typical pattern to managing Sessions in a multi-threaded environment is either to use
- mutexes to limit concurrent access to one thread at a time, or more commonly to establish
- a unique session for every thread, using a threadlocal variable. SQLAlchemy provides
- a thread-managed Session adapter, provided by the [sqlalchemy.orm#scoped_session()] function.
-
+ * The ability to track in-memory changes on scalar- and collection-based
+ object attributes, such that database persistence operations can be
+ assembled based on those changes.
+
+ * The ability to organize individual SQL queries and population of newly
+ generated primary and foreign key-holding attributes during a persist
+ operation such that referential integrity is maintained at all times.
+
+ * The ability to maintain insert ordering against the order in which new
+ instances were added to the session.
+
+ * An Identity Map, which is a dictionary keying instances to their unique
+ primary key identity. This ensures that only one copy of a particular
+ entity is ever present within the session, even if repeated load
+ operations for the same entity occur. This allows many parts of an
+ application to get a handle to a particular object without any chance of
+ modifications going to two different places.
+
+ When dealing with instances of mapped classes, an instance may be
+ *attached* to a particular Session, else it is *unattached* . An instance
+ also may or may not correspond to an actual row in the database. These
+ conditions break up into four distinct states:
+
+ * *Transient* - an instance that's not in a session, and is not saved to
+ the database; i.e. it has no database identity. The only relationship
+ such an object has to the ORM is that its class has a ``mapper()``
+ associated with it.
+
+ * *Pending* - when you ``add()`` a transient instance, it becomes
+ pending. It still wasn't actually flushed to the database yet, but it
+ will be when the next flush occurs.
+
+ * *Persistent* - An instance which is present in the session and has a
+ record in the database. You get persistent instances by either flushing
+ so that the pending instances become persistent, or by querying the
+ database for existing instances (or moving persistent instances from
+ other sessions into your local session).
+
+ * *Detached* - an instance which has a record in the database, but is not
+ in any session. Theres nothing wrong with this, and you can use objects
+ normally when they're detached, **except** they will not be able to
+ issue any SQL in order to load collections or attributes which are not
+ yet loaded, or were marked as "expired".
+
+ The session methods which control instance state include ``add()``,
+ ``delete()``, ``merge()``, and ``expunge()``.
+
+ The Session object is generally **not** threadsafe. A session which is
+ set to ``autocommit`` and is only read from may be used by concurrent
+ threads if it's acceptable that some object instances may be loaded twice.
+
+ The typical pattern to managing Sessions in a multi-threaded environment
+ is either to use mutexes to limit concurrent access to one thread at a
+ time, or more commonly to establish a unique session for every thread,
+ using a threadlocal variable. SQLAlchemy provides a thread-managed
+ Session adapter, provided by the [sqlalchemy.orm#scoped_session()]
+ function.
+
"""
- public_methods = ('get', 'load', 'close', 'add', 'add_all', 'expire_all', 'save', 'commit', 'update', 'save_or_update', 'flush', 'query', 'delete', 'merge', 'clear', 'refresh', 'expire', 'expunge', 'rollback', 'begin', 'begin_nested', 'connection', 'execute', 'scalar', 'get_bind', 'is_modified', '__contains__', '__iter__')
-
- def __init__(self, bind=None, autoflush=True, autoexpire=True, autocommit=False, twophase=False, echo_uow=False, weak_identity_map=True, binds=None, extension=None):
+
+ public_methods = (
+ '__contains__', '__iter__', 'add', 'add_all', 'begin', 'begin_nested',
+ 'clear', 'close', 'commit', 'connection', 'delete', 'execute', 'expire',
+ 'expire_all', 'expunge', 'flush', 'get', 'get_bind', 'is_modified',
+ 'load', 'merge', 'query', 'refresh', 'rollback', 'save',
+ 'save_or_update', 'scalar', 'update')
+
+ def __init__(self, bind=None, autoflush=True, autoexpire=True,
+ autocommit=False, twophase=False, echo_uow=False,
+ weak_identity_map=True, binds=None, extension=None):
"""Construct a new Session.
-
- Arguments to ``Session`` are described using the [sqlalchemy.orm#sessionmaker()] function.
-
+
+ Arguments to ``Session`` are described using the
+ [sqlalchemy.orm#sessionmaker()] function.
+
"""
self.echo_uow = echo_uow
if weak_identity_map:
def begin(self, subtransactions=False, nested=False, _autoflush=True):
"""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 ``begin()`` can create a subtransaction if a
- transaction is already in progress. A subtransaction
- is a non-transactional, delimiting construct that
- allows matching begin()/commit() pairs to be nested
- together, with only the outermost begin/commit pair
- actually affecting transactional state. When a rollback
- is issued, the subtransaction will directly roll back
- the innermost real transaction, however each subtransaction
- still must be explicitly rolled back to maintain proper
- stacking of subtransactions.
-
- If no transaction is in progress,
- then a real transaction is begun.
-
- The ``nested`` flag begins a SAVEPOINT transaction
- and is equivalent to calling ``begin_nested()``.
-
+
+ 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 ``begin()`` can
+ create a subtransaction if a transaction is already in progress. A
+ subtransaction is a non-transactional, delimiting construct that
+ allows matching begin()/commit() pairs to be nested together, with
+ only the outermost begin/commit pair actually affecting transactional
+ state. When a rollback is issued, the subtransaction will directly
+ roll back the innermost real transaction, however each subtransaction
+ still must be explicitly rolled back to maintain proper stacking of
+ subtransactions.
+
+ If no transaction is in progress, then a real transaction is begun.
+
+ The ``nested`` flag begins a SAVEPOINT transaction and is equivalent
+ to calling ``begin_nested()``.
+
"""
if self.transaction is not None:
if subtransactions or nested:
- self.transaction = self.transaction._begin(nested=nested, autoflush=_autoflush)
+ self.transaction = self.transaction._begin(
+ nested=nested, autoflush=_autoflush)
else:
- raise sa_exc.InvalidRequestError("A transaction is already begun. Use subtransactions=True to allow subtransactions.")
+ raise sa_exc.InvalidRequestError(
+ "A transaction is already begun. Use subtransactions=True "
+ "to allow subtransactions.")
else:
- self.transaction = SessionTransaction(self, nested=nested, autoflush=_autoflush)
+ self.transaction = SessionTransaction(
+ self, nested=nested, autoflush=_autoflush)
return self.transaction # needed for __enter__/__exit__ hook
def begin_nested(self):
"""Begin a `nested` transaction on this Session.
- This utilizes a ``SAVEPOINT`` transaction for databases
- which support this feature.
+ The target database(s) must support SQL SAVEPOINTs or a
+ SQLAlchemy-supported vendor implementation of the idea.
- The nested transaction is a real transation, unlike
- a "subtransaction" which corresponds to multiple
- ``begin()`` calls. The next ``rollback()`` or
- ``commit()`` call will operate upon this nested
+ The nested transaction is a real transation, unlike a "subtransaction"
+ which corresponds to multiple ``begin()`` calls. The next
+ ``rollback()`` or ``commit()`` call will operate upon this nested
transaction.
-
+
"""
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-thru.
-
- This method rolls back the current transaction
- or nested transaction regardless of subtransactions
- being in effect. All subtrasactions up to the
- first real transaction are closed. Subtransactions
- occur when begin() is called mulitple times.
-
+ 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 subtrasactions up
+ to the first real transaction are closed. Subtransactions occur when
+ begin() is called mulitple times.
+
"""
if self.transaction is None:
pass
self.begin()
def commit(self):
- """Flush any pending changes, and commit the current transaction
- in progress, assuming no subtransactions are in effect.
-
- If no transaction is in progress, this method raises
- an InvalidRequestError.
-
- 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.
+ """Flush pending changes and commit the current transaction.
+
+ If no transaction is in progress, this method raises an
+ InvalidRequestError.
+
+ 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:
self.transaction.commit()
if self.transaction is None and not self.autocommit:
self.begin()
-
+
def prepare(self):
"""Prepare the current transaction in progress for two phase commit.
- If no transaction is in progress, this method raises
- an InvalidRequestError.
+ 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.
- 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.transaction.prepare()
def connection(self, mapper=None, clause=None, instance=None):
- """Return a ``Connection`` corresponding to this session's
- transactional context, if any.
+ """Return the active Connection.
- If this ``Session`` is transactional, the connection will be in
- the context of this session's transaction. Otherwise, the
- connection is returned by the ``contextual_connect()`` method
- on the engine.
+ Retrieves the ``Connection`` managing the current transaction. Any
+ operations executed on the Connection will take place in the same
+ transactional context as ``Session`` operations.
- The `mapper` argument is a class or mapper to which a bound engine
- will be located; use this when the Session itself is either bound
- to multiple engines or connections, or is not bound to any connectable.
+ For ``autocommit`` Sessions with no active manual transaction,
+ ``connection()`` is a passthrough to ``contextual_connect()`` on the
+ underlying engine.
- \**kwargs are additional arguments which will be passed to get_bind().
- See the get_bind() method for details. Note that the ``ShardedSession``
- subclass takes a different get_bind() argument signature.
- """
+ Ambiguity in multi-bind or unbound Sessions can be resolved through
+ any of the optional keyword arguments. See ``get_bind()`` for more
+ information.
+ mapper
+ Optional, a ``mapper`` or mapped class
+
+ clause
+ Optional, any ``ClauseElement``
+
+ instance
+ Optional, an instance of a mapped class
+
+ """
return self.__connection(self.get_bind(mapper, clause, instance))
def __connection(self, engine, **kwargs):
return engine.contextual_connect(**kwargs)
def execute(self, clause, params=None, mapper=None, instance=None):
- """Execute the given clause, using the current transaction (if any).
+ """Execute a clause within the current transaction.
+
+ Returns a ``ResultProxy`` of execution results. `autocommit` Sessions
+ will create a transaction on the fly.
+
+ Connection ambiguity in multi-bind or unbound Sessions will be
+ resolved by inspecting the clause for binds. The 'mapper' and
+ 'instance' keyword arguments may be used if this is insufficient, See
+ ``get_bind()`` for more information.
- Returns a ``ResultProxy`` corresponding to the execution's results.
-
clause
- a ClauseElement (i.e. select(), text(), etc.) or
+ A ClauseElement (i.e. select(), text(), etc.) or
string SQL statement to be executed
-
- params
- a dictionary of bind parameters.
-
+
+ params
+ Optional, a dictionary of bind parameters.
+
mapper
- a mapped class or Mapper instance which may be needed
- in order to locate the proper bind. This is typically
- if the Session is not directly bound to a single engine.
-
+ Optional, a ``mapper`` or mapped class
+
instance
- used by some Query operations to further identify
- the proper bind, in the case of ShardedSession.
-
+ Optional, an instance of a mapped class
+
"""
clause = expression._literal_as_text(clause)
-
+
engine = self.get_bind(mapper, clause=clause, instance=instance)
- return self.__connection(engine, close_with_result=True).execute(clause, params or {})
+ return self.__connection(engine, close_with_result=True).execute(
+ clause, params or {})
def scalar(self, clause, params=None, mapper=None, instance=None):
"""Like execute() but return a scalar result."""
engine = self.get_bind(mapper, clause=clause, instance=instance)
- return self.__connection(engine, close_with_result=True).scalar(clause, params or {})
+ return self.__connection(engine, close_with_result=True).scalar(
+ clause, params or {})
def close(self):
"""Close this Session.
This clears all items and ends any transaction in progress.
- If this session were created with ``transactional=True``, a
- new transaction is immediately begun. Note that this new
- transaction does not use any connection resources until they
- are first needed.
- """
+ If this session were created with ``transactional=True``, a new
+ transaction is immediately begun. Note that this new transaction does
+ not use any connection resources until they are first needed.
+ """
self.clear()
if self.transaction is not None:
for transaction in self.transaction._iterate_parents():
def expunge_all(self):
"""Remove all object instances from this ``Session``.
- This is equivalent to calling ``expunge()`` for all objects in
- 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):
del state.session_id
self._new = {}
self._deleted = {}
clear = expunge_all
-
+
# TODO: deprecate
#clear = util.deprecated()(expunge_all)
-
+
# 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, entity_name=None):
- """Bind the given `mapper` or `class` to the given ``Engine`` or ``Connection``.
+ """Bind operations for a mapper to a Connectable.
- All subsequent operations involving this ``Mapper`` will use the
- given `bind`.
- """
+ mapper
+ A mapper instance or mapped class
+ bind
+ Any Connectable: a ``Engine`` or ``Connection``.
+
+ entity_name
+ Defaults to None.
+
+ All subsequent operations involving this mapper will use the given
+ `bind`.
+
+ """
if isinstance(mapper, type):
mapper = _class_mapper(mapper, entity_name=entity_name)
self.__binds[t] = bind
def bind_table(self, table, bind):
- """Bind the given `table` to the given ``Engine`` or ``Connection``.
+ """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, instance=None):
- """Return an engine corresponding to the given arguments.
+ def get_bind(self, mapper=None, clause=None, instance=None, state=None):
+ """Resolve and return a configured database bind or raise.
+
+ All arguments are optional.
mapper
- mapper relative to the desired operation.
+ Optional, a ``Mapper`` or mapped class
clause
- a ClauseElement which is to be executed. if
- mapper is not present, this may be used to locate
- Table objects, which are then associated with mappers
- which have associated binds.
-
+ Optional, A ClauseElement (i.e. select(), text(), etc.)
+
instance
- an ORM mapped instance which may be used to further
- locate the correct bind. This is currently used by
- the ShardedSession subclass.
-
+ Optional, an instance of a mapped class
+
+ state
+ Optional, SA internal representation of a mapped instance
+
"""
- if mapper is None and clause is None:
+ if mapper is clause is instance is state is None:
if self.bind:
return self.bind
else:
- raise sa_exc.UnboundExecutionError("This session is not bound to any Engine or Connection; specify a mapper to get_bind()")
-
- elif self.__binds:
- if mapper:
- mapper = _class_to_mapper(mapper)
- if mapper.base_mapper in self.__binds:
- return self.__binds[mapper.base_mapper]
- elif mapper.mapped_table in self.__binds:
- return self.__binds[mapper.mapped_table]
+ raise sa_exc.UnboundExecutionError(
+ "This session is not bound to a single Engine or "
+ "Connection, and no context was provided to locate "
+ "a binding.")
+
+ # fixme: fix internal callers, we're bork3n here
+ if isinstance(instance, attributes.InstanceState):
+ state, instance = instance, None
+
+ mappers = []
+ if state is not None:
+ mappers.append(_state_mapper(state))
+ if instance is not None:
+ mappers.append(_object_mapper(instance))
+ if mapper is not None:
+ mappers.append(_class_to_mapper(mapper))
+
+ # manually bound?
+ if self.__binds:
+ for m in mappers:
+ if m.base_mapper in self.__binds:
+ return self.__binds[m.base_mapper]
+ elif m.mapped_table in self.__binds:
+ return self.__binds[m.mapped_table]
if clause:
for t in sql_util.find_tables(clause):
if t in self.__binds:
return self.__binds[t]
-
- if self.bind:
+ elif self.bind:
return self.bind
- elif isinstance(clause, sql.expression.ClauseElement) and clause.bind:
+
+ if isinstance(clause, sql.expression.ClauseElement) and clause.bind:
return clause.bind
- elif not mapper:
- raise sa_exc.UnboundExecutionError("Could not locate any mapper associated with SQL expression")
- else:
- mapper = _class_to_mapper(mapper)
- e = mapper.mapped_table.bind
- if e is None:
- raise sa_exc.UnboundExecutionError("Could not locate any Engine or Connection bound to mapper '%s'" % str(mapper))
- return e
+
+ for m in mappers:
+ if m.mapped_table.bind:
+ return m.mapped_table.bind
+
+ context = []
+ if mapper is not None:
+ context.append('mapper %s' % c_mapper)
+ if clause is not None:
+ context.append('SQL expression')
+ if instance is not None:
+ context.append('instance %s' % mapperutil.instance_str(instance))
+ if state is not None:
+ context.append('state %r' % state)
+
+ 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 (self.transaction is None or self.transaction.autoflush):
self.flush()
-
+
def _finalize_loaded(self, states):
for state in states:
state.commit_all()
def get(self, class_, ident, entity_name=None):
- """Return an instance of the object based on the given
- identifier, or ``None`` if not found.
+ """Return the instance of class with ident or None.
- The `ident` argument is a scalar or tuple of primary key
- column values in the order of the table def's primary key
- columns.
+ The `ident` argument is a scalar or tuple of primary key column values
+ in the order of the table def's primary key columns.
- The `entity_name` keyword argument may also be specified which
- further qualifies the underlying Mapper used to perform the
- query.
- """
+ The `entity_name` keyword argument may also be specified which further
+ qualifies the underlying Mapper used to perform the query.
+ """
return self.query(class_, entity_name=entity_name).get(ident)
def load(self, class_, ident, entity_name=None):
- """Return an instance of the object based on the given
- identifier.
-
- If not found, raises an exception. The method will **remove
- all pending changes** to the object already existing in the
- ``Session``. The `ident` argument is a scalar or tuple of primary
- key columns in the order of the table def's primary key
- columns.
-
- The `entity_name` keyword argument may also be specified which
- further qualifies the underlying ``Mapper`` used to perform the
- query.
- """
+ """Reset and return the instance of class with ident or raise.
+
+ If not found, raises an exception. The method will **remove all
+ pending changes** to the object already existing in the ``Session``.
+ The `ident` argument is a scalar or tuple of primary key columns in
+ the order of the table def's primary key columns.
+
+ The `entity_name` keyword argument may also be specified which further
+ qualifies the underlying ``Mapper`` used to perform the query.
+ """
return self.query(class_, entity_name=entity_name).load(ident)
def refresh(self, instance, attribute_names=None):
"""Refresh the attributes on the given instance.
- When called, a query will be issued
- to the database which will refresh all attributes with their
- current value.
+ 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.
+ 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.
- The ``attribute_names`` argument is an iterable collection
- of attribute names indicating a subset of attributes to be
- refreshed.
- """
+ The ``attribute_names`` argument is an iterable collection of
+ attribute names indicating a subset of attributes to be refreshed.
+ """
state = attributes.instance_state(instance)
self._validate_persistent(state)
if self.query(_object_mapper(instance))._get(
state.key, refresh_instance=state,
only_load_props=attribute_names) is None:
- raise sa_exc.InvalidRequestError("Could not refresh instance '%s'" % mapperutil.instance_str(instance))
+ raise sa_exc.InvalidRequestError(
+ "Could not refresh instance '%s'" %
+ mapperutil.instance_str(instance))
def expire_all(self):
- """Expires all persistent instances within this Session.
-
- """
+ """Expires all persistent instances within this Session."""
for state in self.identity_map.all_states():
_expire_state(state, None)
-
+
def expire(self, instance, attribute_names=None):
- """Expire the attributes on the given instance.
+ """Expire the attributes on an instance.
- The instance's attributes are instrumented such that
- when an attribute is next accessed, a query will be issued
- to the database which will refresh all attributes with their
- current value.
+ Marks the attributes of an instance as out of date. When an expired
+ attribute is next accessed, query will be issued to the database and
+ the attributes will be refreshed with their current database value.
+ ``expire()`` is a lazy variant of ``refresh()``.
The ``attribute_names`` argument is an iterable collection
of attribute names indicating a subset of attributes to be
expired.
+
"""
state = attributes.instance_state(instance)
self._validate_persistent(state)
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.
+ 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 given `instance` from this ``Session``.
+ """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.
- This will free all internal references to the instance.
- Cascading will be applied according to the *expunge* cascade
- rule.
"""
-
state = attributes.instance_state(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))
+ raise sa_exc.InvalidRequestError(
+ "Instance %s is not present in this Session" %
+ mapperutil.state_str(state))
for s, m in [(state, None)] + list(_cascade_state_iterator('expunge', state)):
self._expunge_state(s)
-
+
def _expunge_state(self, state):
if state in self._new:
self._new.pop(state)
if self.transaction:
self.transaction._new[state] = True
self._new.pop(state)
-
+
def _remove_newly_deleted(self, state):
if self.transaction:
self.transaction._deleted[state] = True
-
+
self.identity_map.discard(state)
self._deleted.pop(state, None)
del state.session_id
def save(self, instance, entity_name=None):
"""Add a transient (unsaved) instance to this ``Session``.
- This operation cascades the `save_or_update` method to
- associated instances if the relation is mapped with
- ``cascade="save-update"``.
+ This operation cascades the `save_or_update` method to associated
+ instances if the relation is mapped with ``cascade="save-update"``.
+
+ The `entity_name` keyword argument will further qualify the specific
+ ``Mapper`` used to handle this instance.
- The `entity_name` keyword argument will further qualify the
- specific ``Mapper`` used to handle this instance.
-
"""
state = _state_for_unsaved_instance(instance, entity_name)
self._save_impl(state)
self._cascade_save_or_update(state, entity_name)
-
- # TODO
- #save = util.deprecated("Use the add() method.")(save)
-
+ save = util.pending_deprecation('0.5.x', "Use session.add()")(save)
+
def _save_without_cascade(self, instance, entity_name=None):
- """used by scoping.py to save on init without cascade."""
-
+ """Used by scoping.py to save on init without cascade."""
+
state = _state_for_unsaved_instance(instance, entity_name)
self._save_impl(state)
-
+
def update(self, instance, entity_name=None):
- """Bring the given detached (saved) instance into this
- ``Session``.
+ """Bring a detached (saved) instance into this ``Session``.
If there is a persistent instance with the same instance key, but
different identity already associated with this ``Session``, an
InvalidRequestError exception is thrown.
- This operation cascades the `save_or_update` method to
- associated instances if the relation is mapped with
- ``cascade="save-update"``.
-
+ This operation cascades the `save_or_update` method to associated
+ instances if the relation is mapped with ``cascade="save-update"``.
+
"""
state = attributes.instance_state(instance)
self._update_impl(state)
self._cascade_save_or_update(state, entity_name)
-
- # TODO
- #update = util.deprecated("Use the add() method.")(update)
-
+ update = util.pending_deprecation('0.5.x', "Use session.add()")(update)
+
def add(self, instance, entity_name=None):
"""Add the given instance into this ``Session``.
+ TODO: rephrase the below in user terms; possibly tie into future
+ function that downgrades persistent to transient. [ticket:1052]
+
The non-None state `key` on the instance's state determines whether
to ``save()`` or ``update()`` the instance.
"""
state = _state_for_unknown_persistence_instance(instance, entity_name)
self._save_or_update_state(state, entity_name)
-
+
def add_all(self, instances):
"""Add the given collection of instances to this ``Session``."""
-
+
for instance in instances:
self.add(instance)
-
- # TODO
- # save_or_update = util.deprecated("Use the add() method.")(add)
- save_or_update = add
-
+
def _save_or_update_state(self, state, entity_name):
self._save_or_update_impl(state)
self._cascade_save_or_update(state, entity_name)
-
+
+ save_or_update = (
+ util.pending_deprecation('0.5.x', "Use session.add()")(add))
+
def _cascade_save_or_update(self, state, entity_name):
for state, mapper in _cascade_unknown_state_iterator('save-update', state, halt_on=lambda c:c in self):
self._save_or_update_impl(state)
def delete(self, instance):
- """Mark the given instance as deleted.
+ """Mark an instance as deleted.
- The delete operation occurs upon ``flush()``.
- """
+ The database delete operation occurs upon ``flush()``.
+ """
state = attributes.instance_state(instance)
self._delete_impl(state)
for state, m in _cascade_state_iterator('delete', state):
self._delete_impl(state, ignore_transient=True)
+ def merge(self, instance, entity_name=None, dont_load=False,
+ _recursive=None):
+ """Copy the state an instance onto the persistent instance with the same identifier.
- def merge(self, instance, entity_name=None, dont_load=False, _recursive=None):
- """Copy the state of the given `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.
- 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"``.
- This operation cascades to associated instances if the
- association is mapped with ``cascade="merge"``.
"""
-
if _recursive is None:
- _recursive = {} # TODO: this should be an IdentityDict for instances, but will need a separate
- # dict for PropertyLoader tuples
+ # TODO: this should be an IdentityDict for instances, but will
+ # need a separate dict for PropertyLoader tuples
+ _recursive = {}
if entity_name is not None:
mapper = _class_mapper(instance.__class__, entity_name=entity_name)
else:
key = state.key
if key is None:
if dont_load:
- raise sa_exc.InvalidRequestError("merge() with dont_load=True option does not support objects transient (i.e. unpersisted) objects. flush() all changes on mapped instances before merging with dont_load=True.")
+ raise sa_exc.InvalidRequestError(
+ "merge() with dont_load=True option does not support "
+ "objects transient (i.e. unpersisted) objects. flush() "
+ "all changes on mapped instances before merging with "
+ "dont_load=True.")
key = mapper._identity_key_from_state(state)
merged = None
merged = self.identity_map[key]
elif dont_load:
if state.modified:
- raise sa_exc.InvalidRequestError("merge() with dont_load=True option does not support objects marked as 'dirty'. flush() all changes on mapped instances before merging with dont_load=True.")
-
+ raise sa_exc.InvalidRequestError(
+ "merge() with dont_load=True option does not support "
+ "objects marked as 'dirty'. flush() all changes on "
+ "mapped instances before merging with dont_load=True.")
merged = mapper.class_manager.new_instance()
merged_state = attributes.instance_state(merged)
merged_state.key = key
identity_key = classmethod(identity_key)
def object_session(cls, instance):
- """Return the ``Session`` to which the given object belongs."""
+ """Return the ``Session`` to which an object belongs."""
return object_session(instance)
object_session = classmethod(object_session)
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))
+ 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:
state.insert_order = len(self._new)
def _update_impl(self, state):
- if self.identity_map.contains_state(state) and state not in self._deleted:
+ 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.key in self.identity_map and not self.identity_map.contains_state(state):
+
+ if (state.key in self.identity_map and
+ not self.identity_map.contains_state(state)):
raise sa_exc.InvalidRequestError(
"Could not update instance '%s', identity key %s; a different "
"instance with the same identity key already exists in this "
"session." % (mapperutil.state_str(state), state.key))
-
+
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)
def _delete_impl(self, state, ignore_transient=False):
if self.identity_map.contains_state(state) and state in self._deleted:
return
-
+
if state.key is None:
if ignore_transient:
return
else:
- raise sa_exc.InvalidRequestError("Instance '%s' is not persisted" % mapperutil.state_str(state))
- if state.key in self.identity_map and not self.identity_map.contains_state(state):
+ raise sa_exc.InvalidRequestError(
+ "Instance '%s' is not persisted" %
+ mapperutil.state_str(state))
+ if (state.key in self.identity_map and
+ not self.identity_map.contains_state(state)):
raise sa_exc.InvalidRequestError(
"Instance '%s' is with key %s already persisted with a "
"different identity" % (mapperutil.state_str(state),
state.session_id = self.hash_key
def __contains__(self, instance):
- """Return True if the given instance is associated with this session.
+ """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.
"""
return self._contains_state(attributes.instance_state(instance))
-
+
def __iter__(self):
- """Return an iterator of all instances which are pending or persistent within this Session."""
+ """Iterate over all pending or persistent instances within this Session."""
return iter(list(self._new.values()) + self.identity_map.values())
def flush(self, objects=None):
- """Flush all the object modifications present in this session
- to the database.
+ """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. You may
+ flush() as often as you like within a transaction to move changes from
+ Python to the database's transaction buffer.
- `objects` is a list or tuple of objects specifically to be
- flushed; if ``None``, all new and modified objects are flushed.
+ 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.
"""
- if not self.identity_map.check_modified() and not self._deleted and not self._new:
+ 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 = False
# store objects whose fate has been decided
processed = util.Set()
- # put all saves/updates into the flush context. detect top-level orphans and throw them into deleted.
+ # put all saves/updates into the flush context. detect top-level
+ # orphans and throw them into deleted.
for state in new.union(dirty).intersection(objset).difference(deleted):
is_orphan = _state_mapper(state)._is_orphan(state)
if is_orphan and not _state_has_identity(state):
- raise exc.FlushError("instance %s is an unsaved, pending instance and is an orphan (is not attached to %s)" %
- (
- mapperutil.state_str(state),
- ", nor ".join(["any parent '%s' instance via that classes' '%s' attribute" % (klass.__name__, key) for (key,klass) in _state_mapper(state).delete_orphans])
- ))
+ path = ", nor ".join(
+ ["any parent '%s' instance "
+ "via that classes' '%s' attribute" %
+ (cls.__name__, key)
+ for (key, cls) in _state_mapper(state).delete_orphans])
+ raise exc.FlushError(
+ "Instance %s is an unsaved, pending instance and is an "
+ "orphan (is not attached to %s)" % (
+ mapperutil.state_str(state), path))
flush_context.register_object(state, isdelete=is_orphan)
processed.add(state)
if len(flush_context.tasks) == 0:
return
-
- flush_context.transaction = transaction = self.begin(subtransactions=True, _autoflush=False)
+
+ flush_context.transaction = transaction = self.begin(
+ subtransactions=True, _autoflush=False)
try:
flush_context.execute()
self.extension.after_flush_postexec(self, flush_context)
def is_modified(self, instance, include_collections=True, passive=False):
- """Return True if the given instance has modified attributes.
+ """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. Note that instances present in the 'dirty'
- collection may result in a value of ``False`` when tested with this method.
+ 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. Note that instances present
+ in the 'dirty' collection may result in a value of ``False`` when
+ tested with this method.
- `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.
+ `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.
- """
+ The `passive` flag indicates if unloaded attributes and collections
+ should not be loaded in the course of performing this test.
+ """
for attr in attributes.manager_of_class(instance.__class__).attributes:
if not include_collections and hasattr(attr.impl, 'get_collection'):
continue
"""
return util.IdentitySet(
- [state for state in self.identity_map.all_states() if state.check_modified()]
- )
+ [state
+ for state in self.identity_map.all_states()
+ if state.check_modified()])
_dirty_states = property(_dirty_states)
def dirty(self):
Instances are considered dirty when they were modified but not
deleted.
- Note that the 'dirty' state here 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
+ 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.
+ 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]
- )
-
+ [state.obj()
+ for state in self._dirty_states
+ if state not in self._deleted])
dirty = property(dirty)
def deleted(self):
- "Return a ``Set`` of all instances marked as 'deleted' within this ``Session``"
-
+ "Return a set of all instances marked as 'deleted' within this ``Session``"
+
return util.IdentitySet(self._deleted.values())
deleted = property(deleted)
def new(self):
- "Return a ``Set`` of all instances marked as 'new' within this ``Session``."
-
+ "Return a set of all instances marked as 'new' within this ``Session``."
+
return util.IdentitySet(self._new.values())
new = property(new)
def _expire_state(state, attribute_names):
- """Standalone expire instance function.
+ """Stand-alone expire instance function.
- Installs a callable with the given instance's _state
- which will fire off when any of the named attributes are accessed;
- their existing value is removed.
+ Installs a callable with the given instance's _state which will fire off
+ when any of the named attributes are accessed; their existing value is
+ removed.
If the list is None or blank, the entire instance is expired.
- """
+ """
state.expire_attributes(attribute_names)
register_attribute = unitofwork.register_attribute
return state
def object_session(instance):
- """Return the ``Session`` to which the given instance is bound, or ``None`` if none."""
-
+ """Return the ``Session`` to which instance belongs, or None."""
return _state_session(attributes.instance_state(instance))
-
+
def _state_session(state):
if state.session_id:
try: