From: Federico Caselli Date: Fri, 27 Aug 2021 19:50:01 +0000 (+0200) Subject: Improve error message when inspecting async proxies X-Git-Tag: rel_1_4_24~48^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1798c3cf1c;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Improve error message when inspecting async proxies Provide better error message when trying to insepct and async engine or asnyc connection. Change-Id: I907f3a22c6b76fe43df9d40cb0e69c57f74a7982 --- diff --git a/doc/build/errors.rst b/doc/build/errors.rst index 5081928dd1..4058b06eaf 100644 --- a/doc/build/errors.rst +++ b/doc/build/errors.rst @@ -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 ====================== diff --git a/doc/build/orm/extensions/asyncio.rst b/doc/build/orm/extensions/asyncio.rst index c5fc356d12..281d9805b3 100644 --- a/doc/build/orm/extensions/asyncio.rst +++ b/doc/build/orm/extensions/asyncio.rst @@ -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 --------------------------------------------------- diff --git a/lib/sqlalchemy/ext/asyncio/base.py b/lib/sqlalchemy/ext/asyncio/base.py index 3f2c084f4a..3f77f55007 100644 --- a/lib/sqlalchemy/ext/asyncio/base.py +++ b/lib/sqlalchemy/ext/asyncio/base.py @@ -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) diff --git a/lib/sqlalchemy/ext/asyncio/engine.py b/lib/sqlalchemy/ext/asyncio/engine.py index f5c3bdca47..5a692ffb1b 100644 --- a/lib/sqlalchemy/ext/asyncio/engine.py +++ b/lib/sqlalchemy/ext/asyncio/engine.py @@ -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", + ) diff --git a/test/ext/asyncio/test_engine_py3k.py b/test/ext/asyncio/test_engine_py3k.py index fec8bc6da1..c75dd86655 100644 --- a/test/ext/asyncio/test_engine_py3k.py +++ b/test/ext/asyncio/test_engine_py3k.py @@ -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() diff --git a/test/ext/asyncio/test_session_py3k.py b/test/ext/asyncio/test_session_py3k.py index ebedfedbfb..a0aaf7ee00 100644 --- a/test/ext/asyncio/test_session_py3k.py +++ b/test/ext/asyncio/test_session_py3k.py @@ -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())