From 129558c3f1ae0742dfd1e515c6e572017e7ffa72 Mon Sep 17 00:00:00 2001 From: HONGJAEHYEONG Date: Mon, 25 Aug 2025 15:35:15 +0900 Subject: [PATCH 1/2] Use asyncio.iscoroutinefunction for Python < 3.13 --- fastapi/dependencies/utils.py | 10 ++++++++-- fastapi/routing.py | 7 ++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index 081b63a8b..1b15e6459 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -1,4 +1,5 @@ import inspect +import sys from contextlib import AsyncExitStack, contextmanager from copy import copy, deepcopy from dataclasses import dataclass @@ -73,6 +74,11 @@ from starlette.responses import Response from starlette.websockets import WebSocket from typing_extensions import Annotated, get_args, get_origin +if sys.version_info >= (3, 13): # pragma: no cover + from inspect import iscoroutinefunction +else: # pragma: no cover + from asyncio import iscoroutinefunction + multipart_not_installed_error = ( 'Form data requires "python-multipart" to be installed. \n' 'You can install "python-multipart" with: \n\n' @@ -529,11 +535,11 @@ def add_param_to_fields(*, field: ModelField, dependant: Dependant) -> None: def is_coroutine_callable(call: Callable[..., Any]) -> bool: if inspect.isroutine(call): - return inspect.iscoroutinefunction(call) + return iscoroutinefunction(call) if inspect.isclass(call): return False dunder_call = getattr(call, "__call__", None) # noqa: B004 - return inspect.iscoroutinefunction(dunder_call) + return iscoroutinefunction(dunder_call) def is_async_gen_callable(call: Callable[..., Any]) -> bool: diff --git a/fastapi/routing.py b/fastapi/routing.py index 54c75a027..8b666cf12 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -3,6 +3,7 @@ import dataclasses import email.message import inspect import json +import sys from contextlib import AsyncExitStack, asynccontextmanager from enum import Enum, IntEnum from typing import ( @@ -76,6 +77,10 @@ from starlette.types import AppType, ASGIApp, Lifespan, Scope from starlette.websockets import WebSocket from typing_extensions import Annotated, Doc, deprecated +if sys.version_info >= (3, 13): # pragma: no cover + from inspect import iscoroutinefunction +else: # pragma: no cover + from asyncio import iscoroutinefunction def _prepare_response_content( res: Any, @@ -231,7 +236,7 @@ def get_request_handler( embed_body_fields: bool = False, ) -> Callable[[Request], Coroutine[Any, Any, Response]]: assert dependant.call is not None, "dependant.call must be a function" - is_coroutine = asyncio.iscoroutinefunction(dependant.call) + is_coroutine = iscoroutinefunction(dependant.call) is_body_form = body_field and isinstance(body_field.field_info, params.Form) if isinstance(response_class, DefaultPlaceholder): actual_response_class: Type[Response] = response_class.value From 96cbcb245188873a1c43b4ef06ac24f11aae36a9 Mon Sep 17 00:00:00 2001 From: secrett2633 Date: Sat, 30 Aug 2025 14:42:46 +0000 Subject: [PATCH 2/2] Apply ruff code formatting to iscoroutinefunction --- fastapi/routing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fastapi/routing.py b/fastapi/routing.py index 8b666cf12..6b1144c16 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -1,4 +1,3 @@ -import asyncio import dataclasses import email.message import inspect @@ -80,7 +79,8 @@ from typing_extensions import Annotated, Doc, deprecated if sys.version_info >= (3, 13): # pragma: no cover from inspect import iscoroutinefunction else: # pragma: no cover - from asyncio import iscoroutinefunction + from asyncio import iscoroutinefunction + def _prepare_response_content( res: Any,