Skip to content

✨ Allow decorating async routes with class based decorator#11200

Closed
zzzzlzzzz wants to merge 10 commits into
fastapi:masterfrom
zzzzlzzzz:fix/async-class-based-decorator
Closed

✨ Allow decorating async routes with class based decorator#11200
zzzzlzzzz wants to merge 10 commits into
fastapi:masterfrom
zzzzlzzzz:fix/async-class-based-decorator

Conversation

@zzzzlzzzz

Copy link
Copy Markdown

Hi, I can't use class based decorator with async call, so proposes this changes for review.

@tiangolo tiangolo added feature New feature or request p4 labels Mar 13, 2024
@YuriiMotov

Copy link
Copy Markdown
Member

Alternative solution: #11508

@YuriiMotov YuriiMotov changed the title Fix call async routes decorated with class based decorator ✨ Allow decorating async routes with class based decorator Jun 10, 2025

@YuriiMotov YuriiMotov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

The idea of PR is that for now we can use function-based decorators to decorate route function:

def noop_wrap(func):
    @wraps(func)
    async def wrapper(*args, **kwargs):
        return await func(*args, **kwargs)

    return wrapper

@app.get("/function-decorator")
@noop_wrap  # This works
async def route2():
    return {"working": True}

But we can not use class-based decorators:

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)

@app.get("/class-decorator")
@SomeDecorator  # This doesn't work for now, but this PR fixes this
async def route1():
    return {"working": True}

This PR makes it possible to use class-based decorators to decorate route functions.

Implementation looks nice to me (I simplified the test a bit).

Full tests code in details:

Details
from functools import wraps

from fastapi import FastAPI
from fastapi.testclient import TestClient

app = FastAPI()

def noop_wrap(func):
    @wraps(func)
    async def wrapper(*args, **kwargs):
        return await func(*args, **kwargs)

    return wrapper

@app.get("/function-decorator")
@noop_wrap
async def route2():
    return {"working": True}

client = TestClient(app)

def test_endpoint_with_async_function_decorator():
    response = client.get("/function-decorator")
    assert response.status_code == 200
    assert response.json() == {"working": True}
from functools import update_wrapper

from fastapi import FastAPI
from fastapi.testclient import TestClient

app = FastAPI()

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)

@app.get("/class-decorator")
@SomeDecorator
async def route1():
    return {"working": True}

client = TestClient(app)

def test_endpoint_with_async_class_decorator():
    response = client.get("/class-decorator")
    # ValueError: [TypeError("'coroutine' object is not iterable ...

    assert response.status_code == 200
    assert response.json() == {"working": True}

Comment thread tests/test_endpoint_with_async_class_decorator.py Outdated
Comment thread tests/test_endpoint_with_async_class_decorator.py Outdated
Comment thread tests/test_endpoint_with_async_class_decorator.py Outdated
@github-actions github-actions Bot added the conflicts Automatically generated when a PR has a merge conflict label Sep 20, 2025
@github-actions

This comment was marked as outdated.

@github-actions github-actions Bot removed the conflicts Automatically generated when a PR has a merge conflict label Sep 20, 2025
@github-actions github-actions Bot added the conflicts Automatically generated when a PR has a merge conflict label Oct 11, 2025
@github-actions

This comment was marked as outdated.

@github-actions github-actions Bot removed the conflicts Automatically generated when a PR has a merge conflict label Oct 21, 2025
@github-actions github-actions Bot added the conflicts Automatically generated when a PR has a merge conflict label Dec 2, 2025
@github-actions

github-actions Bot commented Dec 2, 2025

Copy link
Copy Markdown
Contributor

This pull request has a merge conflict that needs to be resolved.

@YuriiMotov

Copy link
Copy Markdown
Member

This was fixed by #9555.

Test passes on master.

Will be available in FastAPI 0.123.5

@YuriiMotov YuriiMotov closed this Dec 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

conflicts Automatically generated when a PR has a merge conflict feature New feature or request p4

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants