Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Fix async wrappers using @wraps incorrectly detected as sync#14445

Closed
aghabidareh wants to merge 2 commits into
fastapi:masterfrom
aghabidareh:fix/async-wrappers-with-wraps
Closed

Fix async wrappers using @wraps incorrectly detected as sync#14445
aghabidareh wants to merge 2 commits into
fastapi:masterfrom
aghabidareh:fix/async-wrappers-with-wraps

Conversation

@aghabidareh

@aghabidareh aghabidareh commented Dec 3, 2025

Copy link
Copy Markdown

Summary

Fixes #14444

Since FastAPI 0.123.5 (PR #14434), using functools.wraps on an async wrapper around a sync function causes FastAPI to incorrectly treat the handler as sync, resulting in:

ValueError: [TypeError("'coroutine' object is not iterable"), TypeError('vars() argument must have __dict__ attribute')]

Root Cause

The is_coroutine_callable, is_gen_callable, and is_async_gen_callable properties in Dependant use _unwrapped_call (which uses inspect.unwrap()) to determine if a function is async/generator. When a decorator uses @wraps(func), it sets __wrapped__ pointing to the original function, so inspect.unwrap() follows that chain back to the original sync function instead of checking the actual async wrapper.

Fix

Check the actual callable first (not the unwrapped version) to correctly detect if the wrapper itself is async/generator, and only fall back to the unwrapped version for cases like partial.

Example that was broken

from functools import wraps
from fastapi import FastAPI

app = FastAPI()

def my_decorator(func):
    @wraps(func)
    async def wrapper():
        func()
        return 'OK'
    return wrapper

@app.get('/')
@my_decorator
def index():
    print('Hello!')

Test plan

  • Added test case tests/test_async_wrapper_sync_function.py that reproduces the issue
  • Verified existing tests/test_dependency_wrapped.py tests still pass
  • Ran 128 dependency-related tests - all pass

aghabidareh and others added 2 commits December 3, 2025 21:54
When a decorator uses `@wraps(func)` on an async wrapper around a sync
function, FastAPI incorrectly treats the handler as sync because
`is_coroutine_callable` uses `inspect.unwrap()` which follows `__wrapped__`
back to the original sync function.

This fix checks the actual callable first (not the unwrapped version) to
correctly detect if the wrapper itself is async/generator, and only falls
back to the unwrapped version for cases like `partial`.

Fixes #14444
@aghabidareh aghabidareh changed the title 🐛 Fix async wrappers using @wraps incorrectly detected as sync Fix async wrappers using @wraps incorrectly detected as sync Dec 3, 2025
@aghabidareh

Copy link
Copy Markdown
Author

Note: This PR needs the bug label added by a maintainer to pass the CI label check.

@tiangolo

tiangolo commented Dec 4, 2025

Copy link
Copy Markdown
Member

Thanks! I'm doing it here: #14448

So I'll close this one. ☕

@tiangolo tiangolo closed this Dec 4, 2025
@aghabidareh aghabidareh deleted the fix/async-wrappers-with-wraps branch December 4, 2025 06:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FastAPI 0.123.5 breaks async wrappers using @wraps

2 participants