summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormike bayer <mike_mp@zzzcomputing.com>2021-08-30 19:36:40 +0000
committerGerrit Code Review <gerrit@ci3.zzzcomputing.com>2021-08-30 19:36:40 +0000
commitfb874f97fab798eea76f9732a31bb9332877d00e (patch)
tree7fce5c30db89ebbf5ddc212be44b83275bda9881
parent916bd20b6d1e7c153d334c31cf0882f502e3815e (diff)
parent1798c3cf1c407ba2a05855e97ae39997f213084d (diff)
downloadsqlalchemy-fb874f97fab798eea76f9732a31bb9332877d00e.tar.gz
Merge "Improve error message when inspecting async proxies"
-rw-r--r--doc/build/errors.rst28
-rw-r--r--doc/build/orm/extensions/asyncio.rst2
-rw-r--r--lib/sqlalchemy/ext/asyncio/base.py5
-rw-r--r--lib/sqlalchemy/ext/asyncio/engine.py23
-rw-r--r--test/ext/asyncio/test_engine_py3k.py35
-rw-r--r--test/ext/asyncio/test_session_py3k.py8
6 files changed, 96 insertions, 5 deletions
diff --git a/doc/build/errors.rst b/doc/build/errors.rst
index 5081928dd..4058b06ea 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 c5fc356d1..281d9805b 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 3f2c084f4..3f77f5500 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 f5c3bdca4..5a692ffb1 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 fec8bc6da..c75dd8665 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 ebedfedbf..a0aaf7ee0 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())