diff options
Diffstat (limited to 'doc')
| -rw-r--r-- | doc/build/changelog/migration_14.rst | 90 | ||||
| -rw-r--r-- | doc/build/changelog/migration_20.rst | 117 | ||||
| -rw-r--r-- | doc/build/changelog/unreleased_14/3414.rst | 17 | ||||
| -rw-r--r-- | doc/build/conf.py | 4 | ||||
| -rw-r--r-- | doc/build/core/connections.rst | 37 | ||||
| -rw-r--r-- | doc/build/dialects/postgresql.rst | 7 | ||||
| -rw-r--r-- | doc/build/index.rst | 4 | ||||
| -rw-r--r-- | doc/build/intro.rst | 1 | ||||
| -rw-r--r-- | doc/build/orm/examples.rst | 7 | ||||
| -rw-r--r-- | doc/build/orm/extensions/asyncio.rst | 292 | ||||
| -rw-r--r-- | doc/build/orm/extensions/index.rst | 1 |
11 files changed, 442 insertions, 135 deletions
diff --git a/doc/build/changelog/migration_14.rst b/doc/build/changelog/migration_14.rst index 5753cb089..14584fd43 100644 --- a/doc/build/changelog/migration_14.rst +++ b/doc/build/changelog/migration_14.rst @@ -20,8 +20,8 @@ What's New in SQLAlchemy 1.4? For the current status of SQLAlchemy 2.0, see :ref:`migration_20_toplevel`. -Behavioral Changes - General -============================ +Major API changes and features - General +========================================= .. _change_5159: @@ -224,6 +224,92 @@ driven in order to support this new feature. :ticket:`4808` :ticket:`5004` +.. _change_3414: + +Asynchronous IO Support for Core and ORM +------------------------------------------ + +SQLAlchemy now supports Python ``asyncio``-compatible database drivers using an +all-new asyncio front-end interface to :class:`_engine.Connection` for Core +usage as well as :class:`_orm.Session` for ORM use, using the +:class:`_asyncio.AsyncConnection` and :class:`_asyncio.AsyncSession` objects. + +.. note:: The new asyncio feature should be considered **alpha level** for + the initial releases of SQLAlchemy 1.4. This is super new stuff that uses + some previously unfamiliar programming techniques. + +The initial database API supported is the :ref:`dialect-postgresql-asyncpg` +asyncio driver for PostgreSQL. + +The internal features of SQLAlchemy are fully integrated by making use of +the `greenlet <https://greenlet.readthedocs.io/en/latest/>`_ library in order +to adapt the flow of execution within SQLAlchemy's internals to propagate +asyncio ``await`` keywords outwards from the database driver to the end-user +API, which features ``async`` methods. Using this approach, the asyncpg +driver is fully operational within SQLAlchemy's own test suite and features +compatibility with most psycopg2 features. The approach was vetted and +improved upon by developers of the greenlet project for which SQLAlchemy +is appreciative. + +.. sidebar:: greenlets are good + + Don't confuse the greenlet_ library with event-based IO libraries that build + on top of it such as ``gevent`` and ``eventlet``; while the use of these + libraries with SQLAlchemy is common, SQLAlchemy's asyncio integration + **does not** make use of these event based systems in any way. The asyncio + API integrates with the user-provided event loop, typically Python's own + asyncio event loop, without the use of additional threads or event systems. + The approach involves a single greenlet context switch per ``await`` call, + and the extension which makes it possible is less than 20 lines of code. + +The user facing ``async`` API itself is focused around IO-oriented methods such +as :meth:`_asyncio.AsyncEngine.connect` and +:meth:`_asyncio.AsyncConnection.execute`. The new Core constructs strictly +support :term:`2.0 style` usage only; which means all statements must be +invoked given a connection object, in this case +:class:`_asyncio.AsyncConnection`. + +Within the ORM, :term:`2.0 style` query execution is +supported, using :func:`_sql.select` constructs in conjunction with +:meth:`_asyncio.AsyncSession.execute`; the legacy :class:`_orm.Query` +object itself is not supported by the :class:`_asyncio.AsyncSession` class. + +ORM features such as lazy loading of related attributes as well as unexpiry of +expired attributes are by definition disallowed in the traditional asyncio +programming model, as they indicate IO operations that would run implicitly +within the scope of a Python ``getattr()`` operation. To overcome this, the +**traditional** asyncio application should make judicious use of :ref:`eager +loading <loading_toplevel>` techniques as well as forego the use of features +such as :ref:`expire on commit <session_committing>` so that such loads are not +needed. + +For the asyncio application developer who **chooses to break** with +tradition, the new API provides a **strictly optional +feature** such that applications that wish to make use of such ORM features +can opt to organize database-related code into functions which can then be +run within greenlets using the :meth:`_asyncio.AsyncSession.run_sync` +method. See the ``greenlet_orm.py`` example at :ref:`examples_asyncio` +for a demonstration. + +Support for asynchronous cursors is also provided using new methods +:meth:`_asyncio.AsyncConnection.stream` and +:meth:`_asyncio.AsyncSession.stream`, which support a new +:class:`_asyncio.AsyncResult` object that itself provides awaitable +versions of common methods like +:meth:`_asyncio.AsyncResult.all` and +:meth:`_asyncio.AsyncResult.fetchmany`. Both Core and ORM are integrated +with the feature which corresponds to the use of "server side cursors" +in traditional SQLAlchemy. + +.. seealso:: + + :ref:`asyncio_toplevel` + + :ref:`examples_asyncio` + + + +:ticket:`3414` .. _change_deferred_construction: diff --git a/doc/build/changelog/migration_20.rst b/doc/build/changelog/migration_20.rst index 535756f53..7b3d23c8c 100644 --- a/doc/build/changelog/migration_20.rst +++ b/doc/build/changelog/migration_20.rst @@ -1252,10 +1252,7 @@ Asyncio Support .. admonition:: Certainty: definite - A surprising development will allow asyncio support including with the - ORM to be fully implemented. There will even be a **completely optional** - path to having lazy loading be available, for those willing to make use of - some "controversial" patterns. + This is now implemented in 1.4. There was previously an entire section here detailing how asyncio is a nice to have, but not really necessary from a technical standpoint, there are some @@ -1267,113 +1264,7 @@ an entirely separate version of everything be maintained, therefore this makes it feasible to deliver this feature to those users who prefer an all-async application style without impact on the traditional blocking archictecture. -The proof of concept at https://gist.github.com/zzzeek/4e89ce6226826e7a8df13e1b573ad354 -illustrates how to write an asyncio application that makes use of a pure asyncio -driver (asyncpg), with part of the code **in between** remaining as sync code -without the use of any await/async keywords. The central technique involves -minimal use of a greenlet (e.g. stackless Python) to perform the necessary -context switches when an "await" occurs. The approach has been vetted -both with asyncio developers as well as greenlet developers, the latter -of which contributed a great degree of simplification the already simple recipe -such that can context switch async coroutines with no decrease in performance. - -The proof of concept has then been expanded to work within SQLAlchemy Core -and is presently in a Gerrit review. A SQLAlchemy dialect for the asyncpg -driver has been written and it passes most tests. - -Example ORM use will look similar to the following; this example is already -runnable with the in-review codebase:: - - import asyncio - - from sqlalchemy.asyncio import create_async_engine - from sqlalchemy.asyncio import AsyncSession - # ... other imports ... - - async def async_main(): - engine = create_async_engine( - "postgresql+asyncpg://scott:tiger@localhost/test", echo=True, - ) - - - # assume a typical ORM model with classes A and B - - session = AsyncSession(engine) - session.add_all( - [ - A(bs=[B(), B()], data="a1"), - A(bs=[B()], data="a2"), - A(bs=[B(), B()], data="a3"), - ] - ) - await session.commit() - stmt = select(A).options(selectinload(A.bs)) - result = await session.execute(stmt) - for a1 in result.scalars(): - print(a1) - for b1 in a1.bs: - print(b1) - - result = await session.execute(select(A).order_by(A.id)) - - a1 = result.scalars().first() - a1.data = "new data" - await session.commit() - - asyncio.run(async_main()) - -The "controversial" feature, if provided, would include that the "greenlet" -context would be supplied as front-facing API. This would allow an asyncio -application to spawn a greenlet that contains sync-code, which could use the -Core and ORM in a fully traditional manner including that lazy loading -for columns and relationships would be present. This mode of use is -somewhat similar to running an application under an event-based -programming library such as gevent or eventlet, however the underyling -network calls would be within a pure asyncio context, i.e. like that of the -asyncpg driver. An example of this use, which is also runnable with -the in-review codebase:: - - import asyncio - - from sqlalchemy.asyncio import greenlet_spawn - - from sqlalchemy import create_engine - from sqlalchemy.orm import Session - # ... other imports ... - - def main(): - # standard "sync" engine with the "async" driver. - engine = create_engine( - "postgresql+asyncpg://scott:tiger@localhost/test", echo=True, - ) - - # assume a typical ORM model with classes A and B - - session = Session(engine) - session.add_all( - [ - A(bs=[B(), B()], data="a1"), - A(bs=[B()], data="a2"), - A(bs=[B(), B()], data="a3"), - ] - ) - session.commit() - for a1 in session.query(A).all(): - print("a: %s" % a1) - print("bs: %s" % (a1.bs)) # emits a lazyload. - - asyncio.run(greenlet_spawn(main)) - - -Above, we see a ``main()`` function that contains within it a 100% normal -looking Python program using the SQLAlchemy ORM, using plain ORM imports and -basically absolutely nothing out of the ordinary. It just happens to be called -from inside of an ``asyncio.run()`` call rather than directly, and it uses a -DBAPI that is only compatible with asyncio. There is no "monkeypatching" or -anything else like that involved. Any asyncio program can opt -to place it's database-related business methods into the above pattern, -if preferred, rather than using the asyncio SQLAlchemy API directly. This -technique is also being adapted to other frameworks such as Flask and will -hopefully lead to greater interoperability between blocking and non-blocking -libraries and frameworks. +SQLAlchemy 1.4 now includes full asyncio capability with initial support +using the :ref:`dialect-postgresql-asyncpg` Python database driver; +see :ref:`asyncio_toplevel`. diff --git a/doc/build/changelog/unreleased_14/3414.rst b/doc/build/changelog/unreleased_14/3414.rst new file mode 100644 index 000000000..a27824462 --- /dev/null +++ b/doc/build/changelog/unreleased_14/3414.rst @@ -0,0 +1,17 @@ +.. change:: + :tags: feature, engine, orm + :tickets: 3414 + + SQLAlchemy now includes support for Python asyncio within both Core and + ORM, using the included :ref:`asyncio extension <asyncio_toplevel>`. The + extension makes use of the `greenlet + <https://greenlet.readthedocs.io/en/latest/>`_ library in order to adapt + SQLAlchemy's sync-oriented internals such that an asyncio interface that + ultimately interacts with an asyncio database adapter is now feasible. The + single driver supported at the moment is the + :ref:`dialect-postgresql-asyncpg` driver for PostgreSQL. + + .. seealso:: + + :ref:`change_3414` + diff --git a/doc/build/conf.py b/doc/build/conf.py index 13d573296..d4fdf58a0 100644 --- a/doc/build/conf.py +++ b/doc/build/conf.py @@ -106,6 +106,9 @@ autodocmods_convert_modname = { "sqlalchemy.engine.row": "sqlalchemy.engine", "sqlalchemy.engine.cursor": "sqlalchemy.engine", "sqlalchemy.engine.result": "sqlalchemy.engine", + "sqlalchemy.ext.asyncio.result": "sqlalchemy.ext.asyncio", + "sqlalchemy.ext.asyncio.engine": "sqlalchemy.ext.asyncio", + "sqlalchemy.ext.asyncio.session": "sqlalchemy.ext.asyncio", "sqlalchemy.util._collections": "sqlalchemy.util", "sqlalchemy.orm.relationships": "sqlalchemy.orm", "sqlalchemy.orm.interfaces": "sqlalchemy.orm", @@ -128,6 +131,7 @@ zzzeeksphinx_module_prefixes = { "_row": "sqlalchemy.engine", "_schema": "sqlalchemy.schema", "_types": "sqlalchemy.types", + "_asyncio": "sqlalchemy.ext.asyncio", "_expression": "sqlalchemy.sql.expression", "_sql": "sqlalchemy.sql.expression", "_dml": "sqlalchemy.sql.expression", diff --git a/doc/build/core/connections.rst b/doc/build/core/connections.rst index c6186cbaa..b9605bb49 100644 --- a/doc/build/core/connections.rst +++ b/doc/build/core/connections.rst @@ -1225,18 +1225,9 @@ The above will respond to ``create_engine("mysql+foodialect://")`` and load the Connection / Engine API ======================= -.. autoclass:: BaseCursorResult - :members: - -.. autoclass:: ChunkedIteratorResult - :members: - .. autoclass:: Connection :members: -.. autoclass:: Connectable - :members: - .. autoclass:: CreateEnginePlugin :members: @@ -1246,6 +1237,25 @@ Connection / Engine API .. autoclass:: ExceptionContext :members: +.. autoclass:: NestedTransaction + :members: + +.. autoclass:: Transaction + :members: + +.. autoclass:: TwoPhaseTransaction + :members: + + +Result Set API +================= + +.. autoclass:: BaseCursorResult + :members: + +.. autoclass:: ChunkedIteratorResult + :members: + .. autoclass:: FrozenResult :members: @@ -1258,9 +1268,6 @@ Connection / Engine API .. autoclass:: MergedResult :members: -.. autoclass:: NestedTransaction - :members: - .. autoclass:: Result :members: :inherited-members: @@ -1291,9 +1298,3 @@ Connection / Engine API .. autoclass:: RowMapping :members: -.. autoclass:: Transaction - :members: - -.. autoclass:: TwoPhaseTransaction - :members: - diff --git a/doc/build/dialects/postgresql.rst b/doc/build/dialects/postgresql.rst index 35ed285eb..6c36e5814 100644 --- a/doc/build/dialects/postgresql.rst +++ b/doc/build/dialects/postgresql.rst @@ -196,6 +196,13 @@ pg8000 .. automodule:: sqlalchemy.dialects.postgresql.pg8000 +.. _dialect-postgresql-asyncpg: + +asyncpg +------- + +.. automodule:: sqlalchemy.dialects.postgresql.asyncpg + psycopg2cffi ------------ diff --git a/doc/build/index.rst b/doc/build/index.rst index 6afef5083..bee062f89 100644 --- a/doc/build/index.rst +++ b/doc/build/index.rst @@ -44,7 +44,8 @@ of Python objects, proceed first to the tutorial. * **ORM Usage:** :doc:`Session Usage and Guidelines <orm/session>` | - :doc:`Loading Objects <orm/loading_objects>` + :doc:`Loading Objects <orm/loading_objects>` | + :doc:`AsyncIO Support <orm/extensions/asyncio>` * **Extending the ORM:** :doc:`ORM Events and Internals <orm/extending>` @@ -68,6 +69,7 @@ are documented here. In contrast to the ORM's domain-centric mode of usage, the * **Engines, Connections, Pools:** :doc:`Engine Configuration <core/engines>` | :doc:`Connections, Transactions <core/connections>` | + :doc:`AsyncIO Support <orm/extensions/asyncio>` | :doc:`Connection Pooling <core/pooling>` * **Schema Definition:** diff --git a/doc/build/intro.rst b/doc/build/intro.rst index 828ba31b3..4b9376ab0 100644 --- a/doc/build/intro.rst +++ b/doc/build/intro.rst @@ -146,7 +146,6 @@ mechanism:: setuptools. - Installing a Database API ---------------------------------- diff --git a/doc/build/orm/examples.rst b/doc/build/orm/examples.rst index 7a79104b9..10cafb2d2 100644 --- a/doc/build/orm/examples.rst +++ b/doc/build/orm/examples.rst @@ -30,6 +30,13 @@ Associations .. automodule:: examples.association +.. _examples_asyncio: + +Asyncio Integration +------------------- + +.. automodule:: examples.asyncio + Directed Graphs --------------- diff --git a/doc/build/orm/extensions/asyncio.rst b/doc/build/orm/extensions/asyncio.rst new file mode 100644 index 000000000..388dee949 --- /dev/null +++ b/doc/build/orm/extensions/asyncio.rst @@ -0,0 +1,292 @@ +.. _asyncio_toplevel: + +asyncio +======= + +Support for Python asyncio. Support for Core and ORM usage is +included, using asyncio-compatible dialects. + +.. versionadded:: 1.4 + + +.. note:: The asyncio should be regarded as **alpha level** for the + 1.4 release of SQLAlchemy. API details are **subject to change** at + any time. + + +.. seealso:: + + :ref:`change_3414` - initial feature announcement + + :ref:`examples_asyncio` - example scripts illustrating working examples + of Core and ORM use within the asyncio extension. + +Synopsis - Core +--------------- + +For Core use, the :func:`_asyncio.create_async_engine` function creates an +instance of :class:`_asyncio.AsyncEngine` which then offers an async version of +the traditional :class:`_engine.Engine` API. The +:class:`_asyncio.AsyncEngine` delivers an :class:`_asyncio.AsyncConnection` via +its :meth:`_asyncio.AsyncEngine.connect` and :meth:`_asyncio.AsyncEngine.begin` +methods which both deliver asynchronous context managers. The +:class:`_asyncio.AsyncConnection` can then invoke statements using either the +:meth:`_asyncio.AsyncConnection.execute` method to deliver a buffered +:class:`_engine.Result`, or the :meth:`_asyncio.AsyncConnection.stream` method +to deliver a streaming server-side :class:`_asyncio.AsyncResult`:: + + import asyncio + + from sqlalchemy.ext.asyncio import create_async_engine + + async def async_main(): + engine = create_async_engine( + "postgresql+asyncpg://scott:tiger@localhost/test", echo=True, + ) + + async with engine.begin() as conn: + await conn.run_sync(meta.drop_all) + await conn.run_sync(meta.create_all) + + await conn.execute( + t1.insert(), [{"name": "some name 1"}, {"name": "some name 2"}] + ) + + async with engine.connect() as conn: + + # select a Result, which will be delivered with buffered + # results + result = await conn.execute(select(t1).where(t1.c.name == "some name 1")) + + print(result.fetchall()) + + + asyncio.run(async_main()) + +Above, the :meth:`_asyncio.AsyncConnection.run_sync` method may be used to +invoke special DDL functions such as :meth:`_schema.MetaData.create_all` that +don't include an awaitable hook. + +The :class:`_asyncio.AsyncConnection` also features a "streaming" API via +the :meth:`_asyncio.AsyncConnection.stream` method that returns an +:class:`_asyncio.AsyncResult` object. This result object uses a server-side +cursor and provides an async/await API, such as an async iterator:: + + async with engine.connect() as conn: + async_result = await conn.stream(select(t1)) + + async for row in async_result: + print("row: %s" % (row, )) + + +Synopsis - ORM +--------------- + +Using :term:`2.0 style` querying, the :class:`_asyncio.AsyncSession` class +provides full ORM functionality. Within the default mode of use, special care +must be taken to avoid :term:`lazy loading` of ORM relationships and column +attributes, as below where the :func:`_orm.selectinload` eager loading strategy +is used to ensure the ``A.bs`` on each ``A`` object is loaded:: + + import asyncio + + from sqlalchemy.ext.asyncio import create_async_engine + from sqlalchemy.ext.asyncio import AsyncSession + + async def async_main(): + engine = create_async_engine( + "postgresql+asyncpg://scott:tiger@localhost/test", echo=True, + ) + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.drop_all) + await conn.run_sync(Base.metadata.create_all) + + async with AsyncSession(engine) as session: + async with session.begin(): + session.add_all( + [ + A(bs=[B(), B()], data="a1"), + A(bs=[B()], data="a2"), + A(bs=[B(), B()], data="a3"), + ] + ) + + stmt = select(A).options(selectinload(A.bs)) + + result = await session.execute(stmt) + + for a1 in result.scalars(): + print(a1) + for b1 in a1.bs: + print(b1) + + result = await session.execute(select(A).order_by(A.id)) + + a1 = result.scalars().first() + + a1.data = "new data" + + await session.commit() + + asyncio.run(async_main()) + +Above, the :func:`_orm.selectinload` eager loader is employed in order +to eagerly load the ``A.bs`` collection within the scope of the +``await session.execute()`` call. If the default loader strategy of +"lazyload" were left in place, the access of the ``A.bs`` attribute would +raise an asyncio exception. Using traditional asyncio, the application +needs to avoid any points at which IO-on-attribute access may occur. +This also includes that methods such as :meth:`_orm.Session.expire` should be +avoided in favor of :meth:`_asyncio.AsyncSession.refresh`, and that +appropriate loader options should be employed for :func:`_orm.deferred` +columns as well as for :func:`_orm.relationship` constructs. + +Adapting ORM Lazy loads to asyncio +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deepalchemy:: This approach is essentially exposing publicly the + mechanism by which SQLAlchemy is able to provide the asyncio interface + in the first place. While there is no technical issue with doing so, overall + the approach can probably be considered "controversial" as it works against + some of the central philosophies of the asyncio programming model, which + is essentially that any programming statement that can potentially result + in IO being invoked **must** have an ``await`` call, lest the program + does not make it explicitly clear every line at which IO may occur. + This approach does not change that general idea, except that it allows + a series of synchronous IO instructions to be exempted from this rule + within the scope of a function call, essentially bundled up into a single + awaitable. + +As an alternative means of integrating traditional SQLAlchemy "lazy loading" +within an asyncio event loop, an **optional** method known as +:meth:`_asyncio.AsyncSession.run_sync` is provided which will run any +Python function inside of a greenlet, where traditional synchronous +programming concepts will be translated to use ``await`` when they reach the +database driver. A hypothetical approach here is an asyncio-oriented +application can package up database-related methods into functions that are +invoked using :meth:`_asyncio.AsyncSession.run_sync`. + +Altering the above example, if we didn't use :func:`_orm.selectinload` +for the ``A.bs`` collection, we could accomplish our treatment of these +attribute accesses within a separate function:: + + import asyncio + + from sqlalchemy.ext.asyncio import create_async_engine + from sqlalchemy.ext.asyncio import AsyncSession + + def fetch_and_update_objects(session): + """run traditional sync-style ORM code in a function that will be + invoked within an awaitable. + + """ + + # the session object here is a traditional ORM Session. + # all features are available here including legacy Query use. + + stmt = select(A) + + result = session.execute(stmt) + for a1 in result.scalars(): + print(a1) + + # lazy loads + for b1 in a1.bs: + print(b1) + + # legacy Query use + a1 = session.query(A).order_by(A.id).first() + + a1.data = "new data" + + + async def async_main(): + engine = create_async_engine( + "postgresql+asyncpg://scott:tiger@localhost/test", echo=True, + ) + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.drop_all) + await conn.run_sync(Base.metadata.create_all) + + async with AsyncSession(engine) as session: + async with session.begin(): + session.add_all( + [ + A(bs=[B(), B()], data="a1"), + A(bs=[B()], data="a2"), + A(bs=[B(), B()], data="a3"), + ] + ) + + session.run_sync(fetch_and_update_objects) + + await session.commit() + + asyncio.run(async_main()) + +The above approach of running certain functions within a "sync" runner +has some parallels to an application that runs a SQLAlchemy application +on top of an event-based programming library such as ``gevent``. The +differences are as follows: + +1. unlike when using ``gevent``, we can continue to use the standard Python + asyncio event loop, or any custom event loop, without the need to integrate + into the ``gevent`` event loop. + +2. There is no "monkeypatching" whatsoever. The above example makes use of + a real asyncio driver and the underlying SQLAlchemy connection pool is also + using the Python built-in ``asyncio.Queue`` for pooling connections. + +3. The program can freely switch between async/await code and contained + functions that use sync code with virtually no performance penalty. There + is no "thread executor" or any additional waiters or synchronization in use. + +4. The underlying network drivers are also using pure Python asyncio + concepts, no third party networking libraries as ``gevent`` and ``eventlet`` + provides are in use. + +.. currentmodule:: sqlalchemy.ext.asyncio + +Engine API Documentation +------------------------- + +.. autofunction:: create_async_engine + +.. autoclass:: AsyncEngine + :members: + +.. autoclass:: AsyncConnection + :members: + +.. autoclass:: AsyncTransaction + :members: + +Result Set API Documentation +---------------------------------- + +The :class:`_asyncio.AsyncResult` object is an async-adapted version of the +:class:`_result.Result` object. It is only returned when using the +:meth:`_asyncio.AsyncConnection.stream` or :meth:`_asyncio.AsyncSession.stream` +methods, which return a result object that is on top of an active database +cursor. + +.. autoclass:: AsyncResult + :members: + +.. autoclass:: AsyncScalarResult + :members: + +.. autoclass:: AsyncMappingResult + :members: + +ORM Session API Documentation +----------------------------- + +.. autoclass:: AsyncSession + :members: + +.. autoclass:: AsyncSessionTransaction + :members: + + + diff --git a/doc/build/orm/extensions/index.rst b/doc/build/orm/extensions/index.rst index e23fd55ee..ba040b9f6 100644 --- a/doc/build/orm/extensions/index.rst +++ b/doc/build/orm/extensions/index.rst @@ -15,6 +15,7 @@ behavior. In particular the "Horizontal Sharding", "Hybrid Attributes", and .. toctree:: :maxdepth: 1 + asyncio associationproxy automap baked |
