]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Session.connection(), Session.execute() accept 'bind',
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 18 Jan 2011 22:13:36 +0000 (17:13 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 18 Jan 2011 22:13:36 +0000 (17:13 -0500)
to allow execute/connection operations to participate
in the open transaction of an engine explicitly.
[ticket:1996]
- fix up the docs for connection, execute, which were awful.
- correct usage strings in hybrid

CHANGES
lib/sqlalchemy/ext/hybrid.py
lib/sqlalchemy/orm/session.py
test/orm/test_session.py

diff --git a/CHANGES b/CHANGES
index 6b39e23ac1a181715d26d76f906e8798ae690a1b..b86d3b788e908e68d75614acafa8a87a9dab7d5a 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -16,6 +16,11 @@ CHANGES
     assigned, and raise StaleDataError if they don't 
     match.  [ticket:2027]
 
+  - Session.connection(), Session.execute() accept 'bind',
+    to allow execute/connection operations to participate
+    in the open transaction of an engine explicitly.
+    [ticket:1996]
+
 - sql
   - Added NULLS FIRST and NULLS LAST support. It's implemented
     as an extension to the asc() and desc() operators, called
index 5c747400de8b1c6118c148681c1615673795f0ce..56abdabd823688ae67020e5ffda3c9f8fc6df472 100644 (file)
@@ -310,7 +310,7 @@ class hybrid_method(object):
                 def value(self, x, y):
                     return self._value + x + y
             
-                @hybrid_property.expression
+                @value.expression
                 def value(self, x, y):
                     return func.some_function(self._value, x, y)
             
@@ -348,7 +348,7 @@ class hybrid_property(object):
                 def value(self):
                     return self._value
             
-                @hybrid_property.setter
+                @value.setter
                 def value(self, value):
                     self._value = value
             
index 0e1818241faf3b559e4571850932d64b6bb0b63b..e6385a3c85f7be27f891e27032cd0c575c6c29d4 100644 (file)
@@ -636,29 +636,58 @@ class Session(object):
         self.transaction.prepare()
 
     def connection(self, mapper=None, clause=None, 
-                        close_with_result=False, **kw):
-        """Return the active Connection.
+                        bind=None, 
+                        close_with_result=False, 
+                        **kw):
+        """Return a :class:`.Connection` object corresponding to this 
+        :class:`.Session` object's transactional state.
+
+        If this :class:`.Session` is configured with ``autocommit=False``,
+        either the :class:`.Connection` correspoinding to the current transaction
+        is returned, or if no transaction is in progress, a new one is begun
+        and the :class:`.Connection` returned.
+        
+        Alternatively, if this :class:`.Session` is configured with ``autocommit=True``,
+        an ad-hoc :class:`.Connection` is returned using the :meth:`.Engine.contextual_connect` 
+        on the underlying :class:`.Engine`.
+
+        Ambiguity in multi-bind or unbound :class:`.Session` objects can be resolved through
+        any of the optional keyword arguments.   This ultimately makes usage of the 
+        :meth:`.get_bind` method for resolution.
+
+        :param bind:
+          Optional :class:`.Engine` to be used as the bind.  If
+          this engine is already involved in an ongoing transaction,
+          that connection will be used.  This argument takes precedence
+          over ``mapper``, ``clause``.
 
-        Retrieves the ``Connection`` managing the current transaction.  Any
-        operations executed on the Connection will take place in the same
-        transactional context as ``Session`` operations.
+        :param mapper:
+          Optional :func:`.mapper` mapped class, used to identify
+          the appropriate bind.  This argument takes precedence over
+          ``clause``.
 
-        For ``autocommit`` Sessions with no active manual transaction,
-        ``connection()`` is a passthrough to ``contextual_connect()`` on the
-        underlying engine.
+        :param clause:
+            A :class:`.ClauseElement` (i.e. :func:`~.sql.expression.select`, 
+            :func:`~.sql.expression.text`, 
+            etc.) which will be used to locate a bind, if a bind
+            cannot otherwise be identified.
 
-        Ambiguity in multi-bind or unbound Sessions can be resolved through
-        any of the optional keyword arguments.  See ``get_bind()`` for more
-        information.
+        :param close_with_result: Passed to :meth:`Engine.connect`, indicating
+          the :class:`.Connection` should be considered "single use", automatically
+          closing when the first result set is closed.  This flag only has 
+          an effect if this :class:`.Session` is configued with ``autocommit=True``
+          and does not already have a  transaction in progress.
 
-        mapper
-          Optional, a ``mapper`` or mapped class
-
-        clause
-          Optional, any ``ClauseElement``
+        :param \**kw:
+          Additional keyword arguments are sent to :meth:`get_bind()`,
+          allowing additional arguments to be passed to custom 
+          implementations of :meth:`get_bind`.
 
         """
-        return self._connection_for_bind(self.get_bind(mapper, clause, **kw), 
+        if bind is None:
+            bind = self.get_bind(mapper, clause=clause, **kw)
+
+        return self._connection_for_bind(bind, 
                                         close_with_result=close_with_result)
 
     def _connection_for_bind(self, engine, **kwargs):
@@ -667,68 +696,73 @@ class Session(object):
         else:
             return engine.contextual_connect(**kwargs)
 
-    def execute(self, clause, params=None, mapper=None, **kw):
+    def execute(self, clause, params=None, mapper=None, bind=None, **kw):
         """Execute a clause within the current transaction.
 
-        Returns a :class:`~sqlalchemy.engine.base.ResultProxy` representing
+        Returns a :class:`.ResultProxy` representing
         results of the statement execution, in the same manner as that of an
-        :class:`~sqlalchemy.engine.base.Engine` or
-        :class:`~sqlalchemy.engine.base.Connection`.
-
-        :meth:`Session.execute` accepts any executable clause construct, such
-        as :func:`~sqlalchemy.sql.expression.select`,
-        :func:`~sqlalchemy.sql.expression.insert`,
-        :func:`~sqlalchemy.sql.expression.update`,
-        :func:`~sqlalchemy.sql.expression.delete`, and
-        :func:`~sqlalchemy.sql.expression.text`, and additionally accepts
+        :class:`.Engine` or
+        :class:`.Connection`.
+
+        :meth:`~.Session.execute` accepts any executable clause construct, such
+        as :func:`~.sql.expression.select`,
+        :func:`~.sql.expression.insert`,
+        :func:`~.sql.expression.update`,
+        :func:`~.sql.expression.delete`, and
+        :func:`~.sql.expression.text`, and additionally accepts
         plain strings that represent SQL statements. If a plain string is
         passed, it is first converted to a
-        :func:`~sqlalchemy.sql.expression.text` construct, which here means
+        :func:`~.sql.expression.text` construct, which here means
         that bind parameters should be specified using the format ``:param``.
 
         The statement is executed within the current transactional context of
-        this :class:`Session`. If this :class:`Session` is set for
-        "autocommit", and no transaction is in progress, an ad-hoc transaction
-        will be created for the life of the result (i.e., a connection is
-        checked out from the connection pool, which is returned when the
-        result object is closed).
-
-        If the :class:`Session` is not bound to an
-        :class:`~sqlalchemy.engine.base.Engine` or
-        :class:`~sqlalchemy.engine.base.Connection`, the given clause will be
-        inspected for binds (i.e., looking for "bound metadata"). If the
-        session is bound to multiple connectables, the ``mapper`` keyword
-        argument is typically passed in to specify which bind should be used
-        (since the :class:`Session` keys multiple bind sources to a series of
-        :func:`mapper` objects). See :meth:`get_bind` for further details on
-        bind resolution.
-
+        this :class:`.Session`, using the same behavior as that of
+        the :meth:`.Session.connection` method to determine the active
+        :class:`.Connection`.   The ``close_with_result`` flag is
+        set to ``True`` so that an ``autocommit=True`` :class:`.Session`
+        with no active transaction will produce a result that auto-closes
+        the underlying :class:`.Connection`.
+        
         :param clause:
-            A ClauseElement (i.e. select(), text(), etc.) or
-            string SQL statement to be executed
+            A :class:`.ClauseElement` (i.e. :func:`~.sql.expression.select`, 
+            :func:`~.sql.expression.text`, etc.) or string SQL statement to be executed.  The clause
+            will also be used to locate a bind, if this :class:`.Session`
+            is not bound to a single engine already, and the ``mapper``
+            and ``bind`` arguments are not passed.
 
         :param params:
-            Optional, a dictionary of bind parameters.
+            Optional dictionary of bind names mapped to values.
 
         :param mapper:
-          Optional, a ``mapper`` or mapped class
-
+          Optional :func:`.mapper` or mapped class, used to identify
+          the appropriate bind.  This argument takes precedence over
+          ``clause`` when locating a bind.
+
+        :param bind:
+          Optional :class:`.Engine` to be used as the bind.  If
+          this engine is already involved in an ongoing transaction,
+          that connection will be used.  This argument takes
+          precedence over ``mapper`` and ``clause`` when locating
+          a bind.
+          
         :param \**kw:
-          Additional keyword arguments are sent to :meth:`get_bind()`
-          which locates a connectable to use for the execution.
+          Additional keyword arguments are sent to :meth:`get_bind()`,
+          allowing additional arguments to be passed to custom 
+          implementations of :meth:`get_bind`.
 
         """
         clause = expression._literal_as_text(clause)
 
-        engine = self.get_bind(mapper, clause=clause, **kw)
+        if bind is None:
+            bind = self.get_bind(mapper, clause=clause, **kw)
 
-        return self._connection_for_bind(engine, close_with_result=True).execute(
+        return self._connection_for_bind(bind, close_with_result=True).execute(
             clause, params or {})
 
-    def scalar(self, clause, params=None, mapper=None, **kw):
-        """Like execute() but return a scalar result."""
+    def scalar(self, clause, params=None, mapper=None, bind=None, **kw):
+        """Like :meth:`~.Session.execute` but return a scalar result."""
 
-        return self.execute(clause, params=params, mapper=mapper, **kw).scalar()
+        return self.execute(clause, params=params, mapper=mapper, bind=bind, **kw).scalar()
 
     def close(self):
         """Close this Session.
index ab0251368e2af67d1be4585d641d34488aa647c4..bccfe6426bfc103991de718e6affc6d6273a05ac 100644 (file)
@@ -809,6 +809,31 @@ class SessionTest(_fixtures.FixtureTest):
         assert not c.in_transaction()
         assert c.scalar("select count(1) from users") == 1
 
+    @testing.resolve_artifact_names
+    def test_bind_arguments(self):
+        mapper(User, users)
+        mapper(Address, addresses)
+
+        e1 = engines.testing_engine()
+        e2 = engines.testing_engine()
+        e3 = engines.testing_engine()
+
+        sess = Session(e3)
+        sess.bind_mapper(User, e1)
+        sess.bind_mapper(Address, e2)
+
+        assert sess.connection().engine is e3
+        assert sess.connection(bind=e1).engine is e1
+        assert sess.connection(mapper=Address, bind=e1).engine is e1
+        assert sess.connection(mapper=Address).engine is e2
+        assert sess.connection(clause=addresses.select()).engine is e2
+        assert sess.connection(mapper=User, 
+                                clause=addresses.select()).engine is e1
+        assert sess.connection(mapper=User, 
+                                clause=addresses.select(), 
+                                bind=e2).engine is e2
+
+        sess.close()
 
     @engines.close_open_connections
     @testing.resolve_artifact_names