]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Improve error message when inspecting async proxies
authorFederico Caselli <cfederico87@gmail.com>
Fri, 27 Aug 2021 19:50:01 +0000 (21:50 +0200)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 30 Aug 2021 18:19:36 +0000 (14:19 -0400)
Provide better error message when trying to insepct and async engine
or asnyc connection.

Change-Id: I907f3a22c6b76fe43df9d40cb0e69c57f74a7982

doc/build/errors.rst
doc/build/orm/extensions/asyncio.rst
lib/sqlalchemy/ext/asyncio/base.py
lib/sqlalchemy/ext/asyncio/engine.py
test/ext/asyncio/test_engine_py3k.py
test/ext/asyncio/test_session_py3k.py

index 5081928dd1dec87119f29892c045ff1c524827f9..4058b06eaf3e7bf421086a14bb7de665aaca3a17 100644 (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
 ======================
index c5fc356d1205beda1c7162a3a0385559633944ce..281d9805b3badcfac754c45acf850e9d6ecb0d01 100644 (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
 ---------------------------------------------------
 
index 3f2c084f4aca334dc6770e74d7f273b71257ea04..3f77f55007e6a6402552badfddb06803a23be14d 100644 (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)
 
index f5c3bdca475471cab26afe698a87123beca498fc..5a692ffb1be71e852d28f3e94b329af44e066bc2 100644 (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",
+    )
index fec8bc6da1e78c657cf4805dc4f1da46ee15e214..c75dd866555313139c859a9a92a5a47225360611 100644 (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()
 
index ebedfedbfba0926a0ab3607d54bc0669c0e50f4d..a0aaf7ee0029cb61fdec42fa8d7baa055af22181 100644 (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())