From 0b0b4fe4d5220d77b34ee4fe3aa91cd6afd1b1ef Mon Sep 17 00:00:00 2001 From: Fliksh Date: Mon, 26 Feb 2024 14:20:32 +0400 Subject: [PATCH 1/4] Fix call async routes decorated with class based decorator --- fastapi/routing.py | 7 +++- ...est_endpoint_with_async_class_decorator.py | 32 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 tests/test_endpoint_with_async_class_decorator.py diff --git a/fastapi/routing.py b/fastapi/routing.py index 23a32d15f..70dbd0323 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -208,7 +208,12 @@ def get_request_handler( dependency_overrides_provider: Optional[Any] = None, ) -> Callable[[Request], Coroutine[Any, Any, Response]]: assert dependant.call is not None, "dependant.call must be a function" - is_coroutine = asyncio.iscoroutinefunction(dependant.call) + if inspect.isfunction(dependant.call): + is_coroutine = asyncio.iscoroutinefunction(dependant.call) + else: + is_coroutine = asyncio.iscoroutinefunction( + getattr(dependant.call, "__call__", dependant.call) # noqa: B004 + ) 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 diff --git a/tests/test_endpoint_with_async_class_decorator.py b/tests/test_endpoint_with_async_class_decorator.py new file mode 100644 index 000000000..d8ac561d5 --- /dev/null +++ b/tests/test_endpoint_with_async_class_decorator.py @@ -0,0 +1,32 @@ +from functools import update_wrapper + +from fastapi import FastAPI +from fastapi.testclient import TestClient + + +class SomeDecorator: + def __init__(self, original_route): + update_wrapper(wrapper=self, wrapped=original_route) + self.route = original_route + + async def __call__(self, *args, **kwargs): + return await self.route(*args, **kwargs) + + +data = {"working": True} + + +@SomeDecorator +async def route(): + return data + + +app = FastAPI() +app.get("/")(route) + +client = TestClient(app) + + +def test_endpoint_with_async_class_decorator(): + response = client.get("/") + assert data == response.json() From 806965144161be97b1dc73975881aaf401ead611 Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Tue, 22 Jul 2025 19:20:55 +0200 Subject: [PATCH 2/4] Simplify the code of test --- tests/test_endpoint_with_async_class_decorator.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/tests/test_endpoint_with_async_class_decorator.py b/tests/test_endpoint_with_async_class_decorator.py index d8ac561d5..c75823b18 100644 --- a/tests/test_endpoint_with_async_class_decorator.py +++ b/tests/test_endpoint_with_async_class_decorator.py @@ -13,20 +13,14 @@ class SomeDecorator: return await self.route(*args, **kwargs) -data = {"working": True} - - +@app.get("/") @SomeDecorator -async def route(): - return data - - -app = FastAPI() -app.get("/")(route) +async def route1(): + return {"working": True} client = TestClient(app) def test_endpoint_with_async_class_decorator(): response = client.get("/") - assert data == response.json() + assert response.json() == {"working": True} From e44030b95a52b2e12db9bb6306e6e30c36c0acae Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Tue, 22 Jul 2025 19:24:01 +0200 Subject: [PATCH 3/4] Fix test --- tests/test_endpoint_with_async_class_decorator.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_endpoint_with_async_class_decorator.py b/tests/test_endpoint_with_async_class_decorator.py index c75823b18..da322ef66 100644 --- a/tests/test_endpoint_with_async_class_decorator.py +++ b/tests/test_endpoint_with_async_class_decorator.py @@ -13,6 +13,8 @@ class SomeDecorator: return await self.route(*args, **kwargs) +app = FastAPI() + @app.get("/") @SomeDecorator async def route1(): From 2eb2fcbd7611076a063f96f0435a63e4e2d166c2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 22 Jul 2025 17:25:57 +0000 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=8E=A8=20[pre-commit.ci]=20Auto=20for?= =?UTF-8?q?mat=20from=20pre-commit.com=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_endpoint_with_async_class_decorator.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_endpoint_with_async_class_decorator.py b/tests/test_endpoint_with_async_class_decorator.py index da322ef66..a5e225103 100644 --- a/tests/test_endpoint_with_async_class_decorator.py +++ b/tests/test_endpoint_with_async_class_decorator.py @@ -15,11 +15,13 @@ class SomeDecorator: app = FastAPI() + @app.get("/") @SomeDecorator async def route1(): return {"working": True} + client = TestClient(app)