summaryrefslogtreecommitdiff
path: root/doc
diff options
context:
space:
mode:
Diffstat (limited to 'doc')
-rw-r--r--doc/build/changelog/migration_14.rst90
-rw-r--r--doc/build/changelog/migration_20.rst117
-rw-r--r--doc/build/changelog/unreleased_14/3414.rst17
-rw-r--r--doc/build/conf.py4
-rw-r--r--doc/build/core/connections.rst37
-rw-r--r--doc/build/dialects/postgresql.rst7
-rw-r--r--doc/build/index.rst4
-rw-r--r--doc/build/intro.rst1
-rw-r--r--doc/build/orm/examples.rst7
-rw-r--r--doc/build/orm/extensions/asyncio.rst292
-rw-r--r--doc/build/orm/extensions/index.rst1
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