Compare commits

...

15 Commits

Author SHA1 Message Date
Motov Yurii
df9b78b5d1 Merge branch 'master' into update-outdated-translation-fr 2026-02-06 09:51:47 +03:00
Sebastián Ramírez
79406a4b04 🔖 Release version 0.128.2 2026-02-05 20:46:37 +01:00
github-actions[bot]
de56c96c64 📝 Update release notes
[skip ci]
2026-02-05 19:42:42 +00:00
Sebastián Ramírez
570e592a03 🌐 Enable Traditional Chinese translations (#14842) 2026-02-05 19:42:18 +00:00
github-actions[bot]
110b45d9b2 📝 Update release notes
[skip ci]
2026-02-05 19:28:17 +00:00
Sebastián Ramírez
72325f698f 🌐 Enable French docs translations (#14841) 2026-02-05 19:27:51 +00:00
github-actions[bot]
8bdbd3725f 📝 Update release notes
[skip ci]
2026-02-05 19:26:13 +00:00
Sebastián Ramírez
23ddf09dd1 🔨 Add max pages to translate to configs (#14840) 2026-02-05 19:25:49 +00:00
github-actions[bot]
6646e2b94f 📝 Update release notes
[skip ci]
2026-02-05 18:42:10 +00:00
Kanetsuna Masaya
c5fd75a321 🐛 Fix using Json[list[str]] type (issue #10997) (#14616)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2026-02-05 18:41:43 +00:00
github-actions[bot]
54f8aeeb9a 📝 Update release notes
[skip ci]
2026-02-05 18:34:59 +00:00
Albin Skott
97145588f5 Add support for PEP695 TypeAliasType (#13920)
Co-authored-by: lokidev <torsten.zielke@protonmail.com>
Co-authored-by: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Yurii Motov <yurii.motov.monte@gmail.com>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2026-02-05 18:34:34 +00:00
github-actions[bot]
0dd42b746e 📝 Update release notes
[skip ci]
2026-02-05 18:23:44 +00:00
Jonathan Fulton
b49435becd Allow Response type hint as dependency annotation (#14794)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2026-02-05 18:23:16 +00:00
Yurii Motov
2d46372a6f Update outdated 2026-02-05 18:40:25 +01:00
13 changed files with 352 additions and 37 deletions

View File

@@ -35,6 +35,11 @@ on:
type: boolean
required: false
default: false
max:
description: Maximum number of items to translate (e.g. 10)
type: number
required: false
default: 10
jobs:
langs:
@@ -115,3 +120,4 @@ jobs:
EN_PATH: ${{ github.event.inputs.en_path }}
COMMAND: ${{ matrix.command }}
COMMIT_IN_PLACE: ${{ github.event.inputs.commit_in_place }}
MAX: ${{ github.event.inputs.max }}

View File

@@ -7,6 +7,17 @@ hide:
## Latest Changes
## 0.128.2
### Features
* ✨ Add support for PEP695 `TypeAliasType`. PR [#13920](https://github.com/fastapi/fastapi/pull/13920) by [@cstruct](https://github.com/cstruct).
* ✨ Allow `Response` type hint as dependency annotation. PR [#14794](https://github.com/fastapi/fastapi/pull/14794) by [@jonathan-fulton](https://github.com/jonathan-fulton).
### Fixes
* 🐛 Fix using `Json[list[str]]` type (issue #10997). PR [#14616](https://github.com/fastapi/fastapi/pull/14616) by [@mkanetsuna](https://github.com/mkanetsuna).
### Docs
* 📝 Update docs for translations. PR [#14830](https://github.com/fastapi/fastapi/pull/14830) by [@tiangolo](https://github.com/tiangolo).
@@ -14,6 +25,8 @@ hide:
### Translations
* 🌐 Enable Traditional Chinese translations. PR [#14842](https://github.com/fastapi/fastapi/pull/14842) by [@tiangolo](https://github.com/tiangolo).
* 🌐 Enable French docs translations. PR [#14841](https://github.com/fastapi/fastapi/pull/14841) by [@tiangolo](https://github.com/tiangolo).
* 🌐 Update translations for fr (translate-page). PR [#14837](https://github.com/fastapi/fastapi/pull/14837) by [@tiangolo](https://github.com/tiangolo).
* 🌐 Update translations for de (update-outdated). PR [#14836](https://github.com/fastapi/fastapi/pull/14836) by [@tiangolo](https://github.com/tiangolo).
* 🌐 Update translations for pt (update-outdated). PR [#14833](https://github.com/fastapi/fastapi/pull/14833) by [@tiangolo](https://github.com/tiangolo).
@@ -26,6 +39,10 @@ hide:
* 🌐 Update translations for uk (update-outdated). PR [#14822](https://github.com/fastapi/fastapi/pull/14822) by [@tiangolo](https://github.com/tiangolo).
* 🔨 Update docs and translations scripts, enable Turkish. PR [#14824](https://github.com/fastapi/fastapi/pull/14824) by [@tiangolo](https://github.com/tiangolo).
### Internal
* 🔨 Add max pages to translate to configs. PR [#14840](https://github.com/fastapi/fastapi/pull/14840) by [@tiangolo](https://github.com/tiangolo).
## 0.128.1
### Features

View File

@@ -317,6 +317,8 @@ extra:
name: de - Deutsch
- link: /es/
name: es - español
- link: /fr/
name: fr - français
- link: /ja/
name: ja - 日本語
- link: /ko/
@@ -329,6 +331,8 @@ extra:
name: tr - Türkçe
- link: /uk/
name: uk - українська мова
- link: /zh-hant/
name: zh-hant - 繁體中文
extra_css:
- css/termynal.css
- css/custom.css

View File

@@ -2,23 +2,23 @@
Maintenant que nous avons vu comment utiliser `Path` et `Query`, voyons des usages plus avancés des déclarations de paramètres du corps de la requête.
## Mélanger les paramètres `Path`, `Query` et body { #mix-path-query-and-body-parameters }
## Mélanger les paramètres `Path`, `Query` et du corps de la requête { #mix-path-query-and-body-parameters }
Tout d'abord, sachez que vous pouvez mélanger librement les déclarations des paramètres `Path`, `Query` et du body, **FastAPI** saura quoi faire.
Tout d'abord, sachez que vous pouvez mélanger librement les déclarations des paramètres `Path`, `Query` et du corps de la requête, **FastAPI** saura quoi faire.
Et vous pouvez également déclarer des paramètres du body comme étant optionnels, en leur assignant une valeur par défaut à `None` :
Et vous pouvez également déclarer des paramètres du corps de la requête comme étant optionnels, en leur assignant une valeur par défaut à `None` :
{* ../../docs_src/body_multiple_params/tutorial001_an_py310.py hl[18:20] *}
/// note | Remarque
Notez que, dans ce cas, l'élément `item` récupéré depuis le body est optionnel. Comme sa valeur par défaut est `None`.
Notez que, dans ce cas, l'élément `item` récupéré depuis le corps de la requête est optionnel. Comme sa valeur par défaut est `None`.
///
## Paramètres multiples du body { #multiple-body-parameters }
## Paramètres multiples du corps de la requête { #multiple-body-parameters }
Dans l'exemple précédent, les chemins d'accès attendraient un body JSON avec les attributs d'un `Item`, par exemple :
Dans l'exemple précédent, les chemins d'accès attendraient un corps de la requête JSON avec les attributs d'un `Item`, par exemple :
```JSON
{
@@ -29,13 +29,13 @@ Dans l'exemple précédent, les chemins d'accès attendraient un body JSON avec
}
```
Mais vous pouvez également déclarer plusieurs paramètres provenant du body, par exemple `item` et `user` :
Mais vous pouvez également déclarer plusieurs paramètres provenant du corps de la requête, par exemple `item` et `user` :
{* ../../docs_src/body_multiple_params/tutorial002_py310.py hl[20] *}
Dans ce cas, **FastAPI** détectera qu'il y a plus d'un paramètre du body dans la fonction (il y a deux paramètres qui sont des modèles Pydantic).
Dans ce cas, **FastAPI** détectera qu'il y a plus d'un paramètre du corps de la requête dans la fonction (il y a deux paramètres qui sont des modèles Pydantic).
Il utilisera alors les noms des paramètres comme clés (noms de champs) dans le body, et s'attendra à recevoir un body semblable à :
Il utilisera alors les noms des paramètres comme clés (noms de champs) dans le corps de la requête, et s'attendra à recevoir un corps de la requête semblable à :
```JSON
{
@@ -54,7 +54,7 @@ Il utilisera alors les noms des paramètres comme clés (noms de champs) dans le
/// note | Remarque
Notez que, bien que `item` ait été déclaré de la même manière qu'auparavant, il est désormais attendu à l'intérieur du body sous la clé `item`.
Notez que, bien que `item` ait été déclaré de la même manière qu'auparavant, il est désormais attendu à l'intérieur du corps de la requête sous la clé `item`.
///
@@ -62,19 +62,19 @@ Notez que, bien que `item` ait été déclaré de la même manière qu'auparavan
Il effectuera la validation des données composées, et les documentera ainsi pour le schéma OpenAPI et la documentation automatique.
## Valeurs singulières dans le body { #singular-values-in-body }
## Valeurs singulières dans le corps de la requête { #singular-values-in-body }
De la même façon qu'il existe `Query` et `Path` pour définir des données supplémentaires pour les paramètres de requête et de chemin, **FastAPI** fournit un équivalent `Body`.
Par exemple, en étendant le modèle précédent, vous pourriez décider d'avoir une autre clé `importance` dans le même body, en plus de `item` et `user`.
Par exemple, en étendant le modèle précédent, vous pourriez décider d'avoir une autre clé `importance` dans le même corps de la requête, en plus de `item` et `user`.
Si vous le déclarez tel quel, comme c'est une valeur singulière, **FastAPI** supposera qu'il s'agit d'un paramètre de requête.
Mais vous pouvez indiquer à **FastAPI** de la traiter comme une autre clé du body en utilisant `Body` :
Mais vous pouvez indiquer à **FastAPI** de la traiter comme une autre clé du corps de la requête en utilisant `Body` :
{* ../../docs_src/body_multiple_params/tutorial003_an_py310.py hl[23] *}
Dans ce cas, **FastAPI** s'attendra à un body semblable à :
Dans ce cas, **FastAPI** s'attendra à un corps de la requête semblable à :
```JSON
{
@@ -94,9 +94,9 @@ Dans ce cas, **FastAPI** s'attendra à un body semblable à :
Encore une fois, il convertira les types de données, validera, documentera, etc.
## Paramètres multiples du body et paramètres de requête { #multiple-body-params-and-query }
## Paramètres multiples du corps de la requête et paramètres de requête { #multiple-body-params-and-query }
Bien entendu, vous pouvez également déclarer des paramètres de requête supplémentaires quand vous en avez besoin, en plus de tout paramètre du body.
Bien entendu, vous pouvez également déclarer des paramètres de requête supplémentaires quand vous en avez besoin, en plus de tout paramètre du corps de la requête.
Comme, par défaut, les valeurs singulières sont interprétées comme des paramètres de requête, vous n'avez pas besoin d'ajouter explicitement `Query`, vous pouvez simplement écrire :
@@ -120,13 +120,13 @@ Par exemple :
///
## Intégrer un seul paramètre du body { #embed-a-single-body-parameter }
## Intégrer un seul paramètre du corps de la requête { #embed-a-single-body-parameter }
Supposons que vous n'ayez qu'un seul paramètre `item` dans le body, provenant d'un modèle Pydantic `Item`.
Supposons que vous n'ayez qu'un seul paramètre `item` dans le corps de la requête, provenant d'un modèle Pydantic `Item`.
Par défaut, **FastAPI** attendra alors son contenu directement.
Mais si vous voulez qu'il attende un JSON avec une clé `item` contenant le contenu du modèle, comme lorsqu'on déclare des paramètres supplémentaires du body, vous pouvez utiliser le paramètre spécial `embed` de `Body` :
Mais si vous voulez qu'il attende un JSON avec une clé `item` contenant le contenu du modèle, comme lorsqu'on déclare des paramètres supplémentaires du corps de la requête, vous pouvez utiliser le paramètre spécial `embed` de `Body` :
```Python
item: Item = Body(embed=True)
@@ -136,7 +136,7 @@ comme dans :
{* ../../docs_src/body_multiple_params/tutorial005_an_py310.py hl[17] *}
Dans ce cas **FastAPI** s'attendra à un body semblable à :
Dans ce cas **FastAPI** s'attendra à un corps de la requête semblable à :
```JSON hl_lines="2"
{
@@ -162,10 +162,10 @@ au lieu de :
## Récapitulatif { #recap }
Vous pouvez ajouter plusieurs paramètres du body à votre fonction de chemin d'accès, même si une requête ne peut avoir qu'un seul body.
Vous pouvez ajouter plusieurs paramètres du corps de la requête à votre fonction de chemin d'accès, même si une requête ne peut avoir qu'un seul corps de la requête.
Mais **FastAPI** s'en chargera, vous fournira les bonnes données dans votre fonction, et validera et documentera le schéma correct dans le chemin d'accès.
Vous pouvez également déclarer des valeurs singulières à recevoir dans le body.
Vous pouvez également déclarer des valeurs singulières à recevoir dans le corps de la requête.
Et vous pouvez indiquer à **FastAPI** d'intégrer le body sous une clé même lorsqu'un seul paramètre est déclaré.
Et vous pouvez indiquer à **FastAPI** d'intégrer le corps de la requête sous une clé même lorsqu'un seul paramètre est déclaré.

View File

@@ -1,6 +1,6 @@
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
__version__ = "0.128.1"
__version__ = "0.128.2"
from starlette import status as status

View File

@@ -51,7 +51,7 @@ from fastapi.logger import logger
from fastapi.security.oauth2 import SecurityScopes
from fastapi.types import DependencyCacheKey
from fastapi.utils import create_model_field, get_path_param_names
from pydantic import BaseModel
from pydantic import BaseModel, Json
from pydantic.fields import FieldInfo
from starlette.background import BackgroundTasks as StarletteBackgroundTasks
from starlette.concurrency import run_in_threadpool
@@ -66,6 +66,7 @@ from starlette.requests import HTTPConnection, Request
from starlette.responses import Response
from starlette.websockets import WebSocket
from typing_extensions import Literal, get_args, get_origin
from typing_inspection.typing_objects import is_typealiastype
multipart_not_installed_error = (
'Form data requires "python-multipart" to be installed. \n'
@@ -370,6 +371,9 @@ def analyze_param(
depends = None
type_annotation: Any = Any
use_annotation: Any = Any
if is_typealiastype(annotation):
# unpack in case PEP 695 type syntax is used
annotation = annotation.__value__
if annotation is not inspect.Signature.empty:
use_annotation = annotation
type_annotation = annotation
@@ -449,7 +453,9 @@ def analyze_param(
depends = dataclasses.replace(depends, dependency=type_annotation)
# Handle non-param type annotations like Request
if lenient_issubclass(
# Only apply special handling when there's no explicit Depends - if there's a Depends,
# the dependency will be called and its return value used instead of the special injection
if depends is None and lenient_issubclass(
type_annotation,
(
Request,
@@ -460,7 +466,6 @@ def analyze_param(
SecurityScopes,
),
):
assert depends is None, f"Cannot specify `Depends` for type {type_annotation!r}"
assert field_info is None, (
f"Cannot specify FastAPI annotation for type {type_annotation!r}"
)
@@ -721,11 +726,19 @@ def _validate_value_with_model_field(
return v_, []
def _is_json_field(field: ModelField) -> bool:
return any(type(item) is Json for item in field.field_info.metadata)
def _get_multidict_value(
field: ModelField, values: Mapping[str, Any], alias: Union[str, None] = None
) -> Any:
alias = alias or get_validation_alias(field)
if is_sequence_field(field) and isinstance(values, (ImmutableMultiDict, Headers)):
if (
(not _is_json_field(field))
and is_sequence_field(field)
and isinstance(values, (ImmutableMultiDict, Headers))
):
value = values.getlist(alias)
else:
value = values.get(alias, None)

View File

@@ -46,6 +46,7 @@ dependencies = [
"starlette>=0.40.0,<0.51.0",
"pydantic>=2.7.0",
"typing-extensions>=4.8.0",
"typing-inspection>=0.4.2",
"annotated-doc>=0.0.2",
]

View File

@@ -23,7 +23,7 @@ SUPPORTED_LANGS = {
"de",
"en",
"es",
# "fr",
"fr",
"ja",
"ko",
"pt",
@@ -31,7 +31,7 @@ SUPPORTED_LANGS = {
"tr",
"uk",
# "zh",
# "zh-hant",
"zh-hant",
}

View File

@@ -347,9 +347,12 @@ def list_outdated(language: str) -> list[Path]:
@app.command()
def update_outdated(language: Annotated[str, typer.Option(envvar="LANGUAGE")]) -> None:
def update_outdated(
language: Annotated[str, typer.Option(envvar="LANGUAGE")],
max: Annotated[int, typer.Option(envvar="MAX")] = 10,
) -> None:
outdated_paths = list_outdated(language)
for path in outdated_paths:
for path in outdated_paths[:max]:
print(f"Updating lang: {language} path: {path}")
translate_page(language=language, en_path=path)
print(f"Done updating: {path}")
@@ -357,9 +360,12 @@ def update_outdated(language: Annotated[str, typer.Option(envvar="LANGUAGE")]) -
@app.command()
def add_missing(language: Annotated[str, typer.Option(envvar="LANGUAGE")]) -> None:
def add_missing(
language: Annotated[str, typer.Option(envvar="LANGUAGE")],
max: Annotated[int, typer.Option(envvar="MAX")] = 10,
) -> None:
missing_paths = list_missing(language)
for path in missing_paths:
for path in missing_paths[:max]:
print(f"Adding lang: {language} path: {path}")
translate_page(language=language, en_path=path)
print(f"Done adding: {path}")
@@ -367,11 +373,14 @@ def add_missing(language: Annotated[str, typer.Option(envvar="LANGUAGE")]) -> No
@app.command()
def update_and_add(language: Annotated[str, typer.Option(envvar="LANGUAGE")]) -> None:
def update_and_add(
language: Annotated[str, typer.Option(envvar="LANGUAGE")],
max: Annotated[int, typer.Option(envvar="MAX")] = 10,
) -> None:
print(f"Updating outdated translations for {language}")
update_outdated(language=language)
update_outdated(language=language, max=max)
print(f"Adding missing translations for {language}")
add_missing(language=language)
add_missing(language=language, max=max)
print(f"Done updating and adding for {language}")

View File

@@ -0,0 +1,27 @@
from typing import Annotated
from fastapi import Depends, FastAPI
from fastapi.testclient import TestClient
from typing_extensions import TypeAliasType
async def some_value() -> int:
return 123
DependedValue = TypeAliasType(
"DependedValue", Annotated[int, Depends(some_value)], type_params=()
)
def test_pep695_type_dependencies():
app = FastAPI()
@app.get("/")
async def get_with_dep(value: DependedValue) -> str: # noqa
return f"value: {value}"
client = TestClient(app)
response = client.get("/")
assert response.status_code == 200
assert response.text == '"value: 123"'

63
tests/test_json_type.py Normal file
View File

@@ -0,0 +1,63 @@
import json
from typing import Annotated
from fastapi import Cookie, FastAPI, Form, Header, Query
from fastapi.testclient import TestClient
from pydantic import Json
app = FastAPI()
@app.post("/form-json-list")
def form_json_list(items: Annotated[Json[list[str]], Form()]) -> list[str]:
return items
@app.get("/query-json-list")
def query_json_list(items: Annotated[Json[list[str]], Query()]) -> list[str]:
return items
@app.get("/header-json-list")
def header_json_list(x_items: Annotated[Json[list[str]], Header()]) -> list[str]:
return x_items
@app.get("/cookie-json-list")
def cookie_json_list(items: Annotated[Json[list[str]], Cookie()]) -> list[str]:
return items
client = TestClient(app)
def test_form_json_list():
response = client.post(
"/form-json-list", data={"items": json.dumps(["abc", "def"])}
)
assert response.status_code == 200, response.text
assert response.json() == ["abc", "def"]
def test_query_json_list():
response = client.get(
"/query-json-list", params={"items": json.dumps(["abc", "def"])}
)
assert response.status_code == 200, response.text
assert response.json() == ["abc", "def"]
def test_header_json_list():
response = client.get(
"/header-json-list", headers={"x-items": json.dumps(["abc", "def"])}
)
assert response.status_code == 200, response.text
assert response.json() == ["abc", "def"]
def test_cookie_json_list():
client.cookies.set("items", json.dumps(["abc", "def"]))
response = client.get("/cookie-json-list")
assert response.status_code == 200, response.text
assert response.json() == ["abc", "def"]
client.cookies.clear()

View File

@@ -0,0 +1,173 @@
"""Test using special types (Response, Request, BackgroundTasks) as dependency annotations.
These tests verify that special FastAPI types can be used with Depends() annotations
and that the dependency injection system properly handles them.
"""
from typing import Annotated
from fastapi import BackgroundTasks, Depends, FastAPI, Request, Response
from fastapi.responses import JSONResponse
from fastapi.testclient import TestClient
def test_response_with_depends_annotated():
"""Response type hint should work with Annotated[Response, Depends(...)]."""
app = FastAPI()
def modify_response(response: Response) -> Response:
response.headers["X-Custom"] = "modified"
return response
@app.get("/")
def endpoint(response: Annotated[Response, Depends(modify_response)]):
return {"status": "ok"}
client = TestClient(app)
resp = client.get("/")
assert resp.status_code == 200
assert resp.json() == {"status": "ok"}
assert resp.headers.get("X-Custom") == "modified"
def test_response_with_depends_default():
"""Response type hint should work with Response = Depends(...)."""
app = FastAPI()
def modify_response(response: Response) -> Response:
response.headers["X-Custom"] = "modified"
return response
@app.get("/")
def endpoint(response: Response = Depends(modify_response)):
return {"status": "ok"}
client = TestClient(app)
resp = client.get("/")
assert resp.status_code == 200
assert resp.json() == {"status": "ok"}
assert resp.headers.get("X-Custom") == "modified"
def test_response_without_depends():
"""Regular Response injection should still work."""
app = FastAPI()
@app.get("/")
def endpoint(response: Response):
response.headers["X-Direct"] = "set"
return {"status": "ok"}
client = TestClient(app)
resp = client.get("/")
assert resp.status_code == 200
assert resp.json() == {"status": "ok"}
assert resp.headers.get("X-Direct") == "set"
def test_response_dependency_chain():
"""Response dependency should work in a chain of dependencies."""
app = FastAPI()
def first_modifier(response: Response) -> Response:
response.headers["X-First"] = "1"
return response
def second_modifier(
response: Annotated[Response, Depends(first_modifier)],
) -> Response:
response.headers["X-Second"] = "2"
return response
@app.get("/")
def endpoint(response: Annotated[Response, Depends(second_modifier)]):
return {"status": "ok"}
client = TestClient(app)
resp = client.get("/")
assert resp.status_code == 200
assert resp.headers.get("X-First") == "1"
assert resp.headers.get("X-Second") == "2"
def test_response_dependency_returns_different_response_instance():
"""Dependency that returns a different Response instance should work.
When a dependency returns a new Response object (e.g., JSONResponse) instead
of modifying the injected one, the returned response should be used and any
modifications to it in the endpoint should be preserved.
"""
app = FastAPI()
def default_response() -> Response:
response = JSONResponse(content={"status": "ok"})
response.headers["X-Custom"] = "initial"
return response
@app.get("/")
def endpoint(response: Annotated[Response, Depends(default_response)]):
response.headers["X-Custom"] = "modified"
return response
client = TestClient(app)
resp = client.get("/")
assert resp.status_code == 200
assert resp.json() == {"status": "ok"}
assert resp.headers.get("X-Custom") == "modified"
# Tests for Request type hint with Depends
def test_request_with_depends_annotated():
"""Request type hint should work in dependency chain."""
app = FastAPI()
def extract_request_info(request: Request) -> dict:
return {
"path": request.url.path,
"user_agent": request.headers.get("user-agent", "unknown"),
}
@app.get("/")
def endpoint(
info: Annotated[dict, Depends(extract_request_info)],
):
return info
client = TestClient(app)
resp = client.get("/", headers={"user-agent": "test-agent"})
assert resp.status_code == 200
assert resp.json() == {"path": "/", "user_agent": "test-agent"}
# Tests for BackgroundTasks type hint with Depends
def test_background_tasks_with_depends_annotated():
"""BackgroundTasks type hint should work with Annotated[BackgroundTasks, Depends(...)]."""
app = FastAPI()
task_results = []
def background_task(message: str):
task_results.append(message)
def add_background_task(background_tasks: BackgroundTasks) -> BackgroundTasks:
background_tasks.add_task(background_task, "from dependency")
return background_tasks
@app.get("/")
def endpoint(
background_tasks: Annotated[BackgroundTasks, Depends(add_background_task)],
):
background_tasks.add_task(background_task, "from endpoint")
return {"status": "ok"}
client = TestClient(app)
resp = client.get("/")
assert resp.status_code == 200
assert "from dependency" in task_results
assert "from endpoint" in task_results

2
uv.lock generated
View File

@@ -1021,6 +1021,7 @@ dependencies = [
{ name = "starlette", version = "0.49.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
{ name = "starlette", version = "0.50.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
{ name = "typing-extensions" },
{ name = "typing-inspection" },
]
[package.optional-dependencies]
@@ -1202,6 +1203,7 @@ requires-dist = [
{ name = "pyyaml", marker = "extra == 'all'", specifier = ">=5.3.1" },
{ name = "starlette", specifier = ">=0.40.0,<0.51.0" },
{ name = "typing-extensions", specifier = ">=4.8.0" },
{ name = "typing-inspection", specifier = ">=0.4.2" },
{ name = "ujson", marker = "extra == 'all'", specifier = ">=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0" },
{ name = "uvicorn", extras = ["standard"], marker = "extra == 'all'", specifier = ">=0.12.0" },
{ name = "uvicorn", extras = ["standard"], marker = "extra == 'standard'", specifier = ">=0.12.0" },