From: Mike Bayer Date: Mon, 11 Dec 2023 15:55:25 +0000 (-0500) Subject: raise ImportError when greenlet not installed X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=54a4ac0eb9d79a4af3a3267561f4903314494b0f;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git raise ImportError when greenlet not installed This is the runtime raise when an async concurrency function is called. in 2.0 this raises ValueError, however here we've standardized on raising ``ImportError`. continuing for #10747, add a test asserting we dont get an endless loop and get a clean ImportError instead when greenlet not installed and async functions are used. Fixes: #10747 Change-Id: I54dffe8577025e2ef3a59f5ca9ab7f4362d4d91f --- diff --git a/lib/sqlalchemy/testing/requirements.py b/lib/sqlalchemy/testing/requirements.py index f06ccd58bd..eaba84ecd2 100644 --- a/lib/sqlalchemy/testing/requirements.py +++ b/lib/sqlalchemy/testing/requirements.py @@ -1603,6 +1603,18 @@ class SuiteRequirements(Requirements): def asyncio(self): return self.greenlet + @property + def no_greenlet(self): + def go(config): + try: + import greenlet # noqa: F401 + except ImportError: + return True + else: + return False + + return exclusions.only_if(go) + @property def greenlet(self): def go(config): diff --git a/lib/sqlalchemy/util/concurrency.py b/lib/sqlalchemy/util/concurrency.py index 575d249c9f..9e4c6c85da 100644 --- a/lib/sqlalchemy/util/concurrency.py +++ b/lib/sqlalchemy/util/concurrency.py @@ -15,6 +15,7 @@ from typing import Any from typing import Awaitable from typing import Callable from typing import Coroutine +from typing import NoReturn from typing import Optional from typing import Protocol from typing import TYPE_CHECKING @@ -62,6 +63,10 @@ if TYPE_CHECKING: ... +def _not_implemented(*arg: Any, **kw: Any) -> NoReturn: + raise ImportError(_ERROR_MESSAGE) + + class _concurrency_shim_cls: """Late import shim for greenlet""" @@ -78,7 +83,8 @@ class _concurrency_shim_cls: return if not TYPE_CHECKING: - global getcurrent, greenlet, _AsyncIoGreenlet, _has_gr_context + global getcurrent, greenlet, _AsyncIoGreenlet + global _has_gr_context, _greenlet_error try: from greenlet import getcurrent @@ -120,6 +126,9 @@ class _concurrency_shim_cls: def _initialize_no_greenlet(self): self._util_async_run = self._no_greenlet_util_async_run + self.getcurrent = _not_implemented + self.greenlet = _not_implemented # type: ignore + self._AsyncIoGreenlet = _not_implemented # type: ignore def __getattr__(self, key: str) -> Any: if key in self.__slots__: diff --git a/test/base/test_concurrency.py b/test/base/test_concurrency.py index 04d6e52089..1ea61ba7ce 100644 --- a/test/base/test_concurrency.py +++ b/test/base/test_concurrency.py @@ -290,3 +290,18 @@ class GreenletImportTests(fixtures.TestBase): ) def test_concurrency_fn(self, fn): self._run_in_process(fn) + + +class GracefulNoGreenletTest(fixtures.TestBase): + __requires__ = ("no_greenlet",) + + def test_await_only_graceful(self): + async def async_fn(): + pass + + with expect_raises_message( + ImportError, + "The SQLAlchemy asyncio module requires that the Python " + "'greenlet' library is installed", + ): + await_only(async_fn())