Improve error message when inspecting async proxies

Provide better error message when trying to insepct and async engine
or asnyc connection.

Change-Id: I907f3a22c6b76fe43df9d40cb0e69c57f74a7982
This commit is contained in:
Federico Caselli
2021-08-27 21:50:01 +02:00
committed by Mike Bayer
parent 9131a5208f
commit 1798c3cf1c
6 changed files with 96 additions and 5 deletions
+28
View File
@@ -1265,6 +1265,34 @@ attempt, which is unsupported when using SQLAlchemy with AsyncIO dialects.
:ref:`asyncio_orm_avoid_lazyloads` - covers most ORM scenarios where
this problem can occur and how to mitigate.
.. _error_xd3s:
No Inspection Avaliable
-----------------------
Using the :func:`_sa.inspect` function directly on an
:class:`_asyncio.AsyncConnection` or :class:`_asyncio.AsyncEngine` object is
not currently supported, as there is not yet an awaitable form of the
:class:`_reflection.Inspector` object available. Instead, the object
is used by acquiring it using the
:func:`_sa.inspect` function in such a way that it refers to the underlying
:attr:`_asyncio.AsyncConnection.sync_connection` attribute of the
:class:`_asyncio.AsyncConnection` object; the :class:`_engine.Inspector` is
then used in a "synchronous" calling style by using the
:meth:`_asyncio.AsyncConnection.run_sync` method along with a custom function
that performs the desired operations::
async def async_main():
async with engine.connect() as conn:
tables = await conn.run_sync(
lambda sync_conn: inspect(sync_conn).get_table_names()
)
.. seealso::
:ref:`asyncio_inspector` - additional examples of using :func:`_sa.inspect`
with the asyncio extension.
Core Exception Classes
======================
+2
View File
@@ -498,6 +498,8 @@ the usual ``await`` keywords are necessary, including for the
.. currentmodule:: sqlalchemy.ext.asyncio
.. _asyncio_inspector:
Using the Inspector to inspect schema objects
---------------------------------------------------
+5
View File
@@ -8,6 +8,7 @@ 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:
@@ -46,6 +47,8 @@ class ReversibleProxy:
class StartableContext(abc.ABC):
__slots__ = ()
@abc.abstractmethod
async def start(self, is_ctxmanager=False):
pass
@@ -68,6 +71,8 @@ class StartableContext(abc.ABC):
class ProxyComparable(ReversibleProxy):
__slots__ = ()
def __hash__(self):
return id(self)
+23
View File
@@ -9,6 +9,7 @@ from .base import ProxyComparable
from .base import StartableContext
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
@@ -80,6 +81,7 @@ class AsyncConnection(ProxyComparable, StartableContext, AsyncConnectable):
# create a new AsyncConnection that matches this one given only the
# "sync" elements.
__slots__ = (
"engine",
"sync_engine",
"sync_connection",
)
@@ -709,3 +711,24 @@ def _get_sync_engine_or_connection(async_engine):
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",
)
+35
View File
@@ -6,6 +6,7 @@ from sqlalchemy import delete
from sqlalchemy import event
from sqlalchemy import exc
from sqlalchemy import func
from sqlalchemy import inspect
from sqlalchemy import Integer
from sqlalchemy import select
from sqlalchemy import String
@@ -653,6 +654,39 @@ class AsyncEventTest(EngineFixture):
[mock.call(sync_conn, mock.ANY, "select 1", (), mock.ANY, False)],
)
@async_test
async def test_event_on_sync_connection(self, async_engine):
canary = mock.Mock()
async with async_engine.connect() as conn:
event.listen(conn.sync_connection, "begin", canary)
async with conn.begin():
eq_(
canary.mock_calls,
[mock.call(conn.sync_connection)],
)
class AsyncInspection(EngineFixture):
__backend__ = True
@async_test
async def test_inspect_engine(self, async_engine):
with testing.expect_raises_message(
exc.NoInspectionAvailable,
"Inspection on an AsyncEngine is currently not supported.",
):
inspect(async_engine)
@async_test
async def test_inspect_connection(self, async_engine):
async with async_engine.connect() as conn:
with testing.expect_raises_message(
exc.NoInspectionAvailable,
"Inspection on an AsyncConnection is currently not supported.",
):
inspect(conn)
class AsyncResultTest(EngineFixture):
@testing.combinations(
@@ -945,6 +979,7 @@ class AsyncProxyTest(EngineFixture, fixtures.TestBase):
is_not(async_connection.engine, None)
@testing.requires.predictable_gc
@async_test
async def test_gc_engine(self, testing_engine):
ReversibleProxy._proxy_objects.clear()
+3 -5
View File
@@ -580,12 +580,10 @@ class AsyncEventTest(AsyncFixture):
@async_test
async def test_no_async_listeners(self, async_session):
with testing.expect_raises(
with testing.expect_raises_message(
NotImplementedError,
"NotImplementedError: asynchronous events are not implemented "
"at this time. Apply synchronous listeners to the "
"AsyncEngine.sync_engine or "
"AsyncConnection.sync_connection attributes.",
"asynchronous events are not implemented at this time. "
"Apply synchronous listeners to the AsyncSession.sync_session.",
):
event.listen(async_session, "before_flush", mock.Mock())