first add files
This commit is contained in:
22
lib/sqlalchemy/ext/asyncio/__init__.py
Normal file
22
lib/sqlalchemy/ext/asyncio/__init__.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# ext/asyncio/__init__.py
|
||||
# Copyright (C) 2020-2022 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: https://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from .engine import async_engine_from_config
|
||||
from .engine import AsyncConnection
|
||||
from .engine import AsyncEngine
|
||||
from .engine import AsyncTransaction
|
||||
from .engine import create_async_engine
|
||||
from .events import AsyncConnectionEvents
|
||||
from .events import AsyncSessionEvents
|
||||
from .result import AsyncMappingResult
|
||||
from .result import AsyncResult
|
||||
from .result import AsyncScalarResult
|
||||
from .scoping import async_scoped_session
|
||||
from .session import async_object_session
|
||||
from .session import async_session
|
||||
from .session import AsyncSession
|
||||
from .session import AsyncSessionTransaction
|
||||
89
lib/sqlalchemy/ext/asyncio/base.py
Normal file
89
lib/sqlalchemy/ext/asyncio/base.py
Normal file
@@ -0,0 +1,89 @@
|
||||
import abc
|
||||
import functools
|
||||
import weakref
|
||||
|
||||
from . import exc as async_exc
|
||||
|
||||
|
||||
class ReversibleProxy:
|
||||
# weakref.ref(async proxy object) -> weakref.ref(sync proxied object)
|
||||
_proxy_objects = {}
|
||||
__slots__ = ("__weakref__",)
|
||||
|
||||
def _assign_proxied(self, target):
|
||||
if target is not None:
|
||||
target_ref = weakref.ref(target, ReversibleProxy._target_gced)
|
||||
proxy_ref = weakref.ref(
|
||||
self,
|
||||
functools.partial(ReversibleProxy._target_gced, target_ref),
|
||||
)
|
||||
ReversibleProxy._proxy_objects[target_ref] = proxy_ref
|
||||
|
||||
return target
|
||||
|
||||
@classmethod
|
||||
def _target_gced(cls, ref, proxy_ref=None):
|
||||
cls._proxy_objects.pop(ref, None)
|
||||
|
||||
@classmethod
|
||||
def _regenerate_proxy_for_target(cls, target):
|
||||
raise NotImplementedError()
|
||||
|
||||
@classmethod
|
||||
def _retrieve_proxy_for_target(cls, target, regenerate=True):
|
||||
try:
|
||||
proxy_ref = cls._proxy_objects[weakref.ref(target)]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
proxy = proxy_ref()
|
||||
if proxy is not None:
|
||||
return proxy
|
||||
|
||||
if regenerate:
|
||||
return cls._regenerate_proxy_for_target(target)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class StartableContext(abc.ABC):
|
||||
__slots__ = ()
|
||||
|
||||
@abc.abstractmethod
|
||||
async def start(self, is_ctxmanager=False):
|
||||
pass
|
||||
|
||||
def __await__(self):
|
||||
return self.start().__await__()
|
||||
|
||||
async def __aenter__(self):
|
||||
return await self.start(is_ctxmanager=True)
|
||||
|
||||
@abc.abstractmethod
|
||||
async def __aexit__(self, type_, value, traceback):
|
||||
pass
|
||||
|
||||
def _raise_for_not_started(self):
|
||||
raise async_exc.AsyncContextNotStarted(
|
||||
"%s context has not been started and object has not been awaited."
|
||||
% (self.__class__.__name__)
|
||||
)
|
||||
|
||||
|
||||
class ProxyComparable(ReversibleProxy):
|
||||
__slots__ = ()
|
||||
|
||||
def __hash__(self):
|
||||
return id(self)
|
||||
|
||||
def __eq__(self, other):
|
||||
return (
|
||||
isinstance(other, self.__class__)
|
||||
and self._proxied == other._proxied
|
||||
)
|
||||
|
||||
def __ne__(self, other):
|
||||
return (
|
||||
not isinstance(other, self.__class__)
|
||||
or self._proxied != other._proxied
|
||||
)
|
||||
828
lib/sqlalchemy/ext/asyncio/engine.py
Normal file
828
lib/sqlalchemy/ext/asyncio/engine.py
Normal file
@@ -0,0 +1,828 @@
|
||||
# ext/asyncio/engine.py
|
||||
# Copyright (C) 2020-2022 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: https://www.opensource.org/licenses/mit-license.php
|
||||
import asyncio
|
||||
|
||||
from . import exc as async_exc
|
||||
from .base import ProxyComparable
|
||||
from .base import StartableContext
|
||||
from .result import _ensure_sync_result
|
||||
from .result import AsyncResult
|
||||
from ... import exc
|
||||
from ... import inspection
|
||||
from ... import util
|
||||
from ...engine import create_engine as _create_engine
|
||||
from ...engine.base import NestedTransaction
|
||||
from ...future import Connection
|
||||
from ...future import Engine
|
||||
from ...util.concurrency import greenlet_spawn
|
||||
|
||||
|
||||
def create_async_engine(*arg, **kw):
|
||||
"""Create a new async engine instance.
|
||||
|
||||
Arguments passed to :func:`_asyncio.create_async_engine` are mostly
|
||||
identical to those passed to the :func:`_sa.create_engine` function.
|
||||
The specified dialect must be an asyncio-compatible dialect
|
||||
such as :ref:`dialect-postgresql-asyncpg`.
|
||||
|
||||
.. versionadded:: 1.4
|
||||
|
||||
"""
|
||||
|
||||
if kw.get("server_side_cursors", False):
|
||||
raise async_exc.AsyncMethodRequired(
|
||||
"Can't set server_side_cursors for async engine globally; "
|
||||
"use the connection.stream() method for an async "
|
||||
"streaming result set"
|
||||
)
|
||||
kw["future"] = True
|
||||
sync_engine = _create_engine(*arg, **kw)
|
||||
return AsyncEngine(sync_engine)
|
||||
|
||||
|
||||
def async_engine_from_config(configuration, prefix="sqlalchemy.", **kwargs):
|
||||
"""Create a new AsyncEngine instance using a configuration dictionary.
|
||||
|
||||
This function is analogous to the :func:`_sa.engine_from_config` function
|
||||
in SQLAlchemy Core, except that the requested dialect must be an
|
||||
asyncio-compatible dialect such as :ref:`dialect-postgresql-asyncpg`.
|
||||
The argument signature of the function is identical to that
|
||||
of :func:`_sa.engine_from_config`.
|
||||
|
||||
.. versionadded:: 1.4.29
|
||||
|
||||
"""
|
||||
options = {
|
||||
key[len(prefix) :]: value
|
||||
for key, value in configuration.items()
|
||||
if key.startswith(prefix)
|
||||
}
|
||||
options["_coerce_config"] = True
|
||||
options.update(kwargs)
|
||||
url = options.pop("url")
|
||||
return create_async_engine(url, **options)
|
||||
|
||||
|
||||
class AsyncConnectable:
|
||||
__slots__ = "_slots_dispatch", "__weakref__"
|
||||
|
||||
|
||||
@util.create_proxy_methods(
|
||||
Connection,
|
||||
":class:`_future.Connection`",
|
||||
":class:`_asyncio.AsyncConnection`",
|
||||
classmethods=[],
|
||||
methods=[],
|
||||
attributes=[
|
||||
"closed",
|
||||
"invalidated",
|
||||
"dialect",
|
||||
"default_isolation_level",
|
||||
],
|
||||
)
|
||||
class AsyncConnection(ProxyComparable, StartableContext, AsyncConnectable):
|
||||
"""An asyncio proxy for a :class:`_engine.Connection`.
|
||||
|
||||
:class:`_asyncio.AsyncConnection` is acquired using the
|
||||
:meth:`_asyncio.AsyncEngine.connect`
|
||||
method of :class:`_asyncio.AsyncEngine`::
|
||||
|
||||
from sqlalchemy.ext.asyncio import create_async_engine
|
||||
engine = create_async_engine("postgresql+asyncpg://user:pass@host/dbname")
|
||||
|
||||
async with engine.connect() as conn:
|
||||
result = await conn.execute(select(table))
|
||||
|
||||
.. versionadded:: 1.4
|
||||
|
||||
""" # noqa
|
||||
|
||||
# AsyncConnection is a thin proxy; no state should be added here
|
||||
# that is not retrievable from the "sync" engine / connection, e.g.
|
||||
# current transaction, info, etc. It should be possible to
|
||||
# create a new AsyncConnection that matches this one given only the
|
||||
# "sync" elements.
|
||||
__slots__ = (
|
||||
"engine",
|
||||
"sync_engine",
|
||||
"sync_connection",
|
||||
)
|
||||
|
||||
def __init__(self, async_engine, sync_connection=None):
|
||||
self.engine = async_engine
|
||||
self.sync_engine = async_engine.sync_engine
|
||||
self.sync_connection = self._assign_proxied(sync_connection)
|
||||
|
||||
sync_connection: Connection
|
||||
"""Reference to the sync-style :class:`_engine.Connection` this
|
||||
:class:`_asyncio.AsyncConnection` proxies requests towards.
|
||||
|
||||
This instance can be used as an event target.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`asyncio_events`
|
||||
"""
|
||||
|
||||
sync_engine: Engine
|
||||
"""Reference to the sync-style :class:`_engine.Engine` this
|
||||
:class:`_asyncio.AsyncConnection` is associated with via its underlying
|
||||
:class:`_engine.Connection`.
|
||||
|
||||
This instance can be used as an event target.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`asyncio_events`
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def _regenerate_proxy_for_target(cls, target):
|
||||
return AsyncConnection(
|
||||
AsyncEngine._retrieve_proxy_for_target(target.engine), target
|
||||
)
|
||||
|
||||
async def start(self, is_ctxmanager=False):
|
||||
"""Start this :class:`_asyncio.AsyncConnection` object's context
|
||||
outside of using a Python ``with:`` block.
|
||||
|
||||
"""
|
||||
if self.sync_connection:
|
||||
raise exc.InvalidRequestError("connection is already started")
|
||||
self.sync_connection = self._assign_proxied(
|
||||
await (greenlet_spawn(self.sync_engine.connect))
|
||||
)
|
||||
return self
|
||||
|
||||
@property
|
||||
def connection(self):
|
||||
"""Not implemented for async; call
|
||||
:meth:`_asyncio.AsyncConnection.get_raw_connection`.
|
||||
"""
|
||||
raise exc.InvalidRequestError(
|
||||
"AsyncConnection.connection accessor is not implemented as the "
|
||||
"attribute may need to reconnect on an invalidated connection. "
|
||||
"Use the get_raw_connection() method."
|
||||
)
|
||||
|
||||
async def get_raw_connection(self):
|
||||
"""Return the pooled DBAPI-level connection in use by this
|
||||
:class:`_asyncio.AsyncConnection`.
|
||||
|
||||
This is a SQLAlchemy connection-pool proxied connection
|
||||
which then has the attribute
|
||||
:attr:`_pool._ConnectionFairy.driver_connection` that refers to the
|
||||
actual driver connection. Its
|
||||
:attr:`_pool._ConnectionFairy.dbapi_connection` refers instead
|
||||
to an :class:`_engine.AdaptedConnection` instance that
|
||||
adapts the driver connection to the DBAPI protocol.
|
||||
|
||||
"""
|
||||
conn = self._sync_connection()
|
||||
|
||||
return await greenlet_spawn(getattr, conn, "connection")
|
||||
|
||||
@property
|
||||
def _proxied(self):
|
||||
return self.sync_connection
|
||||
|
||||
@property
|
||||
def info(self):
|
||||
"""Return the :attr:`_engine.Connection.info` dictionary of the
|
||||
underlying :class:`_engine.Connection`.
|
||||
|
||||
This dictionary is freely writable for user-defined state to be
|
||||
associated with the database connection.
|
||||
|
||||
This attribute is only available if the :class:`.AsyncConnection` is
|
||||
currently connected. If the :attr:`.AsyncConnection.closed` attribute
|
||||
is ``True``, then accessing this attribute will raise
|
||||
:class:`.ResourceClosedError`.
|
||||
|
||||
.. versionadded:: 1.4.0b2
|
||||
|
||||
"""
|
||||
return self.sync_connection.info
|
||||
|
||||
def _sync_connection(self):
|
||||
if not self.sync_connection:
|
||||
self._raise_for_not_started()
|
||||
return self.sync_connection
|
||||
|
||||
def begin(self):
|
||||
"""Begin a transaction prior to autobegin occurring."""
|
||||
self._sync_connection()
|
||||
return AsyncTransaction(self)
|
||||
|
||||
def begin_nested(self):
|
||||
"""Begin a nested transaction and return a transaction handle."""
|
||||
self._sync_connection()
|
||||
return AsyncTransaction(self, nested=True)
|
||||
|
||||
async def invalidate(self, exception=None):
|
||||
"""Invalidate the underlying DBAPI connection associated with
|
||||
this :class:`_engine.Connection`.
|
||||
|
||||
See the method :meth:`_engine.Connection.invalidate` for full
|
||||
detail on this method.
|
||||
|
||||
"""
|
||||
|
||||
conn = self._sync_connection()
|
||||
return await greenlet_spawn(conn.invalidate, exception=exception)
|
||||
|
||||
async def get_isolation_level(self):
|
||||
conn = self._sync_connection()
|
||||
return await greenlet_spawn(conn.get_isolation_level)
|
||||
|
||||
async def set_isolation_level(self):
|
||||
conn = self._sync_connection()
|
||||
return await greenlet_spawn(conn.get_isolation_level)
|
||||
|
||||
def in_transaction(self):
|
||||
"""Return True if a transaction is in progress.
|
||||
|
||||
.. versionadded:: 1.4.0b2
|
||||
|
||||
"""
|
||||
|
||||
conn = self._sync_connection()
|
||||
|
||||
return conn.in_transaction()
|
||||
|
||||
def in_nested_transaction(self):
|
||||
"""Return True if a transaction is in progress.
|
||||
|
||||
.. versionadded:: 1.4.0b2
|
||||
|
||||
"""
|
||||
conn = self._sync_connection()
|
||||
|
||||
return conn.in_nested_transaction()
|
||||
|
||||
def get_transaction(self):
|
||||
"""Return an :class:`.AsyncTransaction` representing the current
|
||||
transaction, if any.
|
||||
|
||||
This makes use of the underlying synchronous connection's
|
||||
:meth:`_engine.Connection.get_transaction` method to get the current
|
||||
:class:`_engine.Transaction`, which is then proxied in a new
|
||||
:class:`.AsyncTransaction` object.
|
||||
|
||||
.. versionadded:: 1.4.0b2
|
||||
|
||||
"""
|
||||
conn = self._sync_connection()
|
||||
|
||||
trans = conn.get_transaction()
|
||||
if trans is not None:
|
||||
return AsyncTransaction._retrieve_proxy_for_target(trans)
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_nested_transaction(self):
|
||||
"""Return an :class:`.AsyncTransaction` representing the current
|
||||
nested (savepoint) transaction, if any.
|
||||
|
||||
This makes use of the underlying synchronous connection's
|
||||
:meth:`_engine.Connection.get_nested_transaction` method to get the
|
||||
current :class:`_engine.Transaction`, which is then proxied in a new
|
||||
:class:`.AsyncTransaction` object.
|
||||
|
||||
.. versionadded:: 1.4.0b2
|
||||
|
||||
"""
|
||||
conn = self._sync_connection()
|
||||
|
||||
trans = conn.get_nested_transaction()
|
||||
if trans is not None:
|
||||
return AsyncTransaction._retrieve_proxy_for_target(trans)
|
||||
else:
|
||||
return None
|
||||
|
||||
async def execution_options(self, **opt):
|
||||
r"""Set non-SQL options for the connection which take effect
|
||||
during execution.
|
||||
|
||||
This returns this :class:`_asyncio.AsyncConnection` object with
|
||||
the new options added.
|
||||
|
||||
See :meth:`_future.Connection.execution_options` for full details
|
||||
on this method.
|
||||
|
||||
"""
|
||||
|
||||
conn = self._sync_connection()
|
||||
c2 = await greenlet_spawn(conn.execution_options, **opt)
|
||||
assert c2 is conn
|
||||
return self
|
||||
|
||||
async def commit(self):
|
||||
"""Commit the transaction that is currently in progress.
|
||||
|
||||
This method commits the current transaction if one has been started.
|
||||
If no transaction was started, the method has no effect, assuming
|
||||
the connection is in a non-invalidated state.
|
||||
|
||||
A transaction is begun on a :class:`_future.Connection` automatically
|
||||
whenever a statement is first executed, or when the
|
||||
:meth:`_future.Connection.begin` method is called.
|
||||
|
||||
"""
|
||||
conn = self._sync_connection()
|
||||
await greenlet_spawn(conn.commit)
|
||||
|
||||
async def rollback(self):
|
||||
"""Roll back the transaction that is currently in progress.
|
||||
|
||||
This method rolls back the current transaction if one has been started.
|
||||
If no transaction was started, the method has no effect. If a
|
||||
transaction was started and the connection is in an invalidated state,
|
||||
the transaction is cleared using this method.
|
||||
|
||||
A transaction is begun on a :class:`_future.Connection` automatically
|
||||
whenever a statement is first executed, or when the
|
||||
:meth:`_future.Connection.begin` method is called.
|
||||
|
||||
|
||||
"""
|
||||
conn = self._sync_connection()
|
||||
await greenlet_spawn(conn.rollback)
|
||||
|
||||
async def close(self):
|
||||
"""Close this :class:`_asyncio.AsyncConnection`.
|
||||
|
||||
This has the effect of also rolling back the transaction if one
|
||||
is in place.
|
||||
|
||||
"""
|
||||
conn = self._sync_connection()
|
||||
await greenlet_spawn(conn.close)
|
||||
|
||||
async def exec_driver_sql(
|
||||
self,
|
||||
statement,
|
||||
parameters=None,
|
||||
execution_options=util.EMPTY_DICT,
|
||||
):
|
||||
r"""Executes a driver-level SQL string and return buffered
|
||||
:class:`_engine.Result`.
|
||||
|
||||
"""
|
||||
|
||||
conn = self._sync_connection()
|
||||
|
||||
result = await greenlet_spawn(
|
||||
conn.exec_driver_sql,
|
||||
statement,
|
||||
parameters,
|
||||
execution_options,
|
||||
_require_await=True,
|
||||
)
|
||||
|
||||
return await _ensure_sync_result(result, self.exec_driver_sql)
|
||||
|
||||
async def stream(
|
||||
self,
|
||||
statement,
|
||||
parameters=None,
|
||||
execution_options=util.EMPTY_DICT,
|
||||
):
|
||||
"""Execute a statement and return a streaming
|
||||
:class:`_asyncio.AsyncResult` object."""
|
||||
|
||||
conn = self._sync_connection()
|
||||
|
||||
result = await greenlet_spawn(
|
||||
conn._execute_20,
|
||||
statement,
|
||||
parameters,
|
||||
util.EMPTY_DICT.merge_with(
|
||||
execution_options, {"stream_results": True}
|
||||
),
|
||||
_require_await=True,
|
||||
)
|
||||
if not result.context._is_server_side:
|
||||
# TODO: real exception here
|
||||
assert False, "server side result expected"
|
||||
return AsyncResult(result)
|
||||
|
||||
async def execute(
|
||||
self,
|
||||
statement,
|
||||
parameters=None,
|
||||
execution_options=util.EMPTY_DICT,
|
||||
):
|
||||
r"""Executes a SQL statement construct and return a buffered
|
||||
:class:`_engine.Result`.
|
||||
|
||||
:param object: The statement to be executed. This is always
|
||||
an object that is in both the :class:`_expression.ClauseElement` and
|
||||
:class:`_expression.Executable` hierarchies, including:
|
||||
|
||||
* :class:`_expression.Select`
|
||||
* :class:`_expression.Insert`, :class:`_expression.Update`,
|
||||
:class:`_expression.Delete`
|
||||
* :class:`_expression.TextClause` and
|
||||
:class:`_expression.TextualSelect`
|
||||
* :class:`_schema.DDL` and objects which inherit from
|
||||
:class:`_schema.DDLElement`
|
||||
|
||||
:param parameters: parameters which will be bound into the statement.
|
||||
This may be either a dictionary of parameter names to values,
|
||||
or a mutable sequence (e.g. a list) of dictionaries. When a
|
||||
list of dictionaries is passed, the underlying statement execution
|
||||
will make use of the DBAPI ``cursor.executemany()`` method.
|
||||
When a single dictionary is passed, the DBAPI ``cursor.execute()``
|
||||
method will be used.
|
||||
|
||||
:param execution_options: optional dictionary of execution options,
|
||||
which will be associated with the statement execution. This
|
||||
dictionary can provide a subset of the options that are accepted
|
||||
by :meth:`_future.Connection.execution_options`.
|
||||
|
||||
:return: a :class:`_engine.Result` object.
|
||||
|
||||
"""
|
||||
conn = self._sync_connection()
|
||||
|
||||
result = await greenlet_spawn(
|
||||
conn._execute_20,
|
||||
statement,
|
||||
parameters,
|
||||
execution_options,
|
||||
_require_await=True,
|
||||
)
|
||||
return await _ensure_sync_result(result, self.execute)
|
||||
|
||||
async def scalar(
|
||||
self,
|
||||
statement,
|
||||
parameters=None,
|
||||
execution_options=util.EMPTY_DICT,
|
||||
):
|
||||
r"""Executes a SQL statement construct and returns a scalar object.
|
||||
|
||||
This method is shorthand for invoking the
|
||||
:meth:`_engine.Result.scalar` method after invoking the
|
||||
:meth:`_future.Connection.execute` method. Parameters are equivalent.
|
||||
|
||||
:return: a scalar Python value representing the first column of the
|
||||
first row returned.
|
||||
|
||||
"""
|
||||
result = await self.execute(statement, parameters, execution_options)
|
||||
return result.scalar()
|
||||
|
||||
async def scalars(
|
||||
self,
|
||||
statement,
|
||||
parameters=None,
|
||||
execution_options=util.EMPTY_DICT,
|
||||
):
|
||||
r"""Executes a SQL statement construct and returns a scalar objects.
|
||||
|
||||
This method is shorthand for invoking the
|
||||
:meth:`_engine.Result.scalars` method after invoking the
|
||||
:meth:`_future.Connection.execute` method. Parameters are equivalent.
|
||||
|
||||
:return: a :class:`_engine.ScalarResult` object.
|
||||
|
||||
.. versionadded:: 1.4.24
|
||||
|
||||
"""
|
||||
result = await self.execute(statement, parameters, execution_options)
|
||||
return result.scalars()
|
||||
|
||||
async def stream_scalars(
|
||||
self,
|
||||
statement,
|
||||
parameters=None,
|
||||
execution_options=util.EMPTY_DICT,
|
||||
):
|
||||
r"""Executes a SQL statement and returns a streaming scalar result
|
||||
object.
|
||||
|
||||
This method is shorthand for invoking the
|
||||
:meth:`_engine.AsyncResult.scalars` method after invoking the
|
||||
:meth:`_future.Connection.stream` method. Parameters are equivalent.
|
||||
|
||||
:return: an :class:`_asyncio.AsyncScalarResult` object.
|
||||
|
||||
.. versionadded:: 1.4.24
|
||||
|
||||
"""
|
||||
result = await self.stream(statement, parameters, execution_options)
|
||||
return result.scalars()
|
||||
|
||||
async def run_sync(self, fn, *arg, **kw):
|
||||
"""Invoke the given sync callable passing self as the first argument.
|
||||
|
||||
This method maintains the asyncio event loop all the way through
|
||||
to the database connection by running the given callable in a
|
||||
specially instrumented greenlet.
|
||||
|
||||
E.g.::
|
||||
|
||||
with async_engine.begin() as conn:
|
||||
await conn.run_sync(metadata.create_all)
|
||||
|
||||
.. note::
|
||||
|
||||
The provided callable is invoked inline within the asyncio event
|
||||
loop, and will block on traditional IO calls. IO within this
|
||||
callable should only call into SQLAlchemy's asyncio database
|
||||
APIs which will be properly adapted to the greenlet context.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`session_run_sync`
|
||||
"""
|
||||
|
||||
conn = self._sync_connection()
|
||||
|
||||
return await greenlet_spawn(fn, conn, *arg, **kw)
|
||||
|
||||
def __await__(self):
|
||||
return self.start().__await__()
|
||||
|
||||
async def __aexit__(self, type_, value, traceback):
|
||||
await asyncio.shield(self.close())
|
||||
|
||||
|
||||
@util.create_proxy_methods(
|
||||
Engine,
|
||||
":class:`_future.Engine`",
|
||||
":class:`_asyncio.AsyncEngine`",
|
||||
classmethods=[],
|
||||
methods=[
|
||||
"clear_compiled_cache",
|
||||
"update_execution_options",
|
||||
"get_execution_options",
|
||||
],
|
||||
attributes=["url", "pool", "dialect", "engine", "name", "driver", "echo"],
|
||||
)
|
||||
class AsyncEngine(ProxyComparable, AsyncConnectable):
|
||||
"""An asyncio proxy for a :class:`_engine.Engine`.
|
||||
|
||||
:class:`_asyncio.AsyncEngine` is acquired using the
|
||||
:func:`_asyncio.create_async_engine` function::
|
||||
|
||||
from sqlalchemy.ext.asyncio import create_async_engine
|
||||
engine = create_async_engine("postgresql+asyncpg://user:pass@host/dbname")
|
||||
|
||||
.. versionadded:: 1.4
|
||||
|
||||
""" # noqa
|
||||
|
||||
# AsyncEngine is a thin proxy; no state should be added here
|
||||
# that is not retrievable from the "sync" engine / connection, e.g.
|
||||
# current transaction, info, etc. It should be possible to
|
||||
# create a new AsyncEngine that matches this one given only the
|
||||
# "sync" elements.
|
||||
__slots__ = ("sync_engine", "_proxied")
|
||||
|
||||
_connection_cls = AsyncConnection
|
||||
|
||||
_option_cls: type
|
||||
|
||||
class _trans_ctx(StartableContext):
|
||||
def __init__(self, conn):
|
||||
self.conn = conn
|
||||
|
||||
async def start(self, is_ctxmanager=False):
|
||||
await self.conn.start(is_ctxmanager=is_ctxmanager)
|
||||
self.transaction = self.conn.begin()
|
||||
await self.transaction.__aenter__()
|
||||
|
||||
return self.conn
|
||||
|
||||
async def __aexit__(self, type_, value, traceback):
|
||||
async def go():
|
||||
await self.transaction.__aexit__(type_, value, traceback)
|
||||
await self.conn.close()
|
||||
|
||||
await asyncio.shield(go())
|
||||
|
||||
def __init__(self, sync_engine):
|
||||
if not sync_engine.dialect.is_async:
|
||||
raise exc.InvalidRequestError(
|
||||
"The asyncio extension requires an async driver to be used. "
|
||||
f"The loaded {sync_engine.dialect.driver!r} is not async."
|
||||
)
|
||||
self.sync_engine = self._proxied = self._assign_proxied(sync_engine)
|
||||
|
||||
sync_engine: Engine
|
||||
"""Reference to the sync-style :class:`_engine.Engine` this
|
||||
:class:`_asyncio.AsyncEngine` proxies requests towards.
|
||||
|
||||
This instance can be used as an event target.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`asyncio_events`
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def _regenerate_proxy_for_target(cls, target):
|
||||
return AsyncEngine(target)
|
||||
|
||||
def begin(self):
|
||||
"""Return a context manager which when entered will deliver an
|
||||
:class:`_asyncio.AsyncConnection` with an
|
||||
:class:`_asyncio.AsyncTransaction` established.
|
||||
|
||||
E.g.::
|
||||
|
||||
async with async_engine.begin() as conn:
|
||||
await conn.execute(
|
||||
text("insert into table (x, y, z) values (1, 2, 3)")
|
||||
)
|
||||
await conn.execute(text("my_special_procedure(5)"))
|
||||
|
||||
|
||||
"""
|
||||
conn = self.connect()
|
||||
return self._trans_ctx(conn)
|
||||
|
||||
def connect(self):
|
||||
"""Return an :class:`_asyncio.AsyncConnection` object.
|
||||
|
||||
The :class:`_asyncio.AsyncConnection` will procure a database
|
||||
connection from the underlying connection pool when it is entered
|
||||
as an async context manager::
|
||||
|
||||
async with async_engine.connect() as conn:
|
||||
result = await conn.execute(select(user_table))
|
||||
|
||||
The :class:`_asyncio.AsyncConnection` may also be started outside of a
|
||||
context manager by invoking its :meth:`_asyncio.AsyncConnection.start`
|
||||
method.
|
||||
|
||||
"""
|
||||
|
||||
return self._connection_cls(self)
|
||||
|
||||
async def raw_connection(self):
|
||||
"""Return a "raw" DBAPI connection from the connection pool.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`dbapi_connections`
|
||||
|
||||
"""
|
||||
return await greenlet_spawn(self.sync_engine.raw_connection)
|
||||
|
||||
def execution_options(self, **opt):
|
||||
"""Return a new :class:`_asyncio.AsyncEngine` that will provide
|
||||
:class:`_asyncio.AsyncConnection` objects with the given execution
|
||||
options.
|
||||
|
||||
Proxied from :meth:`_future.Engine.execution_options`. See that
|
||||
method for details.
|
||||
|
||||
"""
|
||||
|
||||
return AsyncEngine(self.sync_engine.execution_options(**opt))
|
||||
|
||||
async def dispose(self):
|
||||
"""Dispose of the connection pool used by this
|
||||
:class:`_asyncio.AsyncEngine`.
|
||||
|
||||
This will close all connection pool connections that are
|
||||
**currently checked in**. See the documentation for the underlying
|
||||
:meth:`_future.Engine.dispose` method for further notes.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`_future.Engine.dispose`
|
||||
|
||||
"""
|
||||
|
||||
await greenlet_spawn(self.sync_engine.dispose)
|
||||
|
||||
|
||||
class AsyncTransaction(ProxyComparable, StartableContext):
|
||||
"""An asyncio proxy for a :class:`_engine.Transaction`."""
|
||||
|
||||
__slots__ = ("connection", "sync_transaction", "nested")
|
||||
|
||||
def __init__(self, connection, nested=False):
|
||||
self.connection = connection # AsyncConnection
|
||||
self.sync_transaction = None # sqlalchemy.engine.Transaction
|
||||
self.nested = nested
|
||||
|
||||
@classmethod
|
||||
def _regenerate_proxy_for_target(cls, target):
|
||||
sync_connection = target.connection
|
||||
sync_transaction = target
|
||||
nested = isinstance(target, NestedTransaction)
|
||||
|
||||
async_connection = AsyncConnection._retrieve_proxy_for_target(
|
||||
sync_connection
|
||||
)
|
||||
assert async_connection is not None
|
||||
|
||||
obj = cls.__new__(cls)
|
||||
obj.connection = async_connection
|
||||
obj.sync_transaction = obj._assign_proxied(sync_transaction)
|
||||
obj.nested = nested
|
||||
return obj
|
||||
|
||||
def _sync_transaction(self):
|
||||
if not self.sync_transaction:
|
||||
self._raise_for_not_started()
|
||||
return self.sync_transaction
|
||||
|
||||
@property
|
||||
def _proxied(self):
|
||||
return self.sync_transaction
|
||||
|
||||
@property
|
||||
def is_valid(self):
|
||||
return self._sync_transaction().is_valid
|
||||
|
||||
@property
|
||||
def is_active(self):
|
||||
return self._sync_transaction().is_active
|
||||
|
||||
async def close(self):
|
||||
"""Close this :class:`.Transaction`.
|
||||
|
||||
If this transaction is the base transaction in a begin/commit
|
||||
nesting, the transaction will rollback(). Otherwise, the
|
||||
method returns.
|
||||
|
||||
This is used to cancel a Transaction without affecting the scope of
|
||||
an enclosing transaction.
|
||||
|
||||
"""
|
||||
await greenlet_spawn(self._sync_transaction().close)
|
||||
|
||||
async def rollback(self):
|
||||
"""Roll back this :class:`.Transaction`."""
|
||||
await greenlet_spawn(self._sync_transaction().rollback)
|
||||
|
||||
async def commit(self):
|
||||
"""Commit this :class:`.Transaction`."""
|
||||
|
||||
await greenlet_spawn(self._sync_transaction().commit)
|
||||
|
||||
async def start(self, is_ctxmanager=False):
|
||||
"""Start this :class:`_asyncio.AsyncTransaction` object's context
|
||||
outside of using a Python ``with:`` block.
|
||||
|
||||
"""
|
||||
|
||||
self.sync_transaction = self._assign_proxied(
|
||||
await greenlet_spawn(
|
||||
self.connection._sync_connection().begin_nested
|
||||
if self.nested
|
||||
else self.connection._sync_connection().begin
|
||||
)
|
||||
)
|
||||
if is_ctxmanager:
|
||||
self.sync_transaction.__enter__()
|
||||
return self
|
||||
|
||||
async def __aexit__(self, type_, value, traceback):
|
||||
await greenlet_spawn(
|
||||
self._sync_transaction().__exit__, type_, value, traceback
|
||||
)
|
||||
|
||||
|
||||
def _get_sync_engine_or_connection(async_engine):
|
||||
if isinstance(async_engine, AsyncConnection):
|
||||
return async_engine.sync_connection
|
||||
|
||||
try:
|
||||
return async_engine.sync_engine
|
||||
except AttributeError as e:
|
||||
raise exc.ArgumentError(
|
||||
"AsyncEngine expected, got %r" % async_engine
|
||||
) from e
|
||||
|
||||
|
||||
@inspection._inspects(AsyncConnection)
|
||||
def _no_insp_for_async_conn_yet(subject):
|
||||
raise exc.NoInspectionAvailable(
|
||||
"Inspection on an AsyncConnection is currently not supported. "
|
||||
"Please use ``run_sync`` to pass a callable where it's possible "
|
||||
"to call ``inspect`` on the passed connection.",
|
||||
code="xd3s",
|
||||
)
|
||||
|
||||
|
||||
@inspection._inspects(AsyncEngine)
|
||||
def _no_insp_for_async_engine_xyet(subject):
|
||||
raise exc.NoInspectionAvailable(
|
||||
"Inspection on an AsyncEngine is currently not supported. "
|
||||
"Please obtain a connection then use ``conn.run_sync`` to pass a "
|
||||
"callable where it's possible to call ``inspect`` on the "
|
||||
"passed connection.",
|
||||
code="xd3s",
|
||||
)
|
||||
44
lib/sqlalchemy/ext/asyncio/events.py
Normal file
44
lib/sqlalchemy/ext/asyncio/events.py
Normal file
@@ -0,0 +1,44 @@
|
||||
# ext/asyncio/events.py
|
||||
# Copyright (C) 2020-2022 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: https://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from .engine import AsyncConnectable
|
||||
from .session import AsyncSession
|
||||
from ...engine import events as engine_event
|
||||
from ...orm import events as orm_event
|
||||
|
||||
|
||||
class AsyncConnectionEvents(engine_event.ConnectionEvents):
|
||||
_target_class_doc = "SomeEngine"
|
||||
_dispatch_target = AsyncConnectable
|
||||
|
||||
@classmethod
|
||||
def _no_async_engine_events(cls):
|
||||
raise NotImplementedError(
|
||||
"asynchronous events are not implemented at this time. Apply "
|
||||
"synchronous listeners to the AsyncEngine.sync_engine or "
|
||||
"AsyncConnection.sync_connection attributes."
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _listen(cls, event_key, retval=False):
|
||||
cls._no_async_engine_events()
|
||||
|
||||
|
||||
class AsyncSessionEvents(orm_event.SessionEvents):
|
||||
_target_class_doc = "SomeSession"
|
||||
_dispatch_target = AsyncSession
|
||||
|
||||
@classmethod
|
||||
def _no_async_engine_events(cls):
|
||||
raise NotImplementedError(
|
||||
"asynchronous events are not implemented at this time. Apply "
|
||||
"synchronous listeners to the AsyncSession.sync_session."
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _listen(cls, event_key, retval=False):
|
||||
cls._no_async_engine_events()
|
||||
21
lib/sqlalchemy/ext/asyncio/exc.py
Normal file
21
lib/sqlalchemy/ext/asyncio/exc.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# ext/asyncio/exc.py
|
||||
# Copyright (C) 2020-2022 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: https://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from ... import exc
|
||||
|
||||
|
||||
class AsyncMethodRequired(exc.InvalidRequestError):
|
||||
"""an API can't be used because its result would not be
|
||||
compatible with async"""
|
||||
|
||||
|
||||
class AsyncContextNotStarted(exc.InvalidRequestError):
|
||||
"""a startable context manager has not been started."""
|
||||
|
||||
|
||||
class AsyncContextAlreadyStarted(exc.InvalidRequestError):
|
||||
"""a startable context manager is already started."""
|
||||
671
lib/sqlalchemy/ext/asyncio/result.py
Normal file
671
lib/sqlalchemy/ext/asyncio/result.py
Normal file
@@ -0,0 +1,671 @@
|
||||
# ext/asyncio/result.py
|
||||
# Copyright (C) 2020-2022 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: https://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
import operator
|
||||
|
||||
from . import exc as async_exc
|
||||
from ...engine.result import _NO_ROW
|
||||
from ...engine.result import FilterResult
|
||||
from ...engine.result import FrozenResult
|
||||
from ...engine.result import MergedResult
|
||||
from ...sql.base import _generative
|
||||
from ...util.concurrency import greenlet_spawn
|
||||
|
||||
|
||||
class AsyncCommon(FilterResult):
|
||||
async def close(self):
|
||||
"""Close this result."""
|
||||
|
||||
await greenlet_spawn(self._real_result.close)
|
||||
|
||||
|
||||
class AsyncResult(AsyncCommon):
|
||||
"""An asyncio wrapper around a :class:`_result.Result` object.
|
||||
|
||||
The :class:`_asyncio.AsyncResult` only applies to statement executions that
|
||||
use a server-side cursor. It is returned only from the
|
||||
:meth:`_asyncio.AsyncConnection.stream` and
|
||||
:meth:`_asyncio.AsyncSession.stream` methods.
|
||||
|
||||
.. note:: As is the case with :class:`_engine.Result`, this object is
|
||||
used for ORM results returned by :meth:`_asyncio.AsyncSession.execute`,
|
||||
which can yield instances of ORM mapped objects either individually or
|
||||
within tuple-like rows. Note that these result objects do not
|
||||
deduplicate instances or rows automatically as is the case with the
|
||||
legacy :class:`_orm.Query` object. For in-Python de-duplication of
|
||||
instances or rows, use the :meth:`_asyncio.AsyncResult.unique` modifier
|
||||
method.
|
||||
|
||||
.. versionadded:: 1.4
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, real_result):
|
||||
self._real_result = real_result
|
||||
|
||||
self._metadata = real_result._metadata
|
||||
self._unique_filter_state = real_result._unique_filter_state
|
||||
|
||||
# BaseCursorResult pre-generates the "_row_getter". Use that
|
||||
# if available rather than building a second one
|
||||
if "_row_getter" in real_result.__dict__:
|
||||
self._set_memoized_attribute(
|
||||
"_row_getter", real_result.__dict__["_row_getter"]
|
||||
)
|
||||
|
||||
def keys(self):
|
||||
"""Return the :meth:`_engine.Result.keys` collection from the
|
||||
underlying :class:`_engine.Result`.
|
||||
|
||||
"""
|
||||
return self._metadata.keys
|
||||
|
||||
@_generative
|
||||
def unique(self, strategy=None):
|
||||
"""Apply unique filtering to the objects returned by this
|
||||
:class:`_asyncio.AsyncResult`.
|
||||
|
||||
Refer to :meth:`_engine.Result.unique` in the synchronous
|
||||
SQLAlchemy API for a complete behavioral description.
|
||||
|
||||
|
||||
"""
|
||||
self._unique_filter_state = (set(), strategy)
|
||||
|
||||
def columns(self, *col_expressions):
|
||||
r"""Establish the columns that should be returned in each row.
|
||||
|
||||
Refer to :meth:`_engine.Result.columns` in the synchronous
|
||||
SQLAlchemy API for a complete behavioral description.
|
||||
|
||||
|
||||
"""
|
||||
return self._column_slices(col_expressions)
|
||||
|
||||
async def partitions(self, size=None):
|
||||
"""Iterate through sub-lists of rows of the size given.
|
||||
|
||||
An async iterator is returned::
|
||||
|
||||
async def scroll_results(connection):
|
||||
result = await connection.stream(select(users_table))
|
||||
|
||||
async for partition in result.partitions(100):
|
||||
print("list of rows: %s" % partition)
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`_engine.Result.partitions`
|
||||
|
||||
"""
|
||||
|
||||
getter = self._manyrow_getter
|
||||
|
||||
while True:
|
||||
partition = await greenlet_spawn(getter, self, size)
|
||||
if partition:
|
||||
yield partition
|
||||
else:
|
||||
break
|
||||
|
||||
async def fetchone(self):
|
||||
"""Fetch one row.
|
||||
|
||||
When all rows are exhausted, returns None.
|
||||
|
||||
This method is provided for backwards compatibility with
|
||||
SQLAlchemy 1.x.x.
|
||||
|
||||
To fetch the first row of a result only, use the
|
||||
:meth:`_engine.Result.first` method. To iterate through all
|
||||
rows, iterate the :class:`_engine.Result` object directly.
|
||||
|
||||
:return: a :class:`.Row` object if no filters are applied, or None
|
||||
if no rows remain.
|
||||
|
||||
"""
|
||||
row = await greenlet_spawn(self._onerow_getter, self)
|
||||
if row is _NO_ROW:
|
||||
return None
|
||||
else:
|
||||
return row
|
||||
|
||||
async def fetchmany(self, size=None):
|
||||
"""Fetch many rows.
|
||||
|
||||
When all rows are exhausted, returns an empty list.
|
||||
|
||||
This method is provided for backwards compatibility with
|
||||
SQLAlchemy 1.x.x.
|
||||
|
||||
To fetch rows in groups, use the
|
||||
:meth:`._asyncio.AsyncResult.partitions` method.
|
||||
|
||||
:return: a list of :class:`.Row` objects.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`_asyncio.AsyncResult.partitions`
|
||||
|
||||
"""
|
||||
|
||||
return await greenlet_spawn(self._manyrow_getter, self, size)
|
||||
|
||||
async def all(self):
|
||||
"""Return all rows in a list.
|
||||
|
||||
Closes the result set after invocation. Subsequent invocations
|
||||
will return an empty list.
|
||||
|
||||
:return: a list of :class:`.Row` objects.
|
||||
|
||||
"""
|
||||
|
||||
return await greenlet_spawn(self._allrows)
|
||||
|
||||
def __aiter__(self):
|
||||
return self
|
||||
|
||||
async def __anext__(self):
|
||||
row = await greenlet_spawn(self._onerow_getter, self)
|
||||
if row is _NO_ROW:
|
||||
raise StopAsyncIteration()
|
||||
else:
|
||||
return row
|
||||
|
||||
async def first(self):
|
||||
"""Fetch the first row or None if no row is present.
|
||||
|
||||
Closes the result set and discards remaining rows.
|
||||
|
||||
.. note:: This method returns one **row**, e.g. tuple, by default. To
|
||||
return exactly one single scalar value, that is, the first column of
|
||||
the first row, use the :meth:`_asyncio.AsyncResult.scalar` method,
|
||||
or combine :meth:`_asyncio.AsyncResult.scalars` and
|
||||
:meth:`_asyncio.AsyncResult.first`.
|
||||
|
||||
:return: a :class:`.Row` object, or None
|
||||
if no rows remain.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`_asyncio.AsyncResult.scalar`
|
||||
|
||||
:meth:`_asyncio.AsyncResult.one`
|
||||
|
||||
"""
|
||||
return await greenlet_spawn(self._only_one_row, False, False, False)
|
||||
|
||||
async def one_or_none(self):
|
||||
"""Return at most one result or raise an exception.
|
||||
|
||||
Returns ``None`` if the result has no rows.
|
||||
Raises :class:`.MultipleResultsFound`
|
||||
if multiple rows are returned.
|
||||
|
||||
.. versionadded:: 1.4
|
||||
|
||||
:return: The first :class:`.Row` or None if no row is available.
|
||||
|
||||
:raises: :class:`.MultipleResultsFound`
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`_asyncio.AsyncResult.first`
|
||||
|
||||
:meth:`_asyncio.AsyncResult.one`
|
||||
|
||||
"""
|
||||
return await greenlet_spawn(self._only_one_row, True, False, False)
|
||||
|
||||
async def scalar_one(self):
|
||||
"""Return exactly one scalar result or raise an exception.
|
||||
|
||||
This is equivalent to calling :meth:`_asyncio.AsyncResult.scalars` and
|
||||
then :meth:`_asyncio.AsyncResult.one`.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`_asyncio.AsyncResult.one`
|
||||
|
||||
:meth:`_asyncio.AsyncResult.scalars`
|
||||
|
||||
"""
|
||||
return await greenlet_spawn(self._only_one_row, True, True, True)
|
||||
|
||||
async def scalar_one_or_none(self):
|
||||
"""Return exactly one or no scalar result.
|
||||
|
||||
This is equivalent to calling :meth:`_asyncio.AsyncResult.scalars` and
|
||||
then :meth:`_asyncio.AsyncResult.one_or_none`.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`_asyncio.AsyncResult.one_or_none`
|
||||
|
||||
:meth:`_asyncio.AsyncResult.scalars`
|
||||
|
||||
"""
|
||||
return await greenlet_spawn(self._only_one_row, True, False, True)
|
||||
|
||||
async def one(self):
|
||||
"""Return exactly one row or raise an exception.
|
||||
|
||||
Raises :class:`.NoResultFound` if the result returns no
|
||||
rows, or :class:`.MultipleResultsFound` if multiple rows
|
||||
would be returned.
|
||||
|
||||
.. note:: This method returns one **row**, e.g. tuple, by default.
|
||||
To return exactly one single scalar value, that is, the first
|
||||
column of the first row, use the
|
||||
:meth:`_asyncio.AsyncResult.scalar_one` method, or combine
|
||||
:meth:`_asyncio.AsyncResult.scalars` and
|
||||
:meth:`_asyncio.AsyncResult.one`.
|
||||
|
||||
.. versionadded:: 1.4
|
||||
|
||||
:return: The first :class:`.Row`.
|
||||
|
||||
:raises: :class:`.MultipleResultsFound`, :class:`.NoResultFound`
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`_asyncio.AsyncResult.first`
|
||||
|
||||
:meth:`_asyncio.AsyncResult.one_or_none`
|
||||
|
||||
:meth:`_asyncio.AsyncResult.scalar_one`
|
||||
|
||||
"""
|
||||
return await greenlet_spawn(self._only_one_row, True, True, False)
|
||||
|
||||
async def scalar(self):
|
||||
"""Fetch the first column of the first row, and close the result set.
|
||||
|
||||
Returns None if there are no rows to fetch.
|
||||
|
||||
No validation is performed to test if additional rows remain.
|
||||
|
||||
After calling this method, the object is fully closed,
|
||||
e.g. the :meth:`_engine.CursorResult.close`
|
||||
method will have been called.
|
||||
|
||||
:return: a Python scalar value , or None if no rows remain.
|
||||
|
||||
"""
|
||||
return await greenlet_spawn(self._only_one_row, False, False, True)
|
||||
|
||||
async def freeze(self):
|
||||
"""Return a callable object that will produce copies of this
|
||||
:class:`_asyncio.AsyncResult` when invoked.
|
||||
|
||||
The callable object returned is an instance of
|
||||
:class:`_engine.FrozenResult`.
|
||||
|
||||
This is used for result set caching. The method must be called
|
||||
on the result when it has been unconsumed, and calling the method
|
||||
will consume the result fully. When the :class:`_engine.FrozenResult`
|
||||
is retrieved from a cache, it can be called any number of times where
|
||||
it will produce a new :class:`_engine.Result` object each time
|
||||
against its stored set of rows.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`do_orm_execute_re_executing` - example usage within the
|
||||
ORM to implement a result-set cache.
|
||||
|
||||
"""
|
||||
|
||||
return await greenlet_spawn(FrozenResult, self)
|
||||
|
||||
def merge(self, *others):
|
||||
"""Merge this :class:`_asyncio.AsyncResult` with other compatible
|
||||
result objects.
|
||||
|
||||
The object returned is an instance of :class:`_engine.MergedResult`,
|
||||
which will be composed of iterators from the given result
|
||||
objects.
|
||||
|
||||
The new result will use the metadata from this result object.
|
||||
The subsequent result objects must be against an identical
|
||||
set of result / cursor metadata, otherwise the behavior is
|
||||
undefined.
|
||||
|
||||
"""
|
||||
return MergedResult(self._metadata, (self,) + others)
|
||||
|
||||
def scalars(self, index=0):
|
||||
"""Return an :class:`_asyncio.AsyncScalarResult` filtering object which
|
||||
will return single elements rather than :class:`_row.Row` objects.
|
||||
|
||||
Refer to :meth:`_result.Result.scalars` in the synchronous
|
||||
SQLAlchemy API for a complete behavioral description.
|
||||
|
||||
:param index: integer or row key indicating the column to be fetched
|
||||
from each row, defaults to ``0`` indicating the first column.
|
||||
|
||||
:return: a new :class:`_asyncio.AsyncScalarResult` filtering object
|
||||
referring to this :class:`_asyncio.AsyncResult` object.
|
||||
|
||||
"""
|
||||
return AsyncScalarResult(self._real_result, index)
|
||||
|
||||
def mappings(self):
|
||||
"""Apply a mappings filter to returned rows, returning an instance of
|
||||
:class:`_asyncio.AsyncMappingResult`.
|
||||
|
||||
When this filter is applied, fetching rows will return
|
||||
:class:`.RowMapping` objects instead of :class:`.Row` objects.
|
||||
|
||||
Refer to :meth:`_result.Result.mappings` in the synchronous
|
||||
SQLAlchemy API for a complete behavioral description.
|
||||
|
||||
:return: a new :class:`_asyncio.AsyncMappingResult` filtering object
|
||||
referring to the underlying :class:`_result.Result` object.
|
||||
|
||||
"""
|
||||
|
||||
return AsyncMappingResult(self._real_result)
|
||||
|
||||
|
||||
class AsyncScalarResult(AsyncCommon):
|
||||
"""A wrapper for a :class:`_asyncio.AsyncResult` that returns scalar values
|
||||
rather than :class:`_row.Row` values.
|
||||
|
||||
The :class:`_asyncio.AsyncScalarResult` object is acquired by calling the
|
||||
:meth:`_asyncio.AsyncResult.scalars` method.
|
||||
|
||||
Refer to the :class:`_result.ScalarResult` object in the synchronous
|
||||
SQLAlchemy API for a complete behavioral description.
|
||||
|
||||
.. versionadded:: 1.4
|
||||
|
||||
"""
|
||||
|
||||
_generate_rows = False
|
||||
|
||||
def __init__(self, real_result, index):
|
||||
self._real_result = real_result
|
||||
|
||||
if real_result._source_supports_scalars:
|
||||
self._metadata = real_result._metadata
|
||||
self._post_creational_filter = None
|
||||
else:
|
||||
self._metadata = real_result._metadata._reduce([index])
|
||||
self._post_creational_filter = operator.itemgetter(0)
|
||||
|
||||
self._unique_filter_state = real_result._unique_filter_state
|
||||
|
||||
def unique(self, strategy=None):
|
||||
"""Apply unique filtering to the objects returned by this
|
||||
:class:`_asyncio.AsyncScalarResult`.
|
||||
|
||||
See :meth:`_asyncio.AsyncResult.unique` for usage details.
|
||||
|
||||
"""
|
||||
self._unique_filter_state = (set(), strategy)
|
||||
return self
|
||||
|
||||
async def partitions(self, size=None):
|
||||
"""Iterate through sub-lists of elements of the size given.
|
||||
|
||||
Equivalent to :meth:`_asyncio.AsyncResult.partitions` except that
|
||||
scalar values, rather than :class:`_result.Row` objects,
|
||||
are returned.
|
||||
|
||||
"""
|
||||
|
||||
getter = self._manyrow_getter
|
||||
|
||||
while True:
|
||||
partition = await greenlet_spawn(getter, self, size)
|
||||
if partition:
|
||||
yield partition
|
||||
else:
|
||||
break
|
||||
|
||||
async def fetchall(self):
|
||||
"""A synonym for the :meth:`_asyncio.AsyncScalarResult.all` method."""
|
||||
|
||||
return await greenlet_spawn(self._allrows)
|
||||
|
||||
async def fetchmany(self, size=None):
|
||||
"""Fetch many objects.
|
||||
|
||||
Equivalent to :meth:`_asyncio.AsyncResult.fetchmany` except that
|
||||
scalar values, rather than :class:`_result.Row` objects,
|
||||
are returned.
|
||||
|
||||
"""
|
||||
return await greenlet_spawn(self._manyrow_getter, self, size)
|
||||
|
||||
async def all(self):
|
||||
"""Return all scalar values in a list.
|
||||
|
||||
Equivalent to :meth:`_asyncio.AsyncResult.all` except that
|
||||
scalar values, rather than :class:`_result.Row` objects,
|
||||
are returned.
|
||||
|
||||
"""
|
||||
return await greenlet_spawn(self._allrows)
|
||||
|
||||
def __aiter__(self):
|
||||
return self
|
||||
|
||||
async def __anext__(self):
|
||||
row = await greenlet_spawn(self._onerow_getter, self)
|
||||
if row is _NO_ROW:
|
||||
raise StopAsyncIteration()
|
||||
else:
|
||||
return row
|
||||
|
||||
async def first(self):
|
||||
"""Fetch the first object or None if no object is present.
|
||||
|
||||
Equivalent to :meth:`_asyncio.AsyncResult.first` except that
|
||||
scalar values, rather than :class:`_result.Row` objects,
|
||||
are returned.
|
||||
|
||||
"""
|
||||
return await greenlet_spawn(self._only_one_row, False, False, False)
|
||||
|
||||
async def one_or_none(self):
|
||||
"""Return at most one object or raise an exception.
|
||||
|
||||
Equivalent to :meth:`_asyncio.AsyncResult.one_or_none` except that
|
||||
scalar values, rather than :class:`_result.Row` objects,
|
||||
are returned.
|
||||
|
||||
"""
|
||||
return await greenlet_spawn(self._only_one_row, True, False, False)
|
||||
|
||||
async def one(self):
|
||||
"""Return exactly one object or raise an exception.
|
||||
|
||||
Equivalent to :meth:`_asyncio.AsyncResult.one` except that
|
||||
scalar values, rather than :class:`_result.Row` objects,
|
||||
are returned.
|
||||
|
||||
"""
|
||||
return await greenlet_spawn(self._only_one_row, True, True, False)
|
||||
|
||||
|
||||
class AsyncMappingResult(AsyncCommon):
|
||||
"""A wrapper for a :class:`_asyncio.AsyncResult` that returns dictionary
|
||||
values rather than :class:`_engine.Row` values.
|
||||
|
||||
The :class:`_asyncio.AsyncMappingResult` object is acquired by calling the
|
||||
:meth:`_asyncio.AsyncResult.mappings` method.
|
||||
|
||||
Refer to the :class:`_result.MappingResult` object in the synchronous
|
||||
SQLAlchemy API for a complete behavioral description.
|
||||
|
||||
.. versionadded:: 1.4
|
||||
|
||||
"""
|
||||
|
||||
_generate_rows = True
|
||||
|
||||
_post_creational_filter = operator.attrgetter("_mapping")
|
||||
|
||||
def __init__(self, result):
|
||||
self._real_result = result
|
||||
self._unique_filter_state = result._unique_filter_state
|
||||
self._metadata = result._metadata
|
||||
if result._source_supports_scalars:
|
||||
self._metadata = self._metadata._reduce([0])
|
||||
|
||||
def keys(self):
|
||||
"""Return an iterable view which yields the string keys that would
|
||||
be represented by each :class:`.Row`.
|
||||
|
||||
The view also can be tested for key containment using the Python
|
||||
``in`` operator, which will test both for the string keys represented
|
||||
in the view, as well as for alternate keys such as column objects.
|
||||
|
||||
.. versionchanged:: 1.4 a key view object is returned rather than a
|
||||
plain list.
|
||||
|
||||
|
||||
"""
|
||||
return self._metadata.keys
|
||||
|
||||
def unique(self, strategy=None):
|
||||
"""Apply unique filtering to the objects returned by this
|
||||
:class:`_asyncio.AsyncMappingResult`.
|
||||
|
||||
See :meth:`_asyncio.AsyncResult.unique` for usage details.
|
||||
|
||||
"""
|
||||
self._unique_filter_state = (set(), strategy)
|
||||
return self
|
||||
|
||||
def columns(self, *col_expressions):
|
||||
r"""Establish the columns that should be returned in each row."""
|
||||
return self._column_slices(col_expressions)
|
||||
|
||||
async def partitions(self, size=None):
|
||||
"""Iterate through sub-lists of elements of the size given.
|
||||
|
||||
Equivalent to :meth:`_asyncio.AsyncResult.partitions` except that
|
||||
mapping values, rather than :class:`_result.Row` objects,
|
||||
are returned.
|
||||
|
||||
"""
|
||||
|
||||
getter = self._manyrow_getter
|
||||
|
||||
while True:
|
||||
partition = await greenlet_spawn(getter, self, size)
|
||||
if partition:
|
||||
yield partition
|
||||
else:
|
||||
break
|
||||
|
||||
async def fetchall(self):
|
||||
"""A synonym for the :meth:`_asyncio.AsyncMappingResult.all` method."""
|
||||
|
||||
return await greenlet_spawn(self._allrows)
|
||||
|
||||
async def fetchone(self):
|
||||
"""Fetch one object.
|
||||
|
||||
Equivalent to :meth:`_asyncio.AsyncResult.fetchone` except that
|
||||
mapping values, rather than :class:`_result.Row` objects,
|
||||
are returned.
|
||||
|
||||
"""
|
||||
|
||||
row = await greenlet_spawn(self._onerow_getter, self)
|
||||
if row is _NO_ROW:
|
||||
return None
|
||||
else:
|
||||
return row
|
||||
|
||||
async def fetchmany(self, size=None):
|
||||
"""Fetch many objects.
|
||||
|
||||
Equivalent to :meth:`_asyncio.AsyncResult.fetchmany` except that
|
||||
mapping values, rather than :class:`_result.Row` objects,
|
||||
are returned.
|
||||
|
||||
"""
|
||||
|
||||
return await greenlet_spawn(self._manyrow_getter, self, size)
|
||||
|
||||
async def all(self):
|
||||
"""Return all scalar values in a list.
|
||||
|
||||
Equivalent to :meth:`_asyncio.AsyncResult.all` except that
|
||||
mapping values, rather than :class:`_result.Row` objects,
|
||||
are returned.
|
||||
|
||||
"""
|
||||
|
||||
return await greenlet_spawn(self._allrows)
|
||||
|
||||
def __aiter__(self):
|
||||
return self
|
||||
|
||||
async def __anext__(self):
|
||||
row = await greenlet_spawn(self._onerow_getter, self)
|
||||
if row is _NO_ROW:
|
||||
raise StopAsyncIteration()
|
||||
else:
|
||||
return row
|
||||
|
||||
async def first(self):
|
||||
"""Fetch the first object or None if no object is present.
|
||||
|
||||
Equivalent to :meth:`_asyncio.AsyncResult.first` except that
|
||||
mapping values, rather than :class:`_result.Row` objects,
|
||||
are returned.
|
||||
|
||||
|
||||
"""
|
||||
return await greenlet_spawn(self._only_one_row, False, False, False)
|
||||
|
||||
async def one_or_none(self):
|
||||
"""Return at most one object or raise an exception.
|
||||
|
||||
Equivalent to :meth:`_asyncio.AsyncResult.one_or_none` except that
|
||||
mapping values, rather than :class:`_result.Row` objects,
|
||||
are returned.
|
||||
|
||||
"""
|
||||
return await greenlet_spawn(self._only_one_row, True, False, False)
|
||||
|
||||
async def one(self):
|
||||
"""Return exactly one object or raise an exception.
|
||||
|
||||
Equivalent to :meth:`_asyncio.AsyncResult.one` except that
|
||||
mapping values, rather than :class:`_result.Row` objects,
|
||||
are returned.
|
||||
|
||||
"""
|
||||
return await greenlet_spawn(self._only_one_row, True, True, False)
|
||||
|
||||
|
||||
async def _ensure_sync_result(result, calling_method):
|
||||
if not result._is_cursor:
|
||||
cursor_result = getattr(result, "raw", None)
|
||||
else:
|
||||
cursor_result = result
|
||||
if cursor_result and cursor_result.context._is_server_side:
|
||||
await greenlet_spawn(cursor_result.close)
|
||||
raise async_exc.AsyncMethodRequired(
|
||||
"Can't use the %s.%s() method with a "
|
||||
"server-side cursor. "
|
||||
"Use the %s.stream() method for an async "
|
||||
"streaming result set."
|
||||
% (
|
||||
calling_method.__self__.__class__.__name__,
|
||||
calling_method.__name__,
|
||||
calling_method.__self__.__class__.__name__,
|
||||
)
|
||||
)
|
||||
return result
|
||||
107
lib/sqlalchemy/ext/asyncio/scoping.py
Normal file
107
lib/sqlalchemy/ext/asyncio/scoping.py
Normal file
@@ -0,0 +1,107 @@
|
||||
# ext/asyncio/scoping.py
|
||||
# Copyright (C) 2005-2022 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: https://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from .session import AsyncSession
|
||||
from ...orm.scoping import ScopedSessionMixin
|
||||
from ...util import create_proxy_methods
|
||||
from ...util import ScopedRegistry
|
||||
|
||||
|
||||
@create_proxy_methods(
|
||||
AsyncSession,
|
||||
":class:`_asyncio.AsyncSession`",
|
||||
":class:`_asyncio.scoping.async_scoped_session`",
|
||||
classmethods=["close_all", "object_session", "identity_key"],
|
||||
methods=[
|
||||
"__contains__",
|
||||
"__iter__",
|
||||
"add",
|
||||
"add_all",
|
||||
"begin",
|
||||
"begin_nested",
|
||||
"close",
|
||||
"commit",
|
||||
"connection",
|
||||
"delete",
|
||||
"execute",
|
||||
"expire",
|
||||
"expire_all",
|
||||
"expunge",
|
||||
"expunge_all",
|
||||
"flush",
|
||||
"get",
|
||||
"get_bind",
|
||||
"is_modified",
|
||||
"invalidate",
|
||||
"merge",
|
||||
"refresh",
|
||||
"rollback",
|
||||
"scalar",
|
||||
"scalars",
|
||||
"stream",
|
||||
"stream_scalars",
|
||||
],
|
||||
attributes=[
|
||||
"bind",
|
||||
"dirty",
|
||||
"deleted",
|
||||
"new",
|
||||
"identity_map",
|
||||
"is_active",
|
||||
"autoflush",
|
||||
"no_autoflush",
|
||||
"info",
|
||||
],
|
||||
)
|
||||
class async_scoped_session(ScopedSessionMixin):
|
||||
"""Provides scoped management of :class:`.AsyncSession` objects.
|
||||
|
||||
See the section :ref:`asyncio_scoped_session` for usage details.
|
||||
|
||||
.. versionadded:: 1.4.19
|
||||
|
||||
|
||||
"""
|
||||
|
||||
_support_async = True
|
||||
|
||||
def __init__(self, session_factory, scopefunc):
|
||||
"""Construct a new :class:`_asyncio.async_scoped_session`.
|
||||
|
||||
:param session_factory: a factory to create new :class:`_asyncio.AsyncSession`
|
||||
instances. This is usually, but not necessarily, an instance
|
||||
of :class:`_orm.sessionmaker` which itself was passed the
|
||||
:class:`_asyncio.AsyncSession` to its :paramref:`_orm.sessionmaker.class_`
|
||||
parameter::
|
||||
|
||||
async_session_factory = sessionmaker(some_async_engine, class_= AsyncSession)
|
||||
AsyncSession = async_scoped_session(async_session_factory, scopefunc=current_task)
|
||||
|
||||
:param scopefunc: function which defines
|
||||
the current scope. A function such as ``asyncio.current_task``
|
||||
may be useful here.
|
||||
|
||||
""" # noqa: E501
|
||||
|
||||
self.session_factory = session_factory
|
||||
self.registry = ScopedRegistry(session_factory, scopefunc)
|
||||
|
||||
@property
|
||||
def _proxied(self):
|
||||
return self.registry()
|
||||
|
||||
async def remove(self):
|
||||
"""Dispose of the current :class:`.AsyncSession`, if present.
|
||||
|
||||
Different from scoped_session's remove method, this method would use
|
||||
await to wait for the close method of AsyncSession.
|
||||
|
||||
"""
|
||||
|
||||
if self.registry.has():
|
||||
await self.registry().close()
|
||||
self.registry.clear()
|
||||
759
lib/sqlalchemy/ext/asyncio/session.py
Normal file
759
lib/sqlalchemy/ext/asyncio/session.py
Normal file
@@ -0,0 +1,759 @@
|
||||
# ext/asyncio/session.py
|
||||
# Copyright (C) 2020-2022 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: https://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
import asyncio
|
||||
|
||||
from . import engine
|
||||
from . import result as _result
|
||||
from .base import ReversibleProxy
|
||||
from .base import StartableContext
|
||||
from .result import _ensure_sync_result
|
||||
from ... import util
|
||||
from ...orm import object_session
|
||||
from ...orm import Session
|
||||
from ...orm import state as _instance_state
|
||||
from ...util.concurrency import greenlet_spawn
|
||||
|
||||
_EXECUTE_OPTIONS = util.immutabledict({"prebuffer_rows": True})
|
||||
_STREAM_OPTIONS = util.immutabledict({"stream_results": True})
|
||||
|
||||
|
||||
@util.create_proxy_methods(
|
||||
Session,
|
||||
":class:`_orm.Session`",
|
||||
":class:`_asyncio.AsyncSession`",
|
||||
classmethods=["object_session", "identity_key"],
|
||||
methods=[
|
||||
"__contains__",
|
||||
"__iter__",
|
||||
"add",
|
||||
"add_all",
|
||||
"expire",
|
||||
"expire_all",
|
||||
"expunge",
|
||||
"expunge_all",
|
||||
"is_modified",
|
||||
"in_transaction",
|
||||
"in_nested_transaction",
|
||||
],
|
||||
attributes=[
|
||||
"dirty",
|
||||
"deleted",
|
||||
"new",
|
||||
"identity_map",
|
||||
"is_active",
|
||||
"autoflush",
|
||||
"no_autoflush",
|
||||
"info",
|
||||
],
|
||||
)
|
||||
class AsyncSession(ReversibleProxy):
|
||||
"""Asyncio version of :class:`_orm.Session`.
|
||||
|
||||
The :class:`_asyncio.AsyncSession` is a proxy for a traditional
|
||||
:class:`_orm.Session` instance.
|
||||
|
||||
.. versionadded:: 1.4
|
||||
|
||||
To use an :class:`_asyncio.AsyncSession` with custom :class:`_orm.Session`
|
||||
implementations, see the
|
||||
:paramref:`_asyncio.AsyncSession.sync_session_class` parameter.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
_is_asyncio = True
|
||||
|
||||
dispatch = None
|
||||
|
||||
def __init__(self, bind=None, binds=None, sync_session_class=None, **kw):
|
||||
r"""Construct a new :class:`_asyncio.AsyncSession`.
|
||||
|
||||
All parameters other than ``sync_session_class`` are passed to the
|
||||
``sync_session_class`` callable directly to instantiate a new
|
||||
:class:`_orm.Session`. Refer to :meth:`_orm.Session.__init__` for
|
||||
parameter documentation.
|
||||
|
||||
:param sync_session_class:
|
||||
A :class:`_orm.Session` subclass or other callable which will be used
|
||||
to construct the :class:`_orm.Session` which will be proxied. This
|
||||
parameter may be used to provide custom :class:`_orm.Session`
|
||||
subclasses. Defaults to the
|
||||
:attr:`_asyncio.AsyncSession.sync_session_class` class-level
|
||||
attribute.
|
||||
|
||||
.. versionadded:: 1.4.24
|
||||
|
||||
"""
|
||||
kw["future"] = True
|
||||
if bind:
|
||||
self.bind = bind
|
||||
bind = engine._get_sync_engine_or_connection(bind)
|
||||
|
||||
if binds:
|
||||
self.binds = binds
|
||||
binds = {
|
||||
key: engine._get_sync_engine_or_connection(b)
|
||||
for key, b in binds.items()
|
||||
}
|
||||
|
||||
if sync_session_class:
|
||||
self.sync_session_class = sync_session_class
|
||||
|
||||
self.sync_session = self._proxied = self._assign_proxied(
|
||||
self.sync_session_class(bind=bind, binds=binds, **kw)
|
||||
)
|
||||
|
||||
sync_session_class = Session
|
||||
"""The class or callable that provides the
|
||||
underlying :class:`_orm.Session` instance for a particular
|
||||
:class:`_asyncio.AsyncSession`.
|
||||
|
||||
At the class level, this attribute is the default value for the
|
||||
:paramref:`_asyncio.AsyncSession.sync_session_class` parameter. Custom
|
||||
subclasses of :class:`_asyncio.AsyncSession` can override this.
|
||||
|
||||
At the instance level, this attribute indicates the current class or
|
||||
callable that was used to provide the :class:`_orm.Session` instance for
|
||||
this :class:`_asyncio.AsyncSession` instance.
|
||||
|
||||
.. versionadded:: 1.4.24
|
||||
|
||||
"""
|
||||
|
||||
sync_session: Session
|
||||
"""Reference to the underlying :class:`_orm.Session` this
|
||||
:class:`_asyncio.AsyncSession` proxies requests towards.
|
||||
|
||||
This instance can be used as an event target.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`asyncio_events`
|
||||
|
||||
"""
|
||||
|
||||
async def refresh(
|
||||
self, instance, attribute_names=None, with_for_update=None
|
||||
):
|
||||
"""Expire and refresh the attributes on the given instance.
|
||||
|
||||
A query will be issued to the database and all attributes will be
|
||||
refreshed with their current database value.
|
||||
|
||||
This is the async version of the :meth:`_orm.Session.refresh` method.
|
||||
See that method for a complete description of all options.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`_orm.Session.refresh` - main documentation for refresh
|
||||
|
||||
"""
|
||||
|
||||
return await greenlet_spawn(
|
||||
self.sync_session.refresh,
|
||||
instance,
|
||||
attribute_names=attribute_names,
|
||||
with_for_update=with_for_update,
|
||||
)
|
||||
|
||||
async def run_sync(self, fn, *arg, **kw):
|
||||
"""Invoke the given sync callable passing sync self as the first
|
||||
argument.
|
||||
|
||||
This method maintains the asyncio event loop all the way through
|
||||
to the database connection by running the given callable in a
|
||||
specially instrumented greenlet.
|
||||
|
||||
E.g.::
|
||||
|
||||
with AsyncSession(async_engine) as session:
|
||||
await session.run_sync(some_business_method)
|
||||
|
||||
.. note::
|
||||
|
||||
The provided callable is invoked inline within the asyncio event
|
||||
loop, and will block on traditional IO calls. IO within this
|
||||
callable should only call into SQLAlchemy's asyncio database
|
||||
APIs which will be properly adapted to the greenlet context.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`session_run_sync`
|
||||
"""
|
||||
|
||||
return await greenlet_spawn(fn, self.sync_session, *arg, **kw)
|
||||
|
||||
async def execute(
|
||||
self,
|
||||
statement,
|
||||
params=None,
|
||||
execution_options=util.EMPTY_DICT,
|
||||
bind_arguments=None,
|
||||
**kw
|
||||
):
|
||||
"""Execute a statement and return a buffered
|
||||
:class:`_engine.Result` object.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`_orm.Session.execute` - main documentation for execute
|
||||
|
||||
"""
|
||||
|
||||
if execution_options:
|
||||
execution_options = util.immutabledict(execution_options).union(
|
||||
_EXECUTE_OPTIONS
|
||||
)
|
||||
else:
|
||||
execution_options = _EXECUTE_OPTIONS
|
||||
|
||||
result = await greenlet_spawn(
|
||||
self.sync_session.execute,
|
||||
statement,
|
||||
params=params,
|
||||
execution_options=execution_options,
|
||||
bind_arguments=bind_arguments,
|
||||
**kw
|
||||
)
|
||||
return await _ensure_sync_result(result, self.execute)
|
||||
|
||||
async def scalar(
|
||||
self,
|
||||
statement,
|
||||
params=None,
|
||||
execution_options=util.EMPTY_DICT,
|
||||
bind_arguments=None,
|
||||
**kw
|
||||
):
|
||||
"""Execute a statement and return a scalar result.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`_orm.Session.scalar` - main documentation for scalar
|
||||
|
||||
"""
|
||||
|
||||
result = await self.execute(
|
||||
statement,
|
||||
params=params,
|
||||
execution_options=execution_options,
|
||||
bind_arguments=bind_arguments,
|
||||
**kw
|
||||
)
|
||||
return result.scalar()
|
||||
|
||||
async def scalars(
|
||||
self,
|
||||
statement,
|
||||
params=None,
|
||||
execution_options=util.EMPTY_DICT,
|
||||
bind_arguments=None,
|
||||
**kw
|
||||
):
|
||||
"""Execute a statement and return scalar results.
|
||||
|
||||
:return: a :class:`_result.ScalarResult` object
|
||||
|
||||
.. versionadded:: 1.4.24
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`_orm.Session.scalars` - main documentation for scalars
|
||||
|
||||
:meth:`_asyncio.AsyncSession.stream_scalars` - streaming version
|
||||
|
||||
"""
|
||||
|
||||
result = await self.execute(
|
||||
statement,
|
||||
params=params,
|
||||
execution_options=execution_options,
|
||||
bind_arguments=bind_arguments,
|
||||
**kw
|
||||
)
|
||||
return result.scalars()
|
||||
|
||||
async def get(
|
||||
self,
|
||||
entity,
|
||||
ident,
|
||||
options=None,
|
||||
populate_existing=False,
|
||||
with_for_update=None,
|
||||
identity_token=None,
|
||||
):
|
||||
"""Return an instance based on the given primary key identifier,
|
||||
or ``None`` if not found.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`_orm.Session.get` - main documentation for get
|
||||
|
||||
|
||||
"""
|
||||
return await greenlet_spawn(
|
||||
self.sync_session.get,
|
||||
entity,
|
||||
ident,
|
||||
options=options,
|
||||
populate_existing=populate_existing,
|
||||
with_for_update=with_for_update,
|
||||
identity_token=identity_token,
|
||||
)
|
||||
|
||||
async def stream(
|
||||
self,
|
||||
statement,
|
||||
params=None,
|
||||
execution_options=util.EMPTY_DICT,
|
||||
bind_arguments=None,
|
||||
**kw
|
||||
):
|
||||
"""Execute a statement and return a streaming
|
||||
:class:`_asyncio.AsyncResult` object.
|
||||
|
||||
"""
|
||||
|
||||
if execution_options:
|
||||
execution_options = util.immutabledict(execution_options).union(
|
||||
_STREAM_OPTIONS
|
||||
)
|
||||
else:
|
||||
execution_options = _STREAM_OPTIONS
|
||||
|
||||
result = await greenlet_spawn(
|
||||
self.sync_session.execute,
|
||||
statement,
|
||||
params=params,
|
||||
execution_options=execution_options,
|
||||
bind_arguments=bind_arguments,
|
||||
**kw
|
||||
)
|
||||
return _result.AsyncResult(result)
|
||||
|
||||
async def stream_scalars(
|
||||
self,
|
||||
statement,
|
||||
params=None,
|
||||
execution_options=util.EMPTY_DICT,
|
||||
bind_arguments=None,
|
||||
**kw
|
||||
):
|
||||
"""Execute a statement and return a stream of scalar results.
|
||||
|
||||
:return: an :class:`_asyncio.AsyncScalarResult` object
|
||||
|
||||
.. versionadded:: 1.4.24
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`_orm.Session.scalars` - main documentation for scalars
|
||||
|
||||
:meth:`_asyncio.AsyncSession.scalars` - non streaming version
|
||||
|
||||
"""
|
||||
|
||||
result = await self.stream(
|
||||
statement,
|
||||
params=params,
|
||||
execution_options=execution_options,
|
||||
bind_arguments=bind_arguments,
|
||||
**kw
|
||||
)
|
||||
return result.scalars()
|
||||
|
||||
async def delete(self, instance):
|
||||
"""Mark an instance as deleted.
|
||||
|
||||
The database delete operation occurs upon ``flush()``.
|
||||
|
||||
As this operation may need to cascade along unloaded relationships,
|
||||
it is awaitable to allow for those queries to take place.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`_orm.Session.delete` - main documentation for delete
|
||||
|
||||
"""
|
||||
return await greenlet_spawn(self.sync_session.delete, instance)
|
||||
|
||||
async def merge(self, instance, load=True, options=None):
|
||||
"""Copy the state of a given instance into a corresponding instance
|
||||
within this :class:`_asyncio.AsyncSession`.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`_orm.Session.merge` - main documentation for merge
|
||||
|
||||
"""
|
||||
return await greenlet_spawn(
|
||||
self.sync_session.merge, instance, load=load, options=options
|
||||
)
|
||||
|
||||
async def flush(self, objects=None):
|
||||
"""Flush all the object changes to the database.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`_orm.Session.flush` - main documentation for flush
|
||||
|
||||
"""
|
||||
await greenlet_spawn(self.sync_session.flush, objects=objects)
|
||||
|
||||
def get_transaction(self):
|
||||
"""Return the current root transaction in progress, if any.
|
||||
|
||||
:return: an :class:`_asyncio.AsyncSessionTransaction` object, or
|
||||
``None``.
|
||||
|
||||
.. versionadded:: 1.4.18
|
||||
|
||||
"""
|
||||
trans = self.sync_session.get_transaction()
|
||||
if trans is not None:
|
||||
return AsyncSessionTransaction._retrieve_proxy_for_target(trans)
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_nested_transaction(self):
|
||||
"""Return the current nested transaction in progress, if any.
|
||||
|
||||
:return: an :class:`_asyncio.AsyncSessionTransaction` object, or
|
||||
``None``.
|
||||
|
||||
.. versionadded:: 1.4.18
|
||||
|
||||
"""
|
||||
|
||||
trans = self.sync_session.get_nested_transaction()
|
||||
if trans is not None:
|
||||
return AsyncSessionTransaction._retrieve_proxy_for_target(trans)
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_bind(self, mapper=None, clause=None, bind=None, **kw):
|
||||
"""Return a "bind" to which the synchronous proxied :class:`_orm.Session`
|
||||
is bound.
|
||||
|
||||
Unlike the :meth:`_orm.Session.get_bind` method, this method is
|
||||
currently **not** used by this :class:`.AsyncSession` in any way
|
||||
in order to resolve engines for requests.
|
||||
|
||||
.. note::
|
||||
|
||||
This method proxies directly to the :meth:`_orm.Session.get_bind`
|
||||
method, however is currently **not** useful as an override target,
|
||||
in contrast to that of the :meth:`_orm.Session.get_bind` method.
|
||||
The example below illustrates how to implement custom
|
||||
:meth:`_orm.Session.get_bind` schemes that work with
|
||||
:class:`.AsyncSession` and :class:`.AsyncEngine`.
|
||||
|
||||
The pattern introduced at :ref:`session_custom_partitioning`
|
||||
illustrates how to apply a custom bind-lookup scheme to a
|
||||
:class:`_orm.Session` given a set of :class:`_engine.Engine` objects.
|
||||
To apply a corresponding :meth:`_orm.Session.get_bind` implementation
|
||||
for use with a :class:`.AsyncSession` and :class:`.AsyncEngine`
|
||||
objects, continue to subclass :class:`_orm.Session` and apply it to
|
||||
:class:`.AsyncSession` using
|
||||
:paramref:`.AsyncSession.sync_session_class`. The inner method must
|
||||
continue to return :class:`_engine.Engine` instances, which can be
|
||||
acquired from a :class:`_asyncio.AsyncEngine` using the
|
||||
:attr:`_asyncio.AsyncEngine.sync_engine` attribute::
|
||||
|
||||
# using example from "Custom Vertical Partitioning"
|
||||
|
||||
|
||||
import random
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.ext.asyncio import create_async_engine
|
||||
from sqlalchemy.orm import Session, sessionmaker
|
||||
|
||||
# construct async engines w/ async drivers
|
||||
engines = {
|
||||
'leader':create_async_engine("sqlite+aiosqlite:///leader.db"),
|
||||
'other':create_async_engine("sqlite+aiosqlite:///other.db"),
|
||||
'follower1':create_async_engine("sqlite+aiosqlite:///follower1.db"),
|
||||
'follower2':create_async_engine("sqlite+aiosqlite:///follower2.db"),
|
||||
}
|
||||
|
||||
class RoutingSession(Session):
|
||||
def get_bind(self, mapper=None, clause=None, **kw):
|
||||
# within get_bind(), return sync engines
|
||||
if mapper and issubclass(mapper.class_, MyOtherClass):
|
||||
return engines['other'].sync_engine
|
||||
elif self._flushing or isinstance(clause, (Update, Delete)):
|
||||
return engines['leader'].sync_engine
|
||||
else:
|
||||
return engines[
|
||||
random.choice(['follower1','follower2'])
|
||||
].sync_engine
|
||||
|
||||
# apply to AsyncSession using sync_session_class
|
||||
AsyncSessionMaker = sessionmaker(
|
||||
class_=AsyncSession,
|
||||
sync_session_class=RoutingSession
|
||||
)
|
||||
|
||||
The :meth:`_orm.Session.get_bind` method is called in a non-asyncio,
|
||||
implicitly non-blocking context in the same manner as ORM event hooks
|
||||
and functions that are invoked via :meth:`.AsyncSession.run_sync`, so
|
||||
routines that wish to run SQL commands inside of
|
||||
:meth:`_orm.Session.get_bind` can continue to do so using
|
||||
blocking-style code, which will be translated to implicitly async calls
|
||||
at the point of invoking IO on the database drivers.
|
||||
|
||||
""" # noqa: E501
|
||||
|
||||
return self.sync_session.get_bind(
|
||||
mapper=mapper, clause=clause, bind=bind, **kw
|
||||
)
|
||||
|
||||
async def connection(self, **kw):
|
||||
r"""Return a :class:`_asyncio.AsyncConnection` object corresponding to
|
||||
this :class:`.Session` object's transactional state.
|
||||
|
||||
This method may also be used to establish execution options for the
|
||||
database connection used by the current transaction.
|
||||
|
||||
.. versionadded:: 1.4.24 Added \**kw arguments which are passed
|
||||
through to the underlying :meth:`_orm.Session.connection` method.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`_orm.Session.connection` - main documentation for
|
||||
"connection"
|
||||
|
||||
"""
|
||||
|
||||
sync_connection = await greenlet_spawn(
|
||||
self.sync_session.connection, **kw
|
||||
)
|
||||
return engine.AsyncConnection._retrieve_proxy_for_target(
|
||||
sync_connection
|
||||
)
|
||||
|
||||
def begin(self, **kw):
|
||||
"""Return an :class:`_asyncio.AsyncSessionTransaction` object.
|
||||
|
||||
The underlying :class:`_orm.Session` will perform the
|
||||
"begin" action when the :class:`_asyncio.AsyncSessionTransaction`
|
||||
object is entered::
|
||||
|
||||
async with async_session.begin():
|
||||
# .. ORM transaction is begun
|
||||
|
||||
Note that database IO will not normally occur when the session-level
|
||||
transaction is begun, as database transactions begin on an
|
||||
on-demand basis. However, the begin block is async to accommodate
|
||||
for a :meth:`_orm.SessionEvents.after_transaction_create`
|
||||
event hook that may perform IO.
|
||||
|
||||
For a general description of ORM begin, see
|
||||
:meth:`_orm.Session.begin`.
|
||||
|
||||
"""
|
||||
|
||||
return AsyncSessionTransaction(self)
|
||||
|
||||
def begin_nested(self, **kw):
|
||||
"""Return an :class:`_asyncio.AsyncSessionTransaction` object
|
||||
which will begin a "nested" transaction, e.g. SAVEPOINT.
|
||||
|
||||
Behavior is the same as that of :meth:`_asyncio.AsyncSession.begin`.
|
||||
|
||||
For a general description of ORM begin nested, see
|
||||
:meth:`_orm.Session.begin_nested`.
|
||||
|
||||
"""
|
||||
|
||||
return AsyncSessionTransaction(self, nested=True)
|
||||
|
||||
async def rollback(self):
|
||||
"""Rollback the current transaction in progress."""
|
||||
return await greenlet_spawn(self.sync_session.rollback)
|
||||
|
||||
async def commit(self):
|
||||
"""Commit the current transaction in progress."""
|
||||
return await greenlet_spawn(self.sync_session.commit)
|
||||
|
||||
async def close(self):
|
||||
"""Close out the transactional resources and ORM objects used by this
|
||||
:class:`_asyncio.AsyncSession`.
|
||||
|
||||
This expunges all ORM objects associated with this
|
||||
:class:`_asyncio.AsyncSession`, ends any transaction in progress and
|
||||
:term:`releases` any :class:`_asyncio.AsyncConnection` objects which
|
||||
this :class:`_asyncio.AsyncSession` itself has checked out from
|
||||
associated :class:`_asyncio.AsyncEngine` objects. The operation then
|
||||
leaves the :class:`_asyncio.AsyncSession` in a state which it may be
|
||||
used again.
|
||||
|
||||
.. tip::
|
||||
|
||||
The :meth:`_asyncio.AsyncSession.close` method **does not prevent
|
||||
the Session from being used again**. The
|
||||
:class:`_asyncio.AsyncSession` itself does not actually have a
|
||||
distinct "closed" state; it merely means the
|
||||
:class:`_asyncio.AsyncSession` will release all database
|
||||
connections and ORM objects.
|
||||
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`session_closing` - detail on the semantics of
|
||||
:meth:`_asyncio.AsyncSession.close`
|
||||
|
||||
"""
|
||||
await greenlet_spawn(self.sync_session.close)
|
||||
|
||||
async def invalidate(self):
|
||||
"""Close this Session, using connection invalidation.
|
||||
|
||||
For a complete description, see :meth:`_orm.Session.invalidate`.
|
||||
"""
|
||||
return await greenlet_spawn(self.sync_session.invalidate)
|
||||
|
||||
@classmethod
|
||||
async def close_all(self):
|
||||
"""Close all :class:`_asyncio.AsyncSession` sessions."""
|
||||
return await greenlet_spawn(self.sync_session.close_all)
|
||||
|
||||
async def __aenter__(self):
|
||||
return self
|
||||
|
||||
async def __aexit__(self, type_, value, traceback):
|
||||
await asyncio.shield(self.close())
|
||||
|
||||
def _maker_context_manager(self):
|
||||
# no @contextlib.asynccontextmanager until python3.7, gr
|
||||
return _AsyncSessionContextManager(self)
|
||||
|
||||
|
||||
class _AsyncSessionContextManager:
|
||||
def __init__(self, async_session):
|
||||
self.async_session = async_session
|
||||
|
||||
async def __aenter__(self):
|
||||
self.trans = self.async_session.begin()
|
||||
await self.trans.__aenter__()
|
||||
return self.async_session
|
||||
|
||||
async def __aexit__(self, type_, value, traceback):
|
||||
async def go():
|
||||
await self.trans.__aexit__(type_, value, traceback)
|
||||
await self.async_session.__aexit__(type_, value, traceback)
|
||||
|
||||
await asyncio.shield(go())
|
||||
|
||||
|
||||
class AsyncSessionTransaction(ReversibleProxy, StartableContext):
|
||||
"""A wrapper for the ORM :class:`_orm.SessionTransaction` object.
|
||||
|
||||
This object is provided so that a transaction-holding object
|
||||
for the :meth:`_asyncio.AsyncSession.begin` may be returned.
|
||||
|
||||
The object supports both explicit calls to
|
||||
:meth:`_asyncio.AsyncSessionTransaction.commit` and
|
||||
:meth:`_asyncio.AsyncSessionTransaction.rollback`, as well as use as an
|
||||
async context manager.
|
||||
|
||||
|
||||
.. versionadded:: 1.4
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ("session", "sync_transaction", "nested")
|
||||
|
||||
def __init__(self, session, nested=False):
|
||||
self.session = session
|
||||
self.nested = nested
|
||||
self.sync_transaction = None
|
||||
|
||||
@property
|
||||
def is_active(self):
|
||||
return (
|
||||
self._sync_transaction() is not None
|
||||
and self._sync_transaction().is_active
|
||||
)
|
||||
|
||||
def _sync_transaction(self):
|
||||
if not self.sync_transaction:
|
||||
self._raise_for_not_started()
|
||||
return self.sync_transaction
|
||||
|
||||
async def rollback(self):
|
||||
"""Roll back this :class:`_asyncio.AsyncTransaction`."""
|
||||
await greenlet_spawn(self._sync_transaction().rollback)
|
||||
|
||||
async def commit(self):
|
||||
"""Commit this :class:`_asyncio.AsyncTransaction`."""
|
||||
|
||||
await greenlet_spawn(self._sync_transaction().commit)
|
||||
|
||||
async def start(self, is_ctxmanager=False):
|
||||
self.sync_transaction = self._assign_proxied(
|
||||
await greenlet_spawn(
|
||||
self.session.sync_session.begin_nested
|
||||
if self.nested
|
||||
else self.session.sync_session.begin
|
||||
)
|
||||
)
|
||||
if is_ctxmanager:
|
||||
self.sync_transaction.__enter__()
|
||||
return self
|
||||
|
||||
async def __aexit__(self, type_, value, traceback):
|
||||
await greenlet_spawn(
|
||||
self._sync_transaction().__exit__, type_, value, traceback
|
||||
)
|
||||
|
||||
|
||||
def async_object_session(instance):
|
||||
"""Return the :class:`_asyncio.AsyncSession` to which the given instance
|
||||
belongs.
|
||||
|
||||
This function makes use of the sync-API function
|
||||
:class:`_orm.object_session` to retrieve the :class:`_orm.Session` which
|
||||
refers to the given instance, and from there links it to the original
|
||||
:class:`_asyncio.AsyncSession`.
|
||||
|
||||
If the :class:`_asyncio.AsyncSession` has been garbage collected, the
|
||||
return value is ``None``.
|
||||
|
||||
This functionality is also available from the
|
||||
:attr:`_orm.InstanceState.async_session` accessor.
|
||||
|
||||
:param instance: an ORM mapped instance
|
||||
:return: an :class:`_asyncio.AsyncSession` object, or ``None``.
|
||||
|
||||
.. versionadded:: 1.4.18
|
||||
|
||||
"""
|
||||
|
||||
session = object_session(instance)
|
||||
if session is not None:
|
||||
return async_session(session)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def async_session(session):
|
||||
"""Return the :class:`_asyncio.AsyncSession` which is proxying the given
|
||||
:class:`_orm.Session` object, if any.
|
||||
|
||||
:param session: a :class:`_orm.Session` instance.
|
||||
:return: a :class:`_asyncio.AsyncSession` instance, or ``None``.
|
||||
|
||||
.. versionadded:: 1.4.18
|
||||
|
||||
"""
|
||||
return AsyncSession._retrieve_proxy_for_target(session, regenerate=False)
|
||||
|
||||
|
||||
_instance_state._async_provider = async_session
|
||||
Reference in New Issue
Block a user