]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Emphasize context managers when working with Core
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 20 Mar 2020 19:21:10 +0000 (15:21 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 20 Mar 2020 19:21:10 +0000 (15:21 -0400)
Prep the main documentation for the changes coming up in 1.4
by first removing references to engine.execute() outside
of the "connectionless" section, and using context managers
in all cases.  For features that have always been confusing
and are going away, add a note that this feature will
be going away.

Change-Id: I94583444734e36f5432c32cb37f88a9fad15634a

doc/build/core/connections.rst
doc/build/core/ddl.rst
doc/build/orm/session_transaction.rst

index 42806d53974fc1866eb79564ca24c743541cb6a1..0f83fd3fe7b1f4a70d275444282d9d42bda8bca0 100644 (file)
@@ -25,10 +25,10 @@ the :func:`.create_engine` call::
 
 The typical usage of :func:`.create_engine()` is once per particular database
 URL, held globally for the lifetime of a single application process. A single
-:class:`.Engine` manages many individual DBAPI connections on behalf of the
-process and is intended to be called upon in a concurrent fashion. The
-:class:`.Engine` is **not** synonymous to the DBAPI ``connect`` function,
-which represents just one connection resource - the :class:`.Engine` is most
+:class:`.Engine` manages many individual :term:`DBAPI` connections on behalf of
+the process and is intended to be called upon in a concurrent fashion. The
+:class:`.Engine` is **not** synonymous to the DBAPI ``connect`` function, which
+represents just one connection resource - the :class:`.Engine` is most
 efficient when created just once at the module level of an application, not
 per-object or per-function call.
 
@@ -39,62 +39,39 @@ per-object or per-function call.
     engine is initialized per process.  See :ref:`pooling_multiprocessing` for
     details.
 
-The engine can be used directly to issue SQL to the database. The most generic
-way is first procure a connection resource, which you get via the
-:meth:`.Engine.connect` method::
 
-    connection = engine.connect()
-    result = connection.execute("select username from users")
-    for row in result:
-        print("username:", row['username'])
-    connection.close()
+The most basic function of the :class:`.Engine` is to provide access to a
+:class:`.Connection`, which can then invoke SQL statements.   To emit
+a textual statement to the database looks like::
+
+    with engine.connect() as connection:
+        result = connection.execute("select username from users")
+        for row in result:
+            print("username:", row['username'])
 
-The connection is an instance of :class:`.Connection`,
-which is a **proxy** object for an actual DBAPI connection.  The DBAPI
-connection is retrieved from the connection pool at the point at which
-:class:`.Connection` is created.
+Above, the :meth:`.Engine.connect` method returns a :class:`.Connection`
+object, and by using it in a Python context manager (e.g. the ``with:``
+statement) the :meth:`.Connection.close` method is automatically invoked at the
+end of the block.  The :class:`.Connection`, is a **proxy** object for an
+actual DBAPI connection. The DBAPI connection is retrieved from the connection
+pool at the point at which :class:`.Connection` is created.
 
-The returned result is an instance of :class:`.ResultProxy`, which
-references a DBAPI cursor and provides a largely compatible interface
-with that of the DBAPI cursor.   The DBAPI cursor will be closed
+The object returned is known as :class:`.ResultProxy`, which
+references a DBAPI cursor and provides methods for fetching rows
+similar to that of the DBAPI cursor.   The DBAPI cursor will be closed
 by the :class:`.ResultProxy` when all of its result rows (if any) are
 exhausted.  A :class:`.ResultProxy` that returns no rows, such as that of
 an UPDATE statement (without any returned rows),
 releases cursor resources immediately upon construction.
 
-When the :meth:`~.Connection.close` method is called, the referenced DBAPI
-connection is :term:`released` to the connection pool.   From the perspective
-of the database itself, nothing is actually "closed", assuming pooling is
-in use.  The pooling mechanism issues a ``rollback()`` call on the DBAPI
-connection so that any transactional state or locks are removed, and
-the connection is ready for its next usage.
-
-The above procedure can be performed in a shorthand way by using the
-:meth:`~.Engine.execute` method of :class:`.Engine` itself::
-
-    result = engine.execute("select username from users")
-    for row in result:
-        print("username:", row['username'])
-
-Where above, the :meth:`~.Engine.execute` method acquires a new
-:class:`.Connection` on its own, executes the statement with that object,
-and returns the :class:`.ResultProxy`.  In this case, the :class:`.ResultProxy`
-contains a special flag known as ``close_with_result``, which indicates
-that when its underlying DBAPI cursor is closed, the :class:`.Connection`
-object itself is also closed, which again returns the DBAPI connection
-to the connection pool, releasing transactional resources.
-
-If the :class:`.ResultProxy` potentially has rows remaining, it can be
-instructed to close out its resources explicitly::
-
-    result.close()
-
-If the :class:`.ResultProxy` has pending rows remaining and is dereferenced by
-the application without being closed, Python garbage collection will
-ultimately close out the cursor as well as trigger a return of the pooled
-DBAPI connection resource to the pool (SQLAlchemy achieves this by the usage
-of weakref callbacks - *never* the ``__del__`` method) - however it's never a
-good idea to rely upon Python garbage collection to manage resources.
+When the :class:`.Connection` is closed at the end of the ``with:`` block, the
+referenced DBAPI connection is :term:`released` to the connection pool.   From
+the perspective of the database itself, the connection pool will not actually
+"close" the connection assuming the pool has room to store this connection  for
+the next use.  When the connection is returned to the pool for re-use, the
+pooling mechanism issues a ``rollback()`` call on the DBAPI connection so that
+any transactional state or locks are removed, and the connection is ready for
+its next use.
 
 Our example above illustrated the execution of a textual SQL string.
 The :meth:`~.Connection.execute` method can of course accommodate more than
@@ -114,72 +91,66 @@ Using Transactions
   information.
 
 The :class:`~sqlalchemy.engine.Connection` object provides a :meth:`~.Connection.begin`
-method which returns a :class:`.Transaction` object.
-This object is usually used within a try/except clause so that it is
-guaranteed to invoke :meth:`.Transaction.rollback` or :meth:`.Transaction.commit`::
+method which returns a :class:`.Transaction` object.  Like the :class:`.Connection`
+itself, this object is usually used within a Python ``with:`` block so
+that its scope is managed::
 
-    connection = engine.connect()
-    trans = connection.begin()
-    try:
-        r1 = connection.execute(table1.select())
-        connection.execute(table1.insert(), col1=7, col2='this is some data')
-        trans.commit()
-    except:
-        trans.rollback()
-        raise
+    with engine.connect() as connection:
+        with connection.begin():
+            r1 = connection.execute(table1.select())
+            connection.execute(table1.insert(), {"col1": 7, "col2": "this is some data"})
 
-The above block can be created more succinctly using context
-managers, either given an :class:`.Engine`::
+The above block can be stated more simply by using the :meth:`.Engine.begin`
+method of :class:`.Engine`::
 
     # runs a transaction
     with engine.begin() as connection:
         r1 = connection.execute(table1.select())
-        connection.execute(table1.insert(), col1=7, col2='this is some data')
+        connection.execute(table1.insert(), {"col1": 7, "col2": "this is some data"})
 
-Or from the :class:`.Connection`, in which case the :class:`.Transaction` object
-is available as well::
+The block managed by each ``.begin()`` method has the behavior such that
+the transaction is committed when the block completes.   If an exception is
+raised, the transaction is instead rolled back, and the exception propagated
+outwards.
 
-    with connection.begin() as trans:
-        r1 = connection.execute(table1.select())
-        connection.execute(table1.insert(), col1=7, col2='this is some data')
+The underlying object used to represent the transaction is the
+:class:`.Transaction` object.  This object is returned by the
+:meth:`.Connection.begin` method and includes the methods
+:meth:`.Transaction.commit` and :meth:`.Transaction.rollback`.   The context
+manager calling form, which invokes these methods automatically, is recommended
+as a best practice.
 
 .. _connections_nested_transactions:
 
 Nesting of Transaction Blocks
 -----------------------------
 
-The :class:`.Transaction` object also handles "nested"
-behavior by keeping track of the outermost begin/commit pair. In this example,
-two functions both issue a transaction on a :class:`.Connection`, but only the outermost
+.. note:: The "transaction nesting" feature of SQLAlchemy is a legacy feature
+   that will be deprecated in an upcoming release.  New usage paradigms will
+   eliminate the need for it to be present.
+
+The :class:`.Transaction` object also handles "nested" behavior by keeping
+track of the outermost begin/commit pair. In this example, two functions both
+issue a transaction on a :class:`.Connection`, but only the outermost
 :class:`.Transaction` object actually takes effect when it is committed.
 
 .. sourcecode:: python+sql
 
     # method_a starts a transaction and calls method_b
     def method_a(connection):
-        trans = connection.begin() # open a transaction
-        try:
+        with connection.begin():  # open a transaction
             method_b(connection)
-            trans.commit()  # transaction is committed here
-        except:
-            trans.rollback() # this rolls back the transaction unconditionally
-            raise
 
     # method_b also starts a transaction
     def method_b(connection):
-        trans = connection.begin() # open a transaction - this runs in the context of method_a's transaction
-        try:
+        with connection.begin(): # open a transaction - this runs in the
+                                 # context of method_a's transaction
             connection.execute("insert into mytable values ('bat', 'lala')")
-            connection.execute(mytable.insert(), col1='bat', col2='lala')
-            trans.commit()  # transaction is not committed yet
-        except:
-            trans.rollback() # this rolls back the transaction unconditionally
-            raise
+            connection.execute(mytable.insert(), {"col1": "bat", "col2": "lala"})
 
     # open a Connection and call method_a
-    conn = engine.connect()
-    method_a(conn)
-    conn.close()
+    with engine.connect() as conn:
+        method_a(conn)
 
 Above, ``method_a`` is called first, which calls ``connection.begin()``. Then
 it calls ``method_b``. When ``method_b`` calls ``connection.begin()``, it just
@@ -199,6 +170,10 @@ one exists.
 Understanding Autocommit
 ========================
 
+.. note:: The "autocommit" feature of SQLAlchemy is a legacy feature that will
+   be deprecated in an upcoming release.  New usage paradigms will eliminate
+   the need for it to be present.
+
 The previous transaction example illustrates how to use :class:`.Transaction`
 so that several executions can take part in the same transaction. What happens
 when we issue an INSERT, UPDATE or DELETE call without using
@@ -228,19 +203,23 @@ the ORM, as the :class:`.Session` object by default always maintains an
 ongoing :class:`.Transaction`.
 
 Full control of the "autocommit" behavior is available using the generative
-:meth:`.Connection.execution_options` method provided on :class:`.Connection`,
-:class:`.Engine`, :class:`.Executable`, using the "autocommit" flag which will
+:meth:`.Connection.execution_options` method provided on :class:`.Connection`
+and :class:`.Engine`, using the "autocommit" flag which will
 turn on or off the autocommit for the selected scope. For example, a
 :func:`.text` construct representing a stored procedure that commits might use
 it so that a SELECT statement will issue a COMMIT::
 
-    engine.execute(text("SELECT my_mutating_procedure()").execution_options(autocommit=True))
+    with engine.connect().execution_options(autocommit=True) as conn:
+        conn.execute(text("SELECT my_mutating_procedure()"))
 
 .. _dbengine_implicit:
 
 Connectionless Execution, Implicit Execution
 ============================================
 
+.. note:: "Connectionless" and "implicit" execution are legacy SQLAlchemy
+   features that will be deprecated in an upcoming release.
+
 Recall from the first section we mentioned executing with and without explicit
 usage of :class:`.Connection`. "Connectionless" execution
 refers to the usage of the ``execute()`` method on an object which is not a
@@ -274,11 +253,10 @@ Explicit execution delivers the SQL text or constructed SQL expression to the
 .. sourcecode:: python+sql
 
     engine = create_engine('sqlite:///file.db')
-    connection = engine.connect()
-    result = connection.execute(users_table.select())
-    for row in result:
-        # ....
-    connection.close()
+    with engine.connect() as connection:
+        result = connection.execute(users_table.select())
+        for row in result:
+            # ....
 
 Explicit, connectionless execution delivers the expression to the
 :meth:`~.Engine.execute` method of :class:`~sqlalchemy.engine.Engine`:
index db0905d72620e147a861baed7870ce69a287ff06..3ca4dccf485e70d54838155007e6c35a43b9c929 100644 (file)
@@ -140,7 +140,8 @@ provide DDL expressions. For example, to produce a ``CREATE TABLE`` statement:
 .. sourcecode:: python+sql
 
     from sqlalchemy.schema import CreateTable
-    {sql}engine.execute(CreateTable(mytable))
+    with engine.connecT() as conn:
+    {sql}    conn.execute(CreateTable(mytable))
     CREATE TABLE mytable (
         col1 INTEGER,
         col2 INTEGER,
index 836861b0aef67cd1650797b598c598602aee5d1e..5bba243cc28e9ea40f87506f5f8fbf70785e832f 100644 (file)
@@ -85,6 +85,8 @@ The example below illustrates this lifecycle::
         # invalid state is removed.
         session.close()
 
+
+
 .. _session_begin_nested:
 
 Using SAVEPOINT
@@ -165,7 +167,9 @@ complete.
     considered for new projects.   If autocommit mode is used, it is strongly
     advised that the application at least ensure that transaction scope
     is made present via the :meth:`.Session.begin` method, rather than
-    using the session in pure autocommit mode.
+    using the session in pure autocommit mode.   An upcoming release of
+    SQLAlchemy will include a new mode of usage that provides this pattern
+    as a first class feature.
 
     If the :meth:`.Session.begin` method is not used, and operations are allowed
     to proceed using ad-hoc connections with immediate autocommit, then the