diff options
author | mike bayer <mike_mp@zzzcomputing.com> | 2021-11-07 21:19:45 +0000 |
---|---|---|
committer | Gerrit Code Review <gerrit@ci3.zzzcomputing.com> | 2021-11-07 21:19:45 +0000 |
commit | 201c00bc0837af831f115e8313ad3ccb0be97e7a (patch) | |
tree | beb7558e95d073b63fa4bb76d830ccaa2de9711a /doc | |
parent | 5b1c9053b0903b2d5a06f82b47fe16a870696ddc (diff) | |
parent | d050193daaa8d91371c759296f3304b8641c1976 (diff) | |
download | sqlalchemy-201c00bc0837af831f115e8313ad3ccb0be97e7a.tar.gz |
Merge "fully implement future engine and remove legacy" into main
Diffstat (limited to 'doc')
-rw-r--r-- | doc/build/core/connections.rst | 205 | ||||
-rw-r--r-- | doc/build/core/future.rst | 39 | ||||
-rw-r--r-- | doc/build/core/pooling.rst | 5 | ||||
-rw-r--r-- | doc/build/errors.rst | 60 | ||||
-rw-r--r-- | doc/build/glossary.rst | 20 | ||||
-rw-r--r-- | doc/build/orm/session_api.rst | 9 |
6 files changed, 42 insertions, 296 deletions
diff --git a/doc/build/core/connections.rst b/doc/build/core/connections.rst index 1139e9224..787500c32 100644 --- a/doc/build/core/connections.rst +++ b/doc/build/core/connections.rst @@ -126,189 +126,6 @@ The underlying object used to represent the transaction is the manager calling form, which invokes these methods automatically, is recommended as a best practice. -.. _connections_nested_transactions: - -Nesting of Transaction Blocks ------------------------------ - -.. deprecated:: 1.4 The "transaction nesting" feature of SQLAlchemy is a legacy feature - that is deprecated in the 1.4 release and will be removed in SQLAlchemy 2.0. - The pattern has proven to be a little too awkward and complicated, unless an - application makes more of a first-class framework around the behavior. See - the following subsection :ref:`connections_avoid_nesting`. - -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:`_engine.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): - with connection.begin(): # open a transaction - method_b(connection) - - # method_b also starts a transaction - def method_b(connection): - with connection.begin(): # open a transaction - this runs in the - # context of method_a's transaction - connection.execute(text("insert into mytable values ('bat', 'lala')")) - connection.execute(mytable.insert(), {"col1": "bat", "col2": "lala"}) - - # open a Connection and call method_a - 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 -increments a counter that is decremented when it calls ``commit()``. If either -``method_a`` or ``method_b`` calls ``rollback()``, the whole transaction is -rolled back. The transaction is not committed until ``method_a`` calls the -``commit()`` method. This "nesting" behavior allows the creation of functions -which "guarantee" that a transaction will be used if one was not already -available, but will automatically participate in an enclosing transaction if -one exists. - -.. _connections_avoid_nesting: - -Arbitrary Transaction Nesting as an Antipattern -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -With many years of experience, the above "nesting" pattern has not proven to -be very popular, and where it has been observed in large projects such -as Openstack, it tends to be complicated. - -The most ideal way to organize an application would have a single, or at -least very few, points at which the "beginning" and "commit" of all -database transactions is demarcated. This is also the general -idea discussed in terms of the ORM at :ref:`session_faq_whentocreate`. To -adapt the example from the previous section to this practice looks like:: - - - # method_a calls method_b - def method_a(connection): - method_b(connection) - - # method_b uses the connection and assumes the transaction - # is external - def method_b(connection): - connection.execute(text("insert into mytable values ('bat', 'lala')")) - connection.execute(mytable.insert(), {"col1": "bat", "col2": "lala"}) - - # open a Connection inside of a transaction and call method_a - with engine.begin() as conn: - method_a(conn) - -That is, ``method_a()`` and ``method_b()`` do not deal with the details -of the transaction at all; the transactional scope of the connection is -defined **externally** to the functions that have a SQL dialogue with the -connection. - -It may be observed that the above code has fewer lines, and less indentation -which tends to correlate with lower :term:`cyclomatic complexity`. The -above code is organized such that ``method_a()`` and ``method_b()`` are always -invoked from a point at which a transaction is begun. The previous -version of the example features a ``method_a()`` and a ``method_b()`` that are -trying to be agnostic of this fact, which suggests they are prepared for -at least twice as many potential codepaths through them. - -.. _connections_subtransactions: - -Migrating from the "nesting" pattern -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -As SQLAlchemy's intrinsic-nested pattern is considered legacy, an application -that for either legacy or novel reasons still seeks to have a context that -automatically frames transactions should seek to maintain this functionality -through the use of a custom Python context manager. A similar example is also -provided in terms of the ORM in the "seealso" section below. - -To provide backwards compatibility for applications that make use of this -pattern, the following context manager or a similar implementation based on -a decorator may be used:: - - import contextlib - - @contextlib.contextmanager - def transaction(connection): - if not connection.in_transaction(): - with connection.begin(): - yield connection - else: - yield connection - -The above contextmanager would be used as:: - - # method_a starts a transaction and calls method_b - def method_a(connection): - with transaction(connection): # open a transaction - method_b(connection) - - # method_b either starts a transaction, or uses the one already - # present - def method_b(connection): - with transaction(connection): # open a transaction - connection.execute(text("insert into mytable values ('bat', 'lala')")) - connection.execute(mytable.insert(), {"col1": "bat", "col2": "lala"}) - - # open a Connection and call method_a - with engine.connect() as conn: - method_a(conn) - -A similar approach may be taken such that connectivity is established -on demand as well; the below approach features a single-use context manager -that accesses an enclosing state in order to test if connectivity is already -present:: - - import contextlib - - def connectivity(engine): - connection = None - - @contextlib.contextmanager - def connect(): - nonlocal connection - - if connection is None: - connection = engine.connect() - with connection: - with connection.begin(): - yield connection - else: - yield connection - - return connect - -Using the above would look like:: - - # method_a passes along connectivity context, at the same time - # it chooses to establish a connection by calling "with" - def method_a(connectivity): - with connectivity(): - method_b(connectivity) - - # method_b also wants to use a connection from the context, so it - # also calls "with:", but also it actually uses the connection. - def method_b(connectivity): - with connectivity() as connection: - connection.execute(text("insert into mytable values ('bat', 'lala')")) - connection.execute(mytable.insert(), {"col1": "bat", "col2": "lala"}) - - # create a new connection/transaction context object and call - # method_a - method_a(connectivity(engine)) - -The above context manager acts not only as a "transaction" context but also -as a context that manages having an open connection against a particular -:class:`_engine.Engine`. When using the ORM :class:`_orm.Session`, this -connectivty management is provided by the :class:`_orm.Session` itself. -An overview of ORM connectivity patterns is at :ref:`unitofwork_transaction`. - -.. seealso:: - - :ref:`session_subtransactions` - ORM version - .. _autocommit: Library Level (e.g. emulated) Autocommit @@ -418,18 +235,16 @@ begin a transaction:: with connection.begin(): connection.execute(<statement>) -.. note:: The return value of - the :meth:`_engine.Connection.execution_options` method is a so-called - "branched" connection under the SQLAlchemy 1.x series when not using - :paramref:`_sa.create_engine.future` mode, which is a shallow - copy of the original :class:`_engine.Connection` object. Despite this, - the ``isolation_level`` execution option applies to the - original :class:`_engine.Connection` object and all "branches" overall. - - When using :paramref:`_sa.create_engine.future` mode (i.e. :term:`2.0 style` - usage), the concept of these so-called "branched" connections is removed, - and :meth:`_engine.Connection.execution_options` returns the **same** - :class:`_engine.Connection` object without creating any copies. +.. tip:: The return value of + the :meth:`_engine.Connection.execution_options` method is the same + :class:`_engine.Connection` object upon which the method was called, + meaning, it modifies the state of the :class:`_engine.Connection` + object in place. This is a new behavior as of SQLAlchemy 2.0. + This behavior does not apply to the :meth:`_engine.Engine.execution_options` + method; that method still returns a copy of the :class:`.Engine` and + as described below may be used to construct multiple :class:`.Engine` + objects with different execution options, which nonetheless share the same + dialect and connection pool. The :paramref:`_engine.Connection.execution_options.isolation_level` option may also be set engine wide, as is often preferable. This is achieved by diff --git a/doc/build/core/future.rst b/doc/build/core/future.rst index 204e40135..1174f7d0a 100644 --- a/doc/build/core/future.rst +++ b/doc/build/core/future.rst @@ -4,42 +4,19 @@ SQLAlchemy 2.0 Future (Core) ============================ This package includes a relatively small number of transitional elements -to allow "2.0 mode" to take place within SQLAlchemy 1.4. The primary -objects provided here are :class:`_future.Engine` and :class:`_future.Connection`, -which are both subclasses of the existing :class:`_engine.Engine` and -:class:`_engine.Connection` objects with essentially a smaller set of -methods and the removal of "autocommit". +to allow "2.0 mode" to take place within SQLAlchemy 1.4. -Within the 1.4 series, the "2.0" style of engines and connections is enabled -by passing the :paramref:`_sa.create_engine.future` flag to -:func:`_sa.create_engine`:: +In the 2.0 release of SQLAlchemy, the objects published here are the same +:class:`_engine.Engine`, :class:`_engine.Connection` and +:func:`_sa.create_engine` classes and functions that are +used by default. The package is here for backwards compatibility with +SQLAlchemy 1.4. - from sqlalchemy import create_engine - engine = create_engine("postgresql://user:pass@host/dbname", future=True) - -Similarly, with the ORM, to enable "future" behavior in the ORM :class:`.Session`, -pass the :paramref:`_orm.Session.future` parameter either to the -:class:`.Session` constructor directly, or via the :class:`_orm.sessionmaker` -class:: - - from sqlalchemy.orm import sessionmaker - - Session = sessionmaker(engine, future=True) +The ``sqlalchemy.future`` package will be deprecated in a subsequent +2.x release and eventually removed. .. seealso:: :ref:`migration_20_toplevel` - Introduction to the 2.0 series of SQLAlchemy -.. module:: sqlalchemy.future - -.. autoclass:: sqlalchemy.future.Connection - :members: - -.. autofunction:: sqlalchemy.future.create_engine - -.. autoclass:: sqlalchemy.future.Engine - :members: - -.. autofunction:: sqlalchemy.future.select - diff --git a/doc/build/core/pooling.rst b/doc/build/core/pooling.rst index 858f8b0f5..d0629a4a1 100644 --- a/doc/build/core/pooling.rst +++ b/doc/build/core/pooling.rst @@ -266,8 +266,9 @@ behaviors are needed:: @event.listens_for(some_engine, "engine_connect") def ping_connection(connection, branch): if branch: - # "branch" refers to a sub-connection of a connection, - # we don't want to bother pinging on these. + # this parameter is always False as of SQLAlchemy 2.0, + # but is still accepted by the event hook. In 1.x versions + # of SQLAlchemy, "branched" connections should be skipped. return try: diff --git a/doc/build/errors.rst b/doc/build/errors.rst index 2b163ec26..2318d39b0 100644 --- a/doc/build/errors.rst +++ b/doc/build/errors.rst @@ -536,68 +536,18 @@ sooner. .. _error_8s2b: -Can't reconnect until invalid transaction is rolled back ----------------------------------------------------------- +Can't reconnect until invalid transaction is rolled back. Please rollback() fully before proceeding +----------------------------------------------------------------------------------------------------- This error condition refers to the case where a :class:`_engine.Connection` was invalidated, either due to a database disconnect detection or due to an explicit call to :meth:`_engine.Connection.invalidate`, but there is still a -transaction present that was initiated by the :meth:`_engine.Connection.begin` -method. When a connection is invalidated, any :class:`_engine.Transaction` +transaction present that was initiated either explicitly by the :meth:`_engine.Connection.begin` +method, or due to the connection automatically beginning a transaction as occurs +in the 2.x series of SQLAlchemy when any SQL statements are emitted. When a connection is invalidated, any :class:`_engine.Transaction` that was in progress is now in an invalid state, and must be explicitly rolled back in order to remove it from the :class:`_engine.Connection`. -.. _error_8s2a: - -This connection is on an inactive transaction. Please rollback() fully before proceeding ------------------------------------------------------------------------------------------- - -This error condition was added to SQLAlchemy as of version 1.4. The error -refers to the state where a :class:`_engine.Connection` is placed into a -transaction using a method like :meth:`_engine.Connection.begin`, and then a -further "marker" transaction is created within that scope; the "marker" -transaction is then rolled back using :meth:`.Transaction.rollback` or closed -using :meth:`.Transaction.close`, however the outer transaction is still -present in an "inactive" state and must be rolled back. - -The pattern looks like:: - - engine = create_engine(...) - - connection = engine.connect() - transaction1 = connection.begin() - - # this is a "sub" or "marker" transaction, a logical nesting - # structure based on "real" transaction transaction1 - transaction2 = connection.begin() - transaction2.rollback() - - # transaction1 is still present and needs explicit rollback, - # so this will raise - connection.execute(text("select 1")) - -Above, ``transaction2`` is a "marker" transaction, which indicates a logical -nesting of transactions within an outer one; while the inner transaction -can roll back the whole transaction via its rollback() method, its commit() -method has no effect except to close the scope of the "marker" transaction -itself. The call to ``transaction2.rollback()`` has the effect of -**deactivating** transaction1 which means it is essentially rolled back -at the database level, however is still present in order to accommodate -a consistent nesting pattern of transactions. - -The correct resolution is to ensure the outer transaction is also -rolled back:: - - transaction1.rollback() - -This pattern is not commonly used in Core. Within the ORM, a similar issue can -occur which is the product of the ORM's "logical" transaction structure; this -is described in the FAQ entry at :ref:`faq_session_rollback`. - -The "subtransaction" pattern is to be removed in SQLAlchemy 2.0 so that this -particular programming pattern will no longer be available and this -error message will no longer occur in Core. - .. _error_dbapi: DBAPI Errors diff --git a/doc/build/glossary.rst b/doc/build/glossary.rst index f979df147..88980195b 100644 --- a/doc/build/glossary.rst +++ b/doc/build/glossary.rst @@ -522,16 +522,14 @@ Glossary of classes; "joined", "single", and "concrete". The section :ref:`inheritance_toplevel` describes inheritance mapping fully. - generative - A term that SQLAlchemy uses to refer what's normally known - as :term:`method chaining`; see that term for details. - method chaining - An object-oriented technique whereby the state of an object - is constructed by calling methods on the object. The - object features any number of methods, each of which return - a new object (or in some cases the same object) with - additional state added to the object. + generative + "Method chaining", referred to within SQLAlchemy documentation as + "generative", is an object-oriented technique whereby the state of an + object is constructed by calling methods on the object. The object + features any number of methods, each of which return a new object (or + in some cases the same object) with additional state added to the + object. The two SQLAlchemy objects that make the most use of method chaining are the :class:`_expression.Select` @@ -550,10 +548,6 @@ Glossary :class:`_expression.Select` object with additional qualifiers added. - .. seealso:: - - :term:`generative` - release releases released diff --git a/doc/build/orm/session_api.rst b/doc/build/orm/session_api.rst index ada035e95..93f9541f7 100644 --- a/doc/build/orm/session_api.rst +++ b/doc/build/orm/session_api.rst @@ -46,11 +46,20 @@ Session and sessionmaker() :attr:`_orm.ORMExecuteState.execution_options` .. attribute:: execution_options + The complete dictionary of current execution options. This is a merge of the statement level options with the locally passed execution options. + .. seealso:: + + :attr:`_orm.ORMExecuteState.local_execution_options` + + :meth:`_sql.Executable.execution_options` + + :ref:`orm_queryguide_execution_options` + .. autoclass:: Session :members: :inherited-members: |