From b711e8b77427a290c0f21b4032ab414dfdd1c6bf Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Mon, 30 Dec 2024 08:35:03 +0000 Subject: [PATCH] fixes for 3.14 --- tests/test_auto_detection.py | 5 +++++ tests/test_compat.py | 3 ++- uvicorn/_compat.py | 7 +++++++ uvicorn/config.py | 5 +++-- uvicorn/loops/auto.py | 14 ++++++++++---- 5 files changed, 27 insertions(+), 7 deletions(-) diff --git a/tests/test_auto_detection.py b/tests/test_auto_detection.py index ef86bf265..7da419be3 100644 --- a/tests/test_auto_detection.py +++ b/tests/test_auto_detection.py @@ -1,6 +1,7 @@ import asyncio import contextlib import importlib +import sys import pytest @@ -15,6 +16,10 @@ expected_loop = "uvloop" # pragma: py-win32 except ImportError: # pragma: py-not-win32 expected_loop = "asyncio" +except AttributeError: + if sys.version_info >= (3, 14): + raise + expected_loop = "asyncio" try: importlib.import_module("httptools") diff --git a/tests/test_compat.py b/tests/test_compat.py index 0a962030c..012062311 100644 --- a/tests/test_compat.py +++ b/tests/test_compat.py @@ -1,6 +1,7 @@ from __future__ import annotations import asyncio +import sys from asyncio import AbstractEventLoop import pytest @@ -23,7 +24,7 @@ def test_asyncio_run__custom_loop_factory() -> None: def test_asyncio_run__passing_a_non_awaitable_callback_should_throw_error() -> None: - with pytest.raises(ValueError): + with pytest.raises(TypeError if sys.version_info >= (3, 14) else ValueError): asyncio_run( lambda: None, # type: ignore loop_factory=CustomLoop, diff --git a/uvicorn/_compat.py b/uvicorn/_compat.py index f380aca50..118a19ac0 100644 --- a/uvicorn/_compat.py +++ b/uvicorn/_compat.py @@ -5,6 +5,13 @@ from collections.abc import Callable, Coroutine from typing import Any, TypeVar +__all__ = ["asyncio_run", "iscoroutinefunction"] + +if sys.version_info >= (3, 14): + from inspect import iscoroutinefunction +else: + from asyncio import iscoroutinefunction + _T = TypeVar("_T") if sys.version_info >= (3, 12): diff --git a/uvicorn/config.py b/uvicorn/config.py index 90f48b9eb..cb55a3828 100644 --- a/uvicorn/config.py +++ b/uvicorn/config.py @@ -16,6 +16,7 @@ import click +from uvicorn._compat import iscoroutinefunction from uvicorn._types import ASGIApplication from uvicorn.importer import ImportFromStringError, import_from_string from uvicorn.logging import TRACE_LOG_LEVEL @@ -453,10 +454,10 @@ def load(self) -> None: if inspect.isclass(self.loaded_app): use_asgi_3 = hasattr(self.loaded_app, "__await__") elif inspect.isfunction(self.loaded_app): - use_asgi_3 = asyncio.iscoroutinefunction(self.loaded_app) + use_asgi_3 = iscoroutinefunction(self.loaded_app) else: call = getattr(self.loaded_app, "__call__", None) - use_asgi_3 = asyncio.iscoroutinefunction(call) + use_asgi_3 = iscoroutinefunction(call) self.interface = "asgi3" if use_asgi_3 else "asgi2" if self.interface == "wsgi": diff --git a/uvicorn/loops/auto.py b/uvicorn/loops/auto.py index 190839905..d15d737f9 100644 --- a/uvicorn/loops/auto.py +++ b/uvicorn/loops/auto.py @@ -1,17 +1,23 @@ from __future__ import annotations import asyncio +import sys from collections.abc import Callable -def auto_loop_factory(use_subprocess: bool = False) -> Callable[[], asyncio.AbstractEventLoop]: +def auto_loop_factory(use_subprocess: bool = False) -> Callable[[], asyncio.AbstractEventLoop]: # pragma: no cover try: import uvloop # noqa except ImportError: # pragma: no cover - from uvicorn.loops.asyncio import asyncio_loop_factory as loop_factory - - return loop_factory(use_subprocess=use_subprocess) + pass + except AttributeError: # pragma: no cover + if sys.version_info < (3, 14): + raise else: # pragma: no cover from uvicorn.loops.uvloop import uvloop_loop_factory return uvloop_loop_factory(use_subprocess=use_subprocess) + + from uvicorn.loops.asyncio import asyncio_loop_factory as loop_factory + + return loop_factory(use_subprocess=use_subprocess)