From bb846d1650423ce8013feb93b3c8837b4c5498a0 Mon Sep 17 00:00:00 2001 From: Federico Caselli Date: Tue, 12 Jan 2021 22:00:59 +0100 Subject: [PATCH] Improve error message when await_ call errors Fixes: #5832 Change-Id: Ia2ed8f1d1ec54e5f6e1a8f817a69446fdb3b7f6d --- doc/build/errors.rst | 18 +++++++++++++++++- lib/sqlalchemy/exc.py | 9 +++++++++ lib/sqlalchemy/util/_concurrency_py3k.py | 10 ++++++---- test/base/test_concurrency_py3k.py | 4 ++-- 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/doc/build/errors.rst b/doc/build/errors.rst index 67a8a29b0f..7d767a7118 100644 --- a/doc/build/errors.rst +++ b/doc/build/errors.rst @@ -1130,7 +1130,23 @@ with a non compatible :term:`DBAPI`. .. seealso:: - :ref:`asyncio extension ` + :ref:`asyncio_toplevel` + +.. _error_xd2s: + +MissingGreenlet +--------------- + +A call to the async :term:`DBAPI` was initiated outside the greenlet spawn context +usually setup by the SQLAlchemy AsyncIO proxy classes. +Usually this error happens when an IO was attempted in an unexpected +place, without using the provided async api. +When using the ORM this may be due to a lazy loading attempt, which +is unsupported when using SQLAlchemy with AsyncIO dialects. + +.. seealso:: + + :ref:`_session_run_sync` Core Exception Classes diff --git a/lib/sqlalchemy/exc.py b/lib/sqlalchemy/exc.py index 08b1bb060f..289b8dfab1 100644 --- a/lib/sqlalchemy/exc.py +++ b/lib/sqlalchemy/exc.py @@ -294,6 +294,15 @@ class AwaitRequired(InvalidRequestError): code = "xd1r" +class MissingGreenlet(InvalidRequestError): + r"""Error raised by the async greenlet await\_ if called while not inside + the greenlet spawn context. + + """ + + code = "xd2s" + + class NoReferencedTableError(NoReferenceError): """Raised by ``ForeignKey`` when the referred ``Table`` cannot be located. diff --git a/lib/sqlalchemy/util/_concurrency_py3k.py b/lib/sqlalchemy/util/_concurrency_py3k.py index 663d3e0f42..8edd057ef9 100644 --- a/lib/sqlalchemy/util/_concurrency_py3k.py +++ b/lib/sqlalchemy/util/_concurrency_py3k.py @@ -44,8 +44,9 @@ def await_only(awaitable: Coroutine) -> Any: # this is called in the context greenlet while running fn current = greenlet.getcurrent() if not isinstance(current, _AsyncIoGreenlet): - raise exc.InvalidRequestError( - "greenlet_spawn has not been called; can't call await_() here." + raise exc.MissingGreenlet( + "greenlet_spawn has not been called; can't call await_() here. " + "Was IO attempted in an unexpected place?" ) # returns the control to the driver greenlet passing it @@ -69,9 +70,10 @@ def await_fallback(awaitable: Coroutine) -> Any: if not isinstance(current, _AsyncIoGreenlet): loop = asyncio.get_event_loop() if loop.is_running(): - raise exc.InvalidRequestError( + raise exc.MissingGreenlet( "greenlet_spawn has not been called and asyncio event " - "loop is already running; can't call await_() here." + "loop is already running; can't call await_() here. " + "Was IO attempted in an unexpected place?" ) return loop.run_until_complete(awaitable) diff --git a/test/base/test_concurrency_py3k.py b/test/base/test_concurrency_py3k.py index e7ae8c9ad2..25ce9e7ecc 100644 --- a/test/base/test_concurrency_py3k.py +++ b/test/base/test_concurrency_py3k.py @@ -57,7 +57,7 @@ class TestAsyncioCompat(fixtures.TestBase): async def test_await_only_no_greenlet(self): to_await = run1() with expect_raises_message( - exc.InvalidRequestError, + exc.MissingGreenlet, r"greenlet_spawn has not been called; can't call await_\(\) here.", ): await_only(to_await) @@ -80,7 +80,7 @@ class TestAsyncioCompat(fixtures.TestBase): await_fallback(inner_await()) with expect_raises_message( - exc.InvalidRequestError, + exc.MissingGreenlet, "greenlet_spawn has not been called and asyncio event loop", ): await greenlet_spawn(go) -- 2.47.2