Browse Source

🔄 Merge branch 'master' into pr/lucaswiman/5077

pull/5077/head
Sebastián Ramírez 8 months ago
parent
commit
4cac52b371
  1. 9
      docs/en/docs/release-notes.md
  2. 22
      fastapi/dependencies/models.py
  3. 8
      fastapi/routing.py
  4. 77
      tests/test_dependency_wrapped.py

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

@ -7,6 +7,15 @@ hide:
## Latest Changes
### Features
* ✨ Handle wrapped dependencies. PR [#9555](https://github.com/fastapi/fastapi/pull/9555) by [@phy1729](https://github.com/phy1729).
### Refactors
* 🔥 Remove dangling extra condiitonal no longer needed. PR [#14435](https://github.com/fastapi/fastapi/pull/14435) by [@tiangolo](https://github.com/tiangolo).
* ♻️ 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

8
fastapi/routing.py

@ -3,7 +3,6 @@ import email.message
import functools
import inspect
import json
import sys
from contextlib import AsyncExitStack, asynccontextmanager
from enum import Enum, IntEnum
from typing import (
@ -79,11 +78,6 @@ from starlette.types import AppType, ASGIApp, Lifespan, Receive, Scope, Send
from starlette.websockets import WebSocket
from typing_extensions import Annotated, deprecated
if sys.version_info >= (3, 13): # pragma: no cover
from inspect import iscoroutinefunction
else: # pragma: no cover
from asyncio import iscoroutinefunction
# Copy of starlette.routing.request_response modified to include the
# dependencies' AsyncExitStack
@ -308,7 +302,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