]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Fleshed out Session.get_bind(), generating a couple todos: [ticket:1053], [ticket...
authorJason Kirtland <jek@discorporate.us>
Tue, 20 May 2008 21:44:43 +0000 (21:44 +0000)
committerJason Kirtland <jek@discorporate.us>
Tue, 20 May 2008 21:44:43 +0000 (21:44 +0000)
- Trotted out util.pending_deprecation, replacing some 'TODO: deprecate's
- Big session docstring content edit fiesta
- session.py line length and whitespace non-fiesta

lib/sqlalchemy/orm/session.py
lib/sqlalchemy/orm/util.py
test/testlib/testing.py

index c34a695e3bcf38ab648f1b1a7c2553a0a480a55d..4e9c2da79b713326f39353bce90bfc900d7fb109 100644 (file)
@@ -23,15 +23,19 @@ from sqlalchemy.orm import identity
 
 __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.::
 
@@ -41,19 +45,19 @@ def sessionmaker(bind=None, class_=None, autoflush=True, autocommit=False, autoe
         # 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'))
@@ -61,101 +65,104 @@ def sessionmaker(bind=None, class_=None, autoflush=True, autocommit=False, autoe
         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
@@ -171,13 +178,14 @@ def sessionmaker(bind=None, class_=None, autoflush=True, autocommit=False, autoe
             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_), {})
@@ -185,17 +193,17 @@ def sessionmaker(bind=None, class_=None, autoflush=True, autocommit=False, autoe
 
 
 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):
@@ -207,26 +215,30 @@ class SessionTransaction(object):
         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)
@@ -234,55 +246,58 @@ class SessionTransaction(object):
 
     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:
@@ -291,7 +306,9 @@ class SessionTransaction(object):
             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()
 
@@ -301,29 +318,33 @@ class SessionTransaction(object):
             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()):
@@ -331,34 +352,35 @@ class SessionTransaction(object):
             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:
@@ -370,7 +392,7 @@ class SessionTransaction(object):
 
         self.close()
         return self._parent
-    
+
     def _rollback_impl(self):
         for t in util.Set(self._connections.values()):
             t[1].rollback()
@@ -394,7 +416,7 @@ class SessionTransaction(object):
         self._deactivate()
         self.session = None
         self._connections = None
-    
+
     def __enter__(self):
         return self
 
@@ -411,73 +433,90 @@ class SessionTransaction(object):
             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:
@@ -515,67 +554,64 @@ class Session(object):
 
     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
@@ -585,22 +621,19 @@ class Session(object):
             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:
@@ -612,15 +645,16 @@ class Session(object):
         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:
@@ -631,23 +665,30 @@ class Session(object):
         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):
@@ -657,51 +698,55 @@ class Session(object):
             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():
@@ -720,10 +765,10 @@ class Session(object):
     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
 
@@ -731,19 +776,29 @@ class Session(object):
         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)
 
@@ -752,151 +807,175 @@ class Session(object):
             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)
@@ -913,30 +992,31 @@ class Session(object):
     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)
@@ -974,11 +1054,11 @@ class Session(object):
             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
@@ -986,104 +1066,99 @@ class Session(object):
     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:
@@ -1096,7 +1171,11 @@ class Session(object):
         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
@@ -1105,8 +1184,10 @@ class Session(object):
                 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
@@ -1139,14 +1220,16 @@ class Session(object):
     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:
@@ -1159,24 +1242,25 @@ class Session(object):
             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)
@@ -1186,13 +1270,16 @@ class Session(object):
     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),
@@ -1211,16 +1298,16 @@ class Session(object):
             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())
 
@@ -1229,16 +1316,31 @@ class Session(object):
 
 
     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
@@ -1265,15 +1367,20 @@ class Session(object):
         # 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)
 
@@ -1283,8 +1390,9 @@ class Session(object):
 
         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()
 
@@ -1304,22 +1412,24 @@ class Session(object):
             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
@@ -1336,8 +1446,9 @@ class Session(object):
 
         """
         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):
@@ -1346,46 +1457,46 @@ class Session(object):
         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
@@ -1423,10 +1534,9 @@ def _state_for_unknown_persistence_instance(instance, entity_name):
     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:
index 2f2b4c08bca9fe3114ef35930f4a54b19aa370c2..485d60cd1b2f78ec99b6b5311d1de3fa6433d22d 100644 (file)
@@ -452,7 +452,14 @@ def object_mapper(object, entity_name=None, raiseerror=True):
             be located.  If False, return None.
 
     """
-    state = attributes.instance_state(object)
+    try:
+        state = attributes.instance_state(object)
+    except (KeyError, AttributeError):
+        if not raiseerror:
+            return None
+        raise sa_exc.InvalidRequestError(
+            "FIXME Instance %r with entity name '%s' has no mapper associated with it" %
+            (object, entity_name))
     if state.entity_name is not attributes.NO_ENTITY_NAME:
         # Override the given entity name if the object is not transient.
         entity_name = state.entity_name
index eda83a55df1ddd66f581bbd3b7ae9674180950f1..e09899e0c4b01d756dd9b817840ebf0b7b965370 100644 (file)
@@ -302,14 +302,17 @@ def emits_warning(*messages):
             if sa_exc is None:
                 import sqlalchemy.exc as sa_exc
 
+            # todo: should probably be strict about this, too
+            filters = [dict(action='ignore',
+                            category=sa_exc.SAPendingDeprecationWarning)]
             if not messages:
-                filters = [dict(action='ignore',
-                                category=sa_exc.SAWarning)]
+                filters.append([dict(action='ignore',
+                                     category=sa_exc.SAWarning)])
             else:
-                filters = [dict(action='ignore',
-                                message=message,
-                                category=sa_exc.SAWarning)
-                           for message in messages ]
+                filters.extend([dict(action='ignore',
+                                     message=message,
+                                     category=sa_exc.SAWarning)
+                                for message in messages])
             for f in filters:
                 warnings.filterwarnings(**f)
             try:
@@ -337,17 +340,21 @@ def uses_deprecated(*messages):
             if sa_exc is None:
                 import sqlalchemy.exc as sa_exc
 
+            # todo: should probably be strict about this, too
+            filters = [dict(action='ignore',
+                            category=sa_exc.SAPendingDeprecationWarning)]
             if not messages:
-                filters = [dict(action='ignore',
-                                category=sa_exc.SADeprecationWarning)]
+                filters.append(dict(action='ignore',
+                                    category=sa_exc.SADeprecationWarning))
             else:
-                filters = [dict(action='ignore',
-                                message=message,
-                                category=sa_exc.SADeprecationWarning)
-                           for message in
-                           [ (m.startswith('//') and
-                              ('Call to deprecated function ' + m[2:]) or m)
-                             for m in messages] ]
+                filters.extend(
+                    [dict(action='ignore',
+                          message=message,
+                          category=sa_exc.SADeprecationWarning)
+                     for message in
+                     [ (m.startswith('//') and
+                        ('Call to deprecated function ' + m[2:]) or m)
+                       for m in messages] ])
 
             for f in filters:
                 warnings.filterwarnings(**f)
@@ -365,7 +372,8 @@ def resetwarnings():
     if sa_exc is None:
         import sqlalchemy.exc as sa_exc
 
-    warnings.resetwarnings()
+    warnings.filterwarnings('ignore',
+                            category=sa_exc.SAPendingDeprecationWarning) 
     warnings.filterwarnings('error', category=sa_exc.SADeprecationWarning)
     warnings.filterwarnings('error', category=sa_exc.SAWarning)