Browse Source

Merge branch 'fastapi:master' into sync-german-docs-2025-11-19

pull/14367/head
Nils-Hero Lindemann 8 months ago
committed by GitHub
parent
commit
547bfec543
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 8
      docs/en/docs/release-notes.md
  2. 22
      fastapi/dependencies/models.py
  3. 6
      fastapi/routing.py
  4. 77
      tests/test_dependency_wrapped.py

8
docs/en/docs/release-notes.md

@ -7,6 +7,14 @@ hide:
## Latest Changes
### Features
* ✨ Handle wrapped dependencies. PR [#9555](https://github.com/fastapi/fastapi/pull/9555) by [@phy1729](https://github.com/phy1729).
### Refactors
* ♻️ Refactor internals, update `is_coroutine` check to reuse internal supported variants (unwrap, check class). PR [#14434](https://github.com/fastapi/fastapi/pull/14434) by [@tiangolo](https://github.com/tiangolo).
## 0.123.4
### Fixes

22
fastapi/dependencies/models.py

@ -75,27 +75,33 @@ class Dependant:
return True
return False
@cached_property
def _unwrapped_call(self) -> Any:
if self.call is None:
return self.call # pragma: no cover
return inspect.unwrap(self.call)
@cached_property
def is_gen_callable(self) -> bool:
if inspect.isgeneratorfunction(self.call):
if inspect.isgeneratorfunction(self._unwrapped_call):
return True
dunder_call = getattr(self.call, "__call__", None) # noqa: B004
dunder_call = getattr(self._unwrapped_call, "__call__", None) # noqa: B004
return inspect.isgeneratorfunction(dunder_call)
@cached_property
def is_async_gen_callable(self) -> bool:
if inspect.isasyncgenfunction(self.call):
if inspect.isasyncgenfunction(self._unwrapped_call):
return True
dunder_call = getattr(self.call, "__call__", None) # noqa: B004
dunder_call = getattr(self._unwrapped_call, "__call__", None) # noqa: B004
return inspect.isasyncgenfunction(dunder_call)
@cached_property
def is_coroutine_callable(self) -> bool:
if inspect.isroutine(self.call):
return iscoroutinefunction(self.call)
if inspect.isclass(self.call):
if inspect.isroutine(self._unwrapped_call):
return iscoroutinefunction(self._unwrapped_call)
if inspect.isclass(self._unwrapped_call):
return False
dunder_call = getattr(self.call, "__call__", None) # noqa: B004
dunder_call = getattr(self._unwrapped_call, "__call__", None) # noqa: B004
return iscoroutinefunction(dunder_call)
@cached_property

6
fastapi/routing.py

@ -80,9 +80,9 @@ from starlette.websockets import WebSocket
from typing_extensions import Annotated, deprecated
if sys.version_info >= (3, 13): # pragma: no cover
from inspect import iscoroutinefunction
pass
else: # pragma: no cover
from asyncio import iscoroutinefunction
pass
# Copy of starlette.routing.request_response modified to include the
@ -308,7 +308,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 = iscoroutinefunction(dependant.call)
is_coroutine = dependant.is_coroutine_callable
is_body_form = body_field and isinstance(
body_field.field_info, (params.Form, temp_pydantic_v1_params.Form)
)

77
tests/test_dependency_wrapped.py

@ -0,0 +1,77 @@
from functools import wraps
from typing import AsyncGenerator, Generator
import pytest
from fastapi import Depends, FastAPI
from fastapi.testclient import TestClient
def noop_wrap(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
app = FastAPI()
@noop_wrap
def wrapped_dependency() -> bool:
return True
@noop_wrap
def wrapped_gen_dependency() -> Generator[bool, None, None]:
yield True
@noop_wrap
async def async_wrapped_dependency() -> bool:
return True
@noop_wrap
async def async_wrapped_gen_dependency() -> AsyncGenerator[bool, None]:
yield True
@app.get("/wrapped-dependency/")
async def get_wrapped_dependency(value: bool = Depends(wrapped_dependency)):
return value
@app.get("/wrapped-gen-dependency/")
async def get_wrapped_gen_dependency(value: bool = Depends(wrapped_gen_dependency)):
return value
@app.get("/async-wrapped-dependency/")
async def get_async_wrapped_dependency(value: bool = Depends(async_wrapped_dependency)):
return value
@app.get("/async-wrapped-gen-dependency/")
async def get_async_wrapped_gen_dependency(
value: bool = Depends(async_wrapped_gen_dependency),
):
return value
client = TestClient(app)
@pytest.mark.parametrize(
"route",
[
"/wrapped-dependency",
"/wrapped-gen-dependency",
"/async-wrapped-dependency",
"/async-wrapped-gen-dependency",
],
)
def test_class_dependency(route):
response = client.get(route)
assert response.status_code == 200, response.text
assert response.json() is True
Loading…
Cancel
Save