From c831cdbde22e2dbaae8bcac4544a5556a5a04b5e Mon Sep 17 00:00:00 2001 From: secrett2633 <65999962+secrett2633@users.noreply.github.com> Date: Sun, 21 Sep 2025 01:30:34 +0900 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Fix=20`inspect.getcoroutinefunct?= =?UTF-8?q?ion()`=20can=20break=20testing=20with=20`unittest.mock.patch()`?= =?UTF-8?q?=20(#14022)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi/dependencies/utils.py | 10 ++++++++-- fastapi/routing.py | 9 +++++++-- 2 files changed, 15 insertions(+), 4 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 5418ad982..f620ced5f 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -1,8 +1,8 @@ -import asyncio 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 +76,11 @@ 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, @@ -232,7 +237,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