diff --git a/.github/actions/people/app/main.py b/.github/actions/people/app/main.py
index 33156f1ca..b752d9d2b 100644
--- a/.github/actions/people/app/main.py
+++ b/.github/actions/people/app/main.py
@@ -515,9 +515,9 @@ def get_individual_sponsors(settings: Settings):
tiers: DefaultDict[float, Dict[str, SponsorEntity]] = defaultdict(dict)
for node in nodes:
- tiers[node.tier.monthlyPriceInDollars][
- node.sponsorEntity.login
- ] = node.sponsorEntity
+ tiers[node.tier.monthlyPriceInDollars][node.sponsorEntity.login] = (
+ node.sponsorEntity
+ )
return tiers
diff --git a/.github/labeler.yml b/.github/labeler.yml
new file mode 100644
index 000000000..c5b1f84f3
--- /dev/null
+++ b/.github/labeler.yml
@@ -0,0 +1,38 @@
+docs:
+ - all:
+ - changed-files:
+ - any-glob-to-any-file:
+ - docs/en/docs/**
+ - docs_src/**
+ - all-globs-to-all-files:
+ - '!fastapi/**'
+ - '!pyproject.toml'
+ - '!docs/en/data/sponsors.yml'
+ - '!docs/en/overrides/main.html'
+
+lang-all:
+ - all:
+ - changed-files:
+ - any-glob-to-any-file:
+ - docs/*/docs/**
+ - all-globs-to-all-files:
+ - '!docs/en/docs/**'
+ - '!fastapi/**'
+ - '!pyproject.toml'
+
+internal:
+ - all:
+ - changed-files:
+ - any-glob-to-any-file:
+ - .github/**
+ - scripts/**
+ - .gitignore
+ - .pre-commit-config.yaml
+ - pdm_build.py
+ - requirements*.txt
+ - docs/en/data/sponsors.yml
+ - docs/en/overrides/main.html
+ - all-globs-to-all-files:
+ - '!docs/*/docs/**'
+ - '!fastapi/**'
+ - '!pyproject.toml'
diff --git a/.github/workflows/add-to-project.yml b/.github/workflows/add-to-project.yml
new file mode 100644
index 000000000..dccea83f3
--- /dev/null
+++ b/.github/workflows/add-to-project.yml
@@ -0,0 +1,18 @@
+name: Add to Project
+
+on:
+ pull_request_target:
+ issues:
+ types:
+ - opened
+ - reopened
+
+jobs:
+ add-to-project:
+ name: Add to project
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/add-to-project@v1.0.2
+ with:
+ project-url: https://github.com/orgs/fastapi/projects/2
+ github-token: ${{ secrets.PROJECTS_TOKEN }}
diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml
index e46629e9b..52c34a49e 100644
--- a/.github/workflows/build-docs.yml
+++ b/.github/workflows/build-docs.yml
@@ -113,6 +113,7 @@ jobs:
with:
name: docs-site-${{ matrix.lang }}
path: ./site/**
+ include-hidden-files: true
# https://github.com/marketplace/actions/alls-green#why
docs-all-green: # This job does nothing and is only used for the branch protection
diff --git a/.github/workflows/issue-manager.yml b/.github/workflows/issue-manager.yml
index d5b947a9c..439084434 100644
--- a/.github/workflows/issue-manager.yml
+++ b/.github/workflows/issue-manager.yml
@@ -2,7 +2,7 @@ name: Issue Manager
on:
schedule:
- - cron: "10 3 * * *"
+ - cron: "13 22 * * *"
issue_comment:
types:
- created
@@ -16,6 +16,7 @@ on:
permissions:
issues: write
+ pull-requests: write
jobs:
issue-manager:
@@ -26,7 +27,7 @@ jobs:
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- - uses: tiangolo/issue-manager@0.5.0
+ - uses: tiangolo/issue-manager@0.5.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
config: >
@@ -35,8 +36,8 @@ jobs:
"delay": 864000,
"message": "Assuming the original need was handled, this will be automatically closed now. But feel free to add more comments or create new issues or PRs."
},
- "changes-requested": {
+ "waiting": {
"delay": 2628000,
- "message": "As this PR had requested changes to be applied but has been inactive for a while, it's now going to be closed. But if there's anyone interested, feel free to create a new PR."
+ "message": "As this PR has been waiting for the original user for a while but seems to be inactive, it's now going to be closed. But if there's anyone interested, feel free to create a new PR."
}
}
diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml
new file mode 100644
index 000000000..c3bb83f9a
--- /dev/null
+++ b/.github/workflows/labeler.yml
@@ -0,0 +1,31 @@
+name: Labels
+on:
+ pull_request_target:
+ types:
+ - opened
+ - synchronize
+ - reopened
+ # For label-checker
+ - labeled
+ - unlabeled
+
+jobs:
+ labeler:
+ permissions:
+ contents: read
+ pull-requests: write
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/labeler@v5
+ # Run this after labeler applied labels
+ check-labels:
+ needs:
+ - labeler
+ permissions:
+ pull-requests: read
+ runs-on: ubuntu-latest
+ steps:
+ - uses: docker://agilepathway/pull-request-label-checker:latest
+ with:
+ one_of: breaking,security,feature,bug,refactor,upgrade,docs,lang-all,internal
+ repo_token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/latest-changes.yml b/.github/workflows/latest-changes.yml
index 27e062d09..16da3bc63 100644
--- a/.github/workflows/latest-changes.yml
+++ b/.github/workflows/latest-changes.yml
@@ -34,8 +34,7 @@ jobs:
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
with:
limit-access-to-actor: true
- - uses: docker://tiangolo/latest-changes:0.3.0
- # - uses: tiangolo/latest-changes@main
+ - uses: tiangolo/latest-changes@0.3.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
latest_changes_file: docs/en/docs/release-notes.md
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index e7c69befc..5004b94dd 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -35,7 +35,7 @@ jobs:
TIANGOLO_BUILD_PACKAGE: ${{ matrix.package }}
run: python -m build
- name: Publish
- uses: pypa/gh-action-pypi-publish@v1.8.14
+ uses: pypa/gh-action-pypi-publish@v1.10.1
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index af1ba6a8d..6fe2e4d7d 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -37,7 +37,7 @@ jobs:
if: steps.cache.outputs.cache-hit != 'true'
run: pip install -r requirements-tests.txt
- name: Install Pydantic v2
- run: pip install "pydantic>=2.0.2,<3.0.0"
+ run: pip install --upgrade "pydantic>=2.0.2,<3.0.0"
- name: Lint
run: bash scripts/lint.sh
@@ -94,6 +94,7 @@ jobs:
with:
name: coverage-${{ matrix.python-version }}-${{ matrix.pydantic-version }}
path: coverage
+ include-hidden-files: true
coverage-combine:
needs: [test]
@@ -120,12 +121,13 @@ jobs:
- run: ls -la coverage
- run: coverage combine coverage
- run: coverage report
- - run: coverage html --show-contexts --title "Coverage for ${{ github.sha }}"
+ - run: coverage html --title "Coverage for ${{ github.sha }}"
- name: Store coverage HTML
uses: actions/upload-artifact@v4
with:
name: coverage-html
path: htmlcov
+ include-hidden-files: true
# https://github.com/marketplace/actions/alls-green#why
check: # This job does nothing and is only used for the branch protection
diff --git a/.gitignore b/.gitignore
index 9be494cec..ef6364a9a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,7 +7,7 @@ __pycache__
htmlcov
dist
site
-.coverage
+.coverage*
coverage.xml
.netlify
test.db
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 4d49845d6..f74816f12 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -4,7 +4,7 @@ default_language_version:
python: python3.10
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v4.4.0
+ rev: v4.6.0
hooks:
- id: check-added-large-files
- id: check-toml
@@ -13,8 +13,8 @@ repos:
- --unsafe
- id: end-of-file-fixer
- id: trailing-whitespace
-- repo: https://github.com/charliermarsh/ruff-pre-commit
- rev: v0.2.0
+- repo: https://github.com/astral-sh/ruff-pre-commit
+ rev: v0.6.4
hooks:
- id: ruff
args:
diff --git a/README.md b/README.md
index aa70ff2da..3b01b713a 100644
--- a/README.md
+++ b/README.md
@@ -52,9 +52,8 @@ The key features are:
-
+
-
@@ -132,6 +131,8 @@ FastAPI stands on the shoulders of giants:
## Installation
+Create and activate a virtual environment and then install FastAPI:
+
email_validator
- for email validation.
+* email-validator
- for email validation.
Used by Starlette:
diff --git a/docs/az/docs/fastapi-people.md b/docs/az/docs/fastapi-people.md
deleted file mode 100644
index 9bb7ad6ea..000000000
--- a/docs/az/docs/fastapi-people.md
+++ /dev/null
@@ -1,185 +0,0 @@
----
-hide:
- - navigation
----
-
-# FastAPI İnsanlar
-
-FastAPI-ın bütün mənşəli insanları qəbul edən heyrətamiz icması var.
-
-
-
-## Yaradıcı - İcraçı
-
-Salam! 👋
-
-Bu mənəm:
-
-{% if people %}
-email_validator
- e-poçtun yoxlanılması üçün.
+* email-validator
- e-poçtun yoxlanılması üçün.
* pydantic-settings
- parametrlərin idarə edilməsi üçün.
* pydantic-extra-types
- Pydantic ilə istifadə edilə bilən əlavə tiplər üçün.
diff --git a/docs/bn/docs/index.md b/docs/bn/docs/index.md
index 042cf9399..c882506ff 100644
--- a/docs/bn/docs/index.md
+++ b/docs/bn/docs/index.md
@@ -439,7 +439,7 @@ item: Item
Pydantic দ্বারা ব্যবহৃত:
-- email_validator
- ইমেল যাচাইকরণের জন্য।
+- email-validator
- ইমেল যাচাইকরণের জন্য।
স্টারলেট দ্বারা ব্যবহৃত:
diff --git a/docs/de/docs/advanced/async-tests.md b/docs/de/docs/advanced/async-tests.md
index 9f0bd4aa2..e56841faa 100644
--- a/docs/de/docs/advanced/async-tests.md
+++ b/docs/de/docs/advanced/async-tests.md
@@ -72,7 +72,7 @@ Beachten Sie, dass die Testfunktion jetzt `async def` ist und nicht nur `def` wi
Dann können wir einen `AsyncClient` mit der App erstellen und mit `await` asynchrone Requests an ihn senden.
-```Python hl_lines="9-10"
+```Python hl_lines="9-12"
{!../../../docs_src/async_tests/test_main.py!}
```
diff --git a/docs/de/docs/advanced/middleware.md b/docs/de/docs/advanced/middleware.md
index 4116b30ec..8912225fb 100644
--- a/docs/de/docs/advanced/middleware.md
+++ b/docs/de/docs/advanced/middleware.md
@@ -95,7 +95,6 @@ Es gibt viele andere ASGI-Middlewares.
Zum Beispiel:
-* Sentry
* Uvicorns `ProxyHeadersMiddleware`
* MessagePack
diff --git a/docs/de/docs/deployment/docker.md b/docs/de/docs/deployment/docker.md
index 2186d16c5..c11dc4127 100644
--- a/docs/de/docs/deployment/docker.md
+++ b/docs/de/docs/deployment/docker.md
@@ -205,8 +205,11 @@ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
Die Option `--no-cache-dir` weist `pip` an, die heruntergeladenen Pakete nicht lokal zu speichern, da dies nur benötigt wird, sollte `pip` erneut ausgeführt werden, um dieselben Pakete zu installieren, aber das ist beim Arbeiten mit Containern nicht der Fall.
- !!! note "Hinweis"
- Das `--no-cache-dir` bezieht sich nur auf `pip`, es hat nichts mit Docker oder Containern zu tun.
+ /// note | Hinweis
+
+ Das `--no-cache-dir` bezieht sich nur auf `pip`, es hat nichts mit Docker oder Containern zu tun.
+
+ ///
Die Option `--upgrade` weist `pip` an, die Packages zu aktualisieren, wenn sie bereits installiert sind.
diff --git a/docs/de/docs/external-links.md b/docs/de/docs/external-links.md
deleted file mode 100644
index ae5a6c908..000000000
--- a/docs/de/docs/external-links.md
+++ /dev/null
@@ -1,42 +0,0 @@
-# Externe Links und Artikel
-
-**FastAPI** hat eine großartige Community, die ständig wächst.
-
-Es gibt viele Beiträge, Artikel, Tools und Projekte zum Thema **FastAPI**.
-
-Hier ist eine unvollständige Liste einiger davon.
-
-/// tip | "Tipp"
-
-Wenn Sie einen Artikel, ein Projekt, ein Tool oder irgendetwas im Zusammenhang mit **FastAPI** haben, was hier noch nicht aufgeführt ist, erstellen Sie einen Pull Request und fügen Sie es hinzu.
-
-///
-
-/// note | "Hinweis Deutsche Übersetzung"
-
-Die folgenden Überschriften und Links werden aus einer anderen Datei gelesen und sind daher nicht ins Deutsche übersetzt.
-
-///
-
-{% for section_name, section_content in external_links.items() %}
-
-## {{ section_name }}
-
-{% for lang_name, lang_content in section_content.items() %}
-
-### {{ lang_name }}
-
-{% for item in lang_content %}
-
-* {{ item.title }} by {{ item.author }}.
-
-{% endfor %}
-{% endfor %}
-{% endfor %}
-
-## Projekte
-
-Die neuesten GitHub-Projekte zum Thema `fastapi`:
-
-email_validator
- für E-Mail-Validierung.
+* email-validator
- für E-Mail-Validierung.
* pydantic-settings
- für die Verwaltung von Einstellungen.
* pydantic-extra-types
- für zusätzliche Typen, mit Pydantic zu verwenden.
diff --git a/docs/de/docs/newsletter.md b/docs/de/docs/newsletter.md
deleted file mode 100644
index 31995b164..000000000
--- a/docs/de/docs/newsletter.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# FastAPI und Freunde Newsletter
-
-
-
-
diff --git a/docs/de/docs/reference/apirouter.md b/docs/de/docs/reference/apirouter.md
deleted file mode 100644
index b0728b7df..000000000
--- a/docs/de/docs/reference/apirouter.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# `APIRouter`-Klasse
-
-Hier sind die Referenzinformationen für die Klasse `APIRouter` mit all ihren Parametern, Attributen und Methoden.
-
-Sie können die `APIRouter`-Klasse direkt von `fastapi` importieren:
-
-```python
-from fastapi import APIRouter
-```
-
-::: fastapi.APIRouter
- options:
- members:
- - websocket
- - include_router
- - get
- - put
- - post
- - delete
- - options
- - head
- - patch
- - trace
- - on_event
diff --git a/docs/de/docs/reference/background.md b/docs/de/docs/reference/background.md
deleted file mode 100644
index 0fd389325..000000000
--- a/docs/de/docs/reference/background.md
+++ /dev/null
@@ -1,11 +0,0 @@
-# Hintergrundtasks – `BackgroundTasks`
-
-Sie können einen Parameter in einer *Pfadoperation-Funktion* oder einer Abhängigkeitsfunktion mit dem Typ `BackgroundTasks` deklarieren und diesen danach verwenden, um die Ausführung von Hintergrundtasks nach dem Senden der Response zu definieren.
-
-Sie können `BackgroundTasks` direkt von `fastapi` importieren:
-
-```python
-from fastapi import BackgroundTasks
-```
-
-::: fastapi.BackgroundTasks
diff --git a/docs/de/docs/reference/dependencies.md b/docs/de/docs/reference/dependencies.md
deleted file mode 100644
index 2ed5b5050..000000000
--- a/docs/de/docs/reference/dependencies.md
+++ /dev/null
@@ -1,29 +0,0 @@
-# Abhängigkeiten – `Depends()` und `Security()`
-
-## `Depends()`
-
-Abhängigkeiten werden hauptsächlich mit der speziellen Funktion `Depends()` behandelt, die ein Callable entgegennimmt.
-
-Hier finden Sie deren Referenz und Parameter.
-
-Sie können sie direkt von `fastapi` importieren:
-
-```python
-from fastapi import Depends
-```
-
-::: fastapi.Depends
-
-## `Security()`
-
-In vielen Szenarien können Sie die Sicherheit (Autorisierung, Authentifizierung usw.) mit Abhängigkeiten handhaben, indem Sie `Depends()` verwenden.
-
-Wenn Sie jedoch auch OAuth2-Scopes deklarieren möchten, können Sie `Security()` anstelle von `Depends()` verwenden.
-
-Sie können `Security()` direkt von `fastapi` importieren:
-
-```python
-from fastapi import Security
-```
-
-::: fastapi.Security
diff --git a/docs/de/docs/reference/encoders.md b/docs/de/docs/reference/encoders.md
deleted file mode 100644
index 2489b8c60..000000000
--- a/docs/de/docs/reference/encoders.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Encoder – `jsonable_encoder`
-
-::: fastapi.encoders.jsonable_encoder
diff --git a/docs/de/docs/reference/exceptions.md b/docs/de/docs/reference/exceptions.md
deleted file mode 100644
index 230f902a9..000000000
--- a/docs/de/docs/reference/exceptions.md
+++ /dev/null
@@ -1,20 +0,0 @@
-# Exceptions – `HTTPException` und `WebSocketException`
-
-Dies sind die Exceptions, die Sie auslösen können, um dem Client Fehler zu berichten.
-
-Wenn Sie eine Exception auslösen, wird, wie es bei normalem Python der Fall wäre, der Rest der Ausführung abgebrochen. Auf diese Weise können Sie diese Exceptions von überall im Code werfen, um einen Request abzubrechen und den Fehler dem Client anzuzeigen.
-
-Sie können Folgendes verwenden:
-
-* `HTTPException`
-* `WebSocketException`
-
-Diese Exceptions können direkt von `fastapi` importiert werden:
-
-```python
-from fastapi import HTTPException, WebSocketException
-```
-
-::: fastapi.HTTPException
-
-::: fastapi.WebSocketException
diff --git a/docs/de/docs/reference/fastapi.md b/docs/de/docs/reference/fastapi.md
deleted file mode 100644
index 4e6a56971..000000000
--- a/docs/de/docs/reference/fastapi.md
+++ /dev/null
@@ -1,31 +0,0 @@
-# `FastAPI`-Klasse
-
-Hier sind die Referenzinformationen für die Klasse `FastAPI` mit all ihren Parametern, Attributen und Methoden.
-
-Sie können die `FastAPI`-Klasse direkt von `fastapi` importieren:
-
-```python
-from fastapi import FastAPI
-```
-
-::: fastapi.FastAPI
- options:
- members:
- - openapi_version
- - webhooks
- - state
- - dependency_overrides
- - openapi
- - websocket
- - include_router
- - get
- - put
- - post
- - delete
- - options
- - head
- - patch
- - trace
- - on_event
- - middleware
- - exception_handler
diff --git a/docs/de/docs/reference/httpconnection.md b/docs/de/docs/reference/httpconnection.md
deleted file mode 100644
index 32a9696fa..000000000
--- a/docs/de/docs/reference/httpconnection.md
+++ /dev/null
@@ -1,11 +0,0 @@
-# `HTTPConnection`-Klasse
-
-Wenn Sie Abhängigkeiten definieren möchten, die sowohl mit HTTP als auch mit WebSockets kompatibel sein sollen, können Sie einen Parameter definieren, der eine `HTTPConnection` anstelle eines `Request` oder eines `WebSocket` akzeptiert.
-
-Sie können diese von `fastapi.requests` importieren:
-
-```python
-from fastapi.requests import HTTPConnection
-```
-
-::: fastapi.requests.HTTPConnection
diff --git a/docs/de/docs/reference/index.md b/docs/de/docs/reference/index.md
deleted file mode 100644
index 6fd0ef15f..000000000
--- a/docs/de/docs/reference/index.md
+++ /dev/null
@@ -1,11 +0,0 @@
-# Referenz – Code-API
-
-Hier ist die Referenz oder Code-API, die Klassen, Funktionen, Parameter, Attribute und alle FastAPI-Teile, die Sie in Ihren Anwendungen verwenden können.
-
-Wenn Sie **FastAPI** lernen möchten, ist es viel besser, das [FastAPI-Tutorial](https://fastapi.tiangolo.com/tutorial/) zu lesen.
-
-/// note | "Hinweis Deutsche Übersetzung"
-
-Die nachfolgende API wird aus der Quelltext-Dokumentation erstellt, daher sind nur die Einleitungen auf Deutsch.
-
-///
diff --git a/docs/de/docs/reference/middleware.md b/docs/de/docs/reference/middleware.md
deleted file mode 100644
index d8d2d50fc..000000000
--- a/docs/de/docs/reference/middleware.md
+++ /dev/null
@@ -1,45 +0,0 @@
-# Middleware
-
-Es gibt mehrere Middlewares, die direkt von Starlette bereitgestellt werden.
-
-Lesen Sie mehr darüber in der [FastAPI-Dokumentation über Middleware](../advanced/middleware.md).
-
-::: fastapi.middleware.cors.CORSMiddleware
-
-Kann von `fastapi` importiert werden:
-
-```python
-from fastapi.middleware.cors import CORSMiddleware
-```
-
-::: fastapi.middleware.gzip.GZipMiddleware
-
-Kann von `fastapi` importiert werden:
-
-```python
-from fastapi.middleware.gzip import GZipMiddleware
-```
-
-::: fastapi.middleware.httpsredirect.HTTPSRedirectMiddleware
-
-Kann von `fastapi` importiert werden:
-
-```python
-from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware
-```
-
-::: fastapi.middleware.trustedhost.TrustedHostMiddleware
-
-Kann von `fastapi` importiert werden:
-
-```python
-from fastapi.middleware.trustedhost import TrustedHostMiddleware
-```
-
-::: fastapi.middleware.wsgi.WSGIMiddleware
-
-Kann von `fastapi` importiert werden:
-
-```python
-from fastapi.middleware.wsgi import WSGIMiddleware
-```
diff --git a/docs/de/docs/reference/openapi/docs.md b/docs/de/docs/reference/openapi/docs.md
deleted file mode 100644
index 3c19ba917..000000000
--- a/docs/de/docs/reference/openapi/docs.md
+++ /dev/null
@@ -1,11 +0,0 @@
-# OpenAPI `docs`
-
-Werkzeuge zur Verwaltung der automatischen OpenAPI-UI-Dokumentation, einschließlich Swagger UI (standardmäßig unter `/docs`) und ReDoc (standardmäßig unter `/redoc`).
-
-::: fastapi.openapi.docs.get_swagger_ui_html
-
-::: fastapi.openapi.docs.get_redoc_html
-
-::: fastapi.openapi.docs.get_swagger_ui_oauth2_redirect_html
-
-::: fastapi.openapi.docs.swagger_ui_default_parameters
diff --git a/docs/de/docs/reference/openapi/index.md b/docs/de/docs/reference/openapi/index.md
deleted file mode 100644
index 0ae3d67c6..000000000
--- a/docs/de/docs/reference/openapi/index.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# OpenAPI
-
-Es gibt mehrere Werkzeuge zur Handhabung von OpenAPI.
-
-Normalerweise müssen Sie diese nicht verwenden, es sei denn, Sie haben einen bestimmten fortgeschrittenen Anwendungsfall, welcher das erfordert.
diff --git a/docs/de/docs/reference/openapi/models.md b/docs/de/docs/reference/openapi/models.md
deleted file mode 100644
index 64306b15f..000000000
--- a/docs/de/docs/reference/openapi/models.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# OpenAPI-`models`
-
-OpenAPI Pydantic-Modelle, werden zum Generieren und Validieren der generierten OpenAPI verwendet.
-
-::: fastapi.openapi.models
diff --git a/docs/de/docs/reference/parameters.md b/docs/de/docs/reference/parameters.md
deleted file mode 100644
index 2638eaf48..000000000
--- a/docs/de/docs/reference/parameters.md
+++ /dev/null
@@ -1,35 +0,0 @@
-# Request-Parameter
-
-Hier die Referenzinformationen für die Request-Parameter.
-
-Dies sind die Sonderfunktionen, die Sie mittels `Annotated` in *Pfadoperation-Funktion*-Parameter oder Abhängigkeitsfunktionen einfügen können, um Daten aus dem Request abzurufen.
-
-Dies beinhaltet:
-
-* `Query()`
-* `Path()`
-* `Body()`
-* `Cookie()`
-* `Header()`
-* `Form()`
-* `File()`
-
-Sie können diese alle direkt von `fastapi` importieren:
-
-```python
-from fastapi import Body, Cookie, File, Form, Header, Path, Query
-```
-
-::: fastapi.Query
-
-::: fastapi.Path
-
-::: fastapi.Body
-
-::: fastapi.Cookie
-
-::: fastapi.Header
-
-::: fastapi.Form
-
-::: fastapi.File
diff --git a/docs/de/docs/reference/request.md b/docs/de/docs/reference/request.md
deleted file mode 100644
index cf7eb61ad..000000000
--- a/docs/de/docs/reference/request.md
+++ /dev/null
@@ -1,17 +0,0 @@
-# `Request`-Klasse
-
-Sie können einen Parameter in einer *Pfadoperation-Funktion* oder einer Abhängigkeit als vom Typ `Request` deklarieren und dann direkt auf das Requestobjekt zugreifen, ohne jegliche Validierung, usw.
-
-Sie können es direkt von `fastapi` importieren:
-
-```python
-from fastapi import Request
-```
-
-/// tip | "Tipp"
-
-Wenn Sie Abhängigkeiten definieren möchten, die sowohl mit HTTP als auch mit WebSockets kompatibel sein sollen, können Sie einen Parameter definieren, der eine `HTTPConnection` anstelle eines `Request` oder eines `WebSocket` akzeptiert.
-
-///
-
-::: fastapi.Request
diff --git a/docs/de/docs/reference/response.md b/docs/de/docs/reference/response.md
deleted file mode 100644
index 215918931..000000000
--- a/docs/de/docs/reference/response.md
+++ /dev/null
@@ -1,13 +0,0 @@
-# `Response`-Klasse
-
-Sie können einen Parameter in einer *Pfadoperation-Funktion* oder einer Abhängigkeit als `Response` deklarieren und dann Daten für die Response wie Header oder Cookies festlegen.
-
-Diese können Sie auch direkt verwenden, um eine Instanz davon zu erstellen und diese von Ihren *Pfadoperationen* zurückzugeben.
-
-Sie können sie direkt von `fastapi` importieren:
-
-```python
-from fastapi import Response
-```
-
-::: fastapi.Response
diff --git a/docs/de/docs/reference/responses.md b/docs/de/docs/reference/responses.md
deleted file mode 100644
index c0e9f07e7..000000000
--- a/docs/de/docs/reference/responses.md
+++ /dev/null
@@ -1,164 +0,0 @@
-# Benutzerdefinierte Responseklassen – File, HTML, Redirect, Streaming, usw.
-
-Es gibt mehrere benutzerdefinierte Responseklassen, von denen Sie eine Instanz erstellen und diese direkt von Ihren *Pfadoperationen* zurückgeben können.
-
-Lesen Sie mehr darüber in der [FastAPI-Dokumentation zu benutzerdefinierten Responses – HTML, Stream, Datei, andere](../advanced/custom-response.md).
-
-Sie können diese direkt von `fastapi.responses` importieren:
-
-```python
-from fastapi.responses import (
- FileResponse,
- HTMLResponse,
- JSONResponse,
- ORJSONResponse,
- PlainTextResponse,
- RedirectResponse,
- Response,
- StreamingResponse,
- UJSONResponse,
-)
-```
-
-## FastAPI-Responses
-
-Es gibt einige benutzerdefinierte FastAPI-Responseklassen, welche Sie verwenden können, um die JSON-Performanz zu optimieren.
-
-::: fastapi.responses.UJSONResponse
- options:
- members:
- - charset
- - status_code
- - media_type
- - body
- - background
- - raw_headers
- - render
- - init_headers
- - headers
- - set_cookie
- - delete_cookie
-
-::: fastapi.responses.ORJSONResponse
- options:
- members:
- - charset
- - status_code
- - media_type
- - body
- - background
- - raw_headers
- - render
- - init_headers
- - headers
- - set_cookie
- - delete_cookie
-
-## Starlette-Responses
-
-::: fastapi.responses.FileResponse
- options:
- members:
- - chunk_size
- - charset
- - status_code
- - media_type
- - body
- - background
- - raw_headers
- - render
- - init_headers
- - headers
- - set_cookie
- - delete_cookie
-
-::: fastapi.responses.HTMLResponse
- options:
- members:
- - charset
- - status_code
- - media_type
- - body
- - background
- - raw_headers
- - render
- - init_headers
- - headers
- - set_cookie
- - delete_cookie
-
-::: fastapi.responses.JSONResponse
- options:
- members:
- - charset
- - status_code
- - media_type
- - body
- - background
- - raw_headers
- - render
- - init_headers
- - headers
- - set_cookie
- - delete_cookie
-
-::: fastapi.responses.PlainTextResponse
- options:
- members:
- - charset
- - status_code
- - media_type
- - body
- - background
- - raw_headers
- - render
- - init_headers
- - headers
- - set_cookie
- - delete_cookie
-
-::: fastapi.responses.RedirectResponse
- options:
- members:
- - charset
- - status_code
- - media_type
- - body
- - background
- - raw_headers
- - render
- - init_headers
- - headers
- - set_cookie
- - delete_cookie
-
-::: fastapi.responses.Response
- options:
- members:
- - charset
- - status_code
- - media_type
- - body
- - background
- - raw_headers
- - render
- - init_headers
- - headers
- - set_cookie
- - delete_cookie
-
-::: fastapi.responses.StreamingResponse
- options:
- members:
- - body_iterator
- - charset
- - status_code
- - media_type
- - body
- - background
- - raw_headers
- - render
- - init_headers
- - headers
- - set_cookie
- - delete_cookie
diff --git a/docs/de/docs/reference/security/index.md b/docs/de/docs/reference/security/index.md
deleted file mode 100644
index 4c2375f2f..000000000
--- a/docs/de/docs/reference/security/index.md
+++ /dev/null
@@ -1,73 +0,0 @@
-# Sicherheitstools
-
-Wenn Sie Abhängigkeiten mit OAuth2-Scopes deklarieren müssen, verwenden Sie `Security()`.
-
-Aber Sie müssen immer noch definieren, was das Dependable, das Callable ist, welches Sie als Parameter an `Depends()` oder `Security()` übergeben.
-
-Es gibt mehrere Tools, mit denen Sie diese Dependables erstellen können, und sie werden in OpenAPI integriert, sodass sie in der Oberfläche der automatischen Dokumentation angezeigt werden und von automatisch generierten Clients und SDKs, usw., verwendet werden können.
-
-Sie können sie von `fastapi.security` importieren:
-
-```python
-from fastapi.security import (
- APIKeyCookie,
- APIKeyHeader,
- APIKeyQuery,
- HTTPAuthorizationCredentials,
- HTTPBasic,
- HTTPBasicCredentials,
- HTTPBearer,
- HTTPDigest,
- OAuth2,
- OAuth2AuthorizationCodeBearer,
- OAuth2PasswordBearer,
- OAuth2PasswordRequestForm,
- OAuth2PasswordRequestFormStrict,
- OpenIdConnect,
- SecurityScopes,
-)
-```
-
-## API-Schlüssel-Sicherheitsschemas
-
-::: fastapi.security.APIKeyCookie
-
-::: fastapi.security.APIKeyHeader
-
-::: fastapi.security.APIKeyQuery
-
-## HTTP-Authentifizierungsschemas
-
-::: fastapi.security.HTTPBasic
-
-::: fastapi.security.HTTPBearer
-
-::: fastapi.security.HTTPDigest
-
-## HTTP-Anmeldeinformationen
-
-::: fastapi.security.HTTPAuthorizationCredentials
-
-::: fastapi.security.HTTPBasicCredentials
-
-## OAuth2-Authentifizierung
-
-::: fastapi.security.OAuth2
-
-::: fastapi.security.OAuth2AuthorizationCodeBearer
-
-::: fastapi.security.OAuth2PasswordBearer
-
-## OAuth2-Passwortformulare
-
-::: fastapi.security.OAuth2PasswordRequestForm
-
-::: fastapi.security.OAuth2PasswordRequestFormStrict
-
-## OAuth2-Sicherheitsscopes in Abhängigkeiten
-
-::: fastapi.security.SecurityScopes
-
-## OpenID Connect
-
-::: fastapi.security.OpenIdConnect
diff --git a/docs/de/docs/reference/staticfiles.md b/docs/de/docs/reference/staticfiles.md
deleted file mode 100644
index 5629854c6..000000000
--- a/docs/de/docs/reference/staticfiles.md
+++ /dev/null
@@ -1,13 +0,0 @@
-# Statische Dateien – `StaticFiles`
-
-Sie können die `StaticFiles`-Klasse verwenden, um statische Dateien wie JavaScript, CSS, Bilder, usw. bereitzustellen.
-
-Lesen Sie mehr darüber in der [FastAPI-Dokumentation zu statischen Dateien](../tutorial/static-files.md).
-
-Sie können sie direkt von `fastapi.staticfiles` importieren:
-
-```python
-from fastapi.staticfiles import StaticFiles
-```
-
-::: fastapi.staticfiles.StaticFiles
diff --git a/docs/de/docs/reference/status.md b/docs/de/docs/reference/status.md
deleted file mode 100644
index 1d9458ee9..000000000
--- a/docs/de/docs/reference/status.md
+++ /dev/null
@@ -1,36 +0,0 @@
-# Statuscodes
-
-Sie können das Modul `status` von `fastapi` importieren:
-
-```python
-from fastapi import status
-```
-
-`status` wird direkt von Starlette bereitgestellt.
-
-Es enthält eine Gruppe benannter Konstanten (Variablen) mit ganzzahligen Statuscodes.
-
-Zum Beispiel:
-
-* 200: `status.HTTP_200_OK`
-* 403: `status.HTTP_403_FORBIDDEN`
-* usw.
-
-Es kann praktisch sein, schnell auf HTTP- (und WebSocket-)Statuscodes in Ihrer Anwendung zuzugreifen, indem Sie die automatische Vervollständigung für den Namen verwenden, ohne sich die Zahlen für die Statuscodes merken zu müssen.
-
-Lesen Sie mehr darüber in der [FastAPI-Dokumentation zu Response-Statuscodes](../tutorial/response-status-code.md).
-
-## Beispiel
-
-```python
-from fastapi import FastAPI, status
-
-app = FastAPI()
-
-
-@app.get("/items/", status_code=status.HTTP_418_IM_A_TEAPOT)
-def read_items():
- return [{"name": "Plumbus"}, {"name": "Portal Gun"}]
-```
-
-::: fastapi.status
diff --git a/docs/de/docs/reference/templating.md b/docs/de/docs/reference/templating.md
deleted file mode 100644
index c367a0179..000000000
--- a/docs/de/docs/reference/templating.md
+++ /dev/null
@@ -1,13 +0,0 @@
-# Templating – `Jinja2Templates`
-
-Sie können die `Jinja2Templates`-Klasse verwenden, um Jinja-Templates zu rendern.
-
-Lesen Sie mehr darüber in der [FastAPI-Dokumentation zu Templates](../advanced/templates.md).
-
-Sie können die Klasse direkt von `fastapi.templating` importieren:
-
-```python
-from fastapi.templating import Jinja2Templates
-```
-
-::: fastapi.templating.Jinja2Templates
diff --git a/docs/de/docs/reference/testclient.md b/docs/de/docs/reference/testclient.md
deleted file mode 100644
index 5bc089c05..000000000
--- a/docs/de/docs/reference/testclient.md
+++ /dev/null
@@ -1,13 +0,0 @@
-# Testclient – `TestClient`
-
-Sie können die `TestClient`-Klasse verwenden, um FastAPI-Anwendungen zu testen, ohne eine tatsächliche HTTP- und Socket-Verbindung zu erstellen, Sie kommunizieren einfach direkt mit dem FastAPI-Code.
-
-Lesen Sie mehr darüber in der [FastAPI-Dokumentation über Testen](../tutorial/testing.md).
-
-Sie können sie direkt von `fastapi.testclient` importieren:
-
-```python
-from fastapi.testclient import TestClient
-```
-
-::: fastapi.testclient.TestClient
diff --git a/docs/de/docs/reference/uploadfile.md b/docs/de/docs/reference/uploadfile.md
deleted file mode 100644
index 8556edf82..000000000
--- a/docs/de/docs/reference/uploadfile.md
+++ /dev/null
@@ -1,22 +0,0 @@
-# `UploadFile`-Klasse
-
-Sie können *Pfadoperation-Funktionsparameter* als Parameter vom Typ `UploadFile` definieren, um Dateien aus dem Request zu erhalten.
-
-Sie können es direkt von `fastapi` importieren:
-
-```python
-from fastapi import UploadFile
-```
-
-::: fastapi.UploadFile
- options:
- members:
- - file
- - filename
- - size
- - headers
- - content_type
- - read
- - write
- - seek
- - close
diff --git a/docs/de/docs/reference/websockets.md b/docs/de/docs/reference/websockets.md
deleted file mode 100644
index d5597d0ee..000000000
--- a/docs/de/docs/reference/websockets.md
+++ /dev/null
@@ -1,67 +0,0 @@
-# WebSockets
-
-Bei der Definition von WebSockets deklarieren Sie normalerweise einen Parameter vom Typ `WebSocket` und können damit Daten vom Client lesen und an ihn senden. Er wird direkt von Starlette bereitgestellt, Sie können ihn aber von `fastapi` importieren:
-
-```python
-from fastapi import WebSocket
-```
-
-/// tip | "Tipp"
-
-Wenn Sie Abhängigkeiten definieren möchten, die sowohl mit HTTP als auch mit WebSockets kompatibel sein sollen, können Sie einen Parameter definieren, der eine `HTTPConnection` anstelle eines `Request` oder eines `WebSocket` akzeptiert.
-
-///
-
-::: fastapi.WebSocket
- options:
- members:
- - scope
- - app
- - url
- - base_url
- - headers
- - query_params
- - path_params
- - cookies
- - client
- - state
- - url_for
- - client_state
- - application_state
- - receive
- - send
- - accept
- - receive_text
- - receive_bytes
- - receive_json
- - iter_text
- - iter_bytes
- - iter_json
- - send_text
- - send_bytes
- - send_json
- - close
-
-Wenn ein Client die Verbindung trennt, wird eine `WebSocketDisconnect`-Exception ausgelöst, die Sie abfangen können.
-
-Sie können diese direkt von `fastapi` importieren:
-
-```python
-from fastapi import WebSocketDisconnect
-```
-
-::: fastapi.WebSocketDisconnect
-
-## WebSockets – zusätzliche Klassen
-
-Zusätzliche Klassen für die Handhabung von WebSockets.
-
-Werden direkt von Starlette bereitgestellt, Sie können sie jedoch von `fastapi` importieren:
-
-```python
-from fastapi.websockets import WebSocketDisconnect, WebSocketState
-```
-
-::: fastapi.websockets.WebSocketDisconnect
-
-::: fastapi.websockets.WebSocketState
diff --git a/docs/de/docs/tutorial/response-model.md b/docs/de/docs/tutorial/response-model.md
index 3f632b1cb..b480780bc 100644
--- a/docs/de/docs/tutorial/response-model.md
+++ b/docs/de/docs/tutorial/response-model.md
@@ -131,7 +131,7 @@ Im Folgenden deklarieren wir ein `UserIn`-Modell; es enthält ein Klartext-Passw
/// info
-Um `EmailStr` zu verwenden, installieren Sie zuerst `email_validator`.
+Um `EmailStr` zu verwenden, installieren Sie zuerst `email-validator`.
Z. B. `pip install email-validator`
oder `pip install pydantic[email]`.
diff --git a/docs/em/docs/advanced/async-tests.md b/docs/em/docs/advanced/async-tests.md
index 324b4f68a..11f885fe6 100644
--- a/docs/em/docs/advanced/async-tests.md
+++ b/docs/em/docs/advanced/async-tests.md
@@ -72,7 +72,7 @@ $ pytest
⤴️ 👥 💪 ✍ `AsyncClient` ⏮️ 📱, & 📨 🔁 📨 ⚫️, ⚙️ `await`.
-```Python hl_lines="9-10"
+```Python hl_lines="9-12"
{!../../../docs_src/async_tests/test_main.py!}
```
diff --git a/docs/em/docs/advanced/middleware.md b/docs/em/docs/advanced/middleware.md
index 89f494aa3..e3cc389c6 100644
--- a/docs/em/docs/advanced/middleware.md
+++ b/docs/em/docs/advanced/middleware.md
@@ -95,7 +95,6 @@ app.add_middleware(UnicornMiddleware, some_config="rainbow")
🖼:
-* 🔫
* Uvicorn `ProxyHeadersMiddleware`
* 🇸🇲
diff --git a/docs/em/docs/deployment/docker.md b/docs/em/docs/deployment/docker.md
index 6540761ac..2152f1a0e 100644
--- a/docs/em/docs/deployment/docker.md
+++ b/docs/em/docs/deployment/docker.md
@@ -205,8 +205,11 @@ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
`--no-cache-dir` 🎛 💬 `pip` 🚫 🖊 ⏬ 📦 🌐, 👈 🕴 🚥 `pip` 🔜 🏃 🔄 ❎ 🎏 📦, ✋️ 👈 🚫 💼 🕐❔ 👷 ⏮️ 📦.
- !!! note
- `--no-cache-dir` 🕴 🔗 `pip`, ⚫️ ✔️ 🕳 ⏮️ ☁ ⚖️ 📦.
+ /// note
+
+ `--no-cache-dir` 🕴 🔗 `pip`, ⚫️ ✔️ 🕳 ⏮️ ☁ ⚖️ 📦.
+
+ ///
`--upgrade` 🎛 💬 `pip` ♻ 📦 🚥 👫 ⏪ ❎.
diff --git a/docs/em/docs/external-links.md b/docs/em/docs/external-links.md
deleted file mode 100644
index 486b134d4..000000000
--- a/docs/em/docs/external-links.md
+++ /dev/null
@@ -1,38 +0,0 @@
-# 🔢 🔗 & 📄
-
-**FastAPI** ✔️ 👑 👪 🕧 💗.
-
-📤 📚 🏤, 📄, 🧰, & 🏗, 🔗 **FastAPI**.
-
-📥 ❌ 📇 👫.
-
-/// tip
-
-🚥 👆 ✔️ 📄, 🏗, 🧰, ⚖️ 🕳 🔗 **FastAPI** 👈 🚫 📇 📥, ✍ 🚲 📨 ❎ ⚫️.
-
-///
-
-## 📄
-
-{% for section_name, section_content in external_links.items() %}
-
-## {{ section_name }}
-
-{% for lang_name, lang_content in section_content.items() %}
-
-### {{ lang_name }}
-
-{% for item in lang_content %}
-
-* {{ item.title }} by {{ item.author }}.
-
-{% endfor %}
-{% endfor %}
-{% endfor %}
-
-## 🏗
-
-⏪ 📂 🏗 ⏮️ ❔ `fastapi`:
-
-email_validator
- 📧 🔬.
+* email-validator
- 📧 🔬.
⚙️ 💃:
diff --git a/docs/em/docs/tutorial/response-model.md b/docs/em/docs/tutorial/response-model.md
index caae47d14..9483508aa 100644
--- a/docs/em/docs/tutorial/response-model.md
+++ b/docs/em/docs/tutorial/response-model.md
@@ -131,7 +131,7 @@ FastAPI 🔜 ⚙️ 👉 `response_model` 🌐 💽 🧾, 🔬, ♒️. & **
/// info
-⚙️ `EmailStr`, 🥇 ❎ `email_validator`.
+⚙️ `EmailStr`, 🥇 ❎ `email-validator`.
🤶 Ⓜ. `pip install email-validator`
⚖️ `pip install pydantic[email]`.
diff --git a/docs/en/data/external_links.yml b/docs/en/data/external_links.yml
index 15f6169ee..63fd3d0cf 100644
--- a/docs/en/data/external_links.yml
+++ b/docs/en/data/external_links.yml
@@ -264,6 +264,14 @@ Articles:
author_link: https://devonray.com
link: https://devonray.com/blog/deploying-a-fastapi-project-using-aws-lambda-aurora-cdk
title: Deployment using Docker, Lambda, Aurora, CDK & GH Actions
+ - author: Shubhendra Kushwaha
+ author_link: https://www.linkedin.com/in/theshubhendra/
+ link: https://theshubhendra.medium.com/mastering-soft-delete-advanced-sqlalchemy-techniques-4678f4738947
+ title: 'Mastering Soft Delete: Advanced SQLAlchemy Techniques'
+ - author: Shubhendra Kushwaha
+ author_link: https://www.linkedin.com/in/theshubhendra/
+ link: https://theshubhendra.medium.com/role-based-row-filtering-advanced-sqlalchemy-techniques-733e6b1328f6
+ title: 'Role based row filtering: Advanced SQLAlchemy Techniques'
German:
- author: Marcel Sander (actidoo)
author_link: https://www.actidoo.com
diff --git a/docs/en/data/members.yml b/docs/en/data/members.yml
index 0b9e7b94c..0069f8c75 100644
--- a/docs/en/data/members.yml
+++ b/docs/en/data/members.yml
@@ -1,19 +1,19 @@
members:
- login: tiangolo
- avatar_url: https://github.com/tiangolo.png
+ avatar_url: https://avatars.githubusercontent.com/u/1326112
url: https://github.com/tiangolo
- login: Kludex
- avatar_url: https://github.com/Kludex.png
+ avatar_url: https://avatars.githubusercontent.com/u/7353520
url: https://github.com/Kludex
- login: alejsdev
- avatar_url: https://github.com/alejsdev.png
+ avatar_url: https://avatars.githubusercontent.com/u/90076947
url: https://github.com/alejsdev
- login: svlandeg
- avatar_url: https://github.com/svlandeg.png
+ avatar_url: https://avatars.githubusercontent.com/u/8796347
url: https://github.com/svlandeg
- login: estebanx64
- avatar_url: https://github.com/estebanx64.png
+ avatar_url: https://avatars.githubusercontent.com/u/10840422
url: https://github.com/estebanx64
- login: patrick91
- avatar_url: https://github.com/patrick91.png
+ avatar_url: https://avatars.githubusercontent.com/u/667029
url: https://github.com/patrick91
diff --git a/docs/en/data/sponsors.yml b/docs/en/data/sponsors.yml
index 8c0956ac5..d96646fb3 100644
--- a/docs/en/data/sponsors.yml
+++ b/docs/en/data/sponsors.yml
@@ -17,15 +17,12 @@ gold:
- url: https://www.propelauth.com/?utm_source=fastapi&utm_campaign=1223&utm_medium=mainbadge
title: Auth, user management and more for your B2B product
img: https://fastapi.tiangolo.com/img/sponsors/propelauth.png
- - url: https://docs.withcoherence.com/configuration/frameworks/?utm_medium=advertising&utm_source=fastapi&utm_campaign=docs#fastapi-example
+ - url: https://www.withcoherence.com/?utm_medium=advertising&utm_source=fastapi&utm_campaign=website
title: Coherence
img: https://fastapi.tiangolo.com/img/sponsors/coherence.png
- url: https://www.mongodb.com/developer/languages/python/python-quickstart-fastapi/?utm_campaign=fastapi_framework&utm_source=fastapi_sponsorship&utm_medium=web_referral
title: Simplify Full Stack Development with FastAPI & MongoDB
img: https://fastapi.tiangolo.com/img/sponsors/mongodb.png
- - url: https://konghq.com/products/kong-konnect?utm_medium=referral&utm_source=github&utm_campaign=platform&utm_content=fast-api
- title: Kong Konnect - API management platform
- img: https://fastapi.tiangolo.com/img/sponsors/kong.png
- url: https://zuplo.link/fastapi-gh
title: 'Zuplo: Scale, Protect, Document, and Monetize your FastAPI'
img: https://fastapi.tiangolo.com/img/sponsors/zuplo.png
diff --git a/docs/en/docs/advanced/additional-responses.md b/docs/en/docs/advanced/additional-responses.md
index 95ca90f6b..674f0672c 100644
--- a/docs/en/docs/advanced/additional-responses.md
+++ b/docs/en/docs/advanced/additional-responses.md
@@ -40,7 +40,7 @@ Keep in mind that you have to return the `JSONResponse` directly.
The `model` key is not part of OpenAPI.
-**FastAPI** will take the Pydantic model from there, generate the `JSON Schema`, and put it in the correct place.
+**FastAPI** will take the Pydantic model from there, generate the JSON Schema, and put it in the correct place.
The correct place is:
@@ -251,5 +251,5 @@ For example:
To see what exactly you can include in the responses, you can check these sections in the OpenAPI specification:
-* OpenAPI Responses Object, it includes the `Response Object`.
-* OpenAPI Response Object, you can include anything from this directly in each response inside your `responses` parameter. Including `description`, `headers`, `content` (inside of this is that you declare different media types and JSON Schemas), and `links`.
+* OpenAPI Responses Object, it includes the `Response Object`.
+* OpenAPI Response Object, you can include anything from this directly in each response inside your `responses` parameter. Including `description`, `headers`, `content` (inside of this is that you declare different media types and JSON Schemas), and `links`.
diff --git a/docs/en/docs/advanced/async-tests.md b/docs/en/docs/advanced/async-tests.md
index ac459ff0c..580d9142c 100644
--- a/docs/en/docs/advanced/async-tests.md
+++ b/docs/en/docs/advanced/async-tests.md
@@ -72,7 +72,7 @@ Note that the test function is now `async def` instead of just `def` as before w
Then we can create an `AsyncClient` with the app, and send async requests to it, using `await`.
-```Python hl_lines="9-10"
+```Python hl_lines="9-12"
{!../../../docs_src/async_tests/test_main.py!}
```
diff --git a/docs/en/docs/advanced/behind-a-proxy.md b/docs/en/docs/advanced/behind-a-proxy.md
index 0447a7220..5ff64016c 100644
--- a/docs/en/docs/advanced/behind-a-proxy.md
+++ b/docs/en/docs/advanced/behind-a-proxy.md
@@ -211,7 +211,7 @@ Now create that other file `routes.toml`:
This file configures Traefik to use the path prefix `/api/v1`.
-And then it will redirect its requests to your Uvicorn running on `http://127.0.0.1:8000`.
+And then Traefik will redirect its requests to your Uvicorn running on `http://127.0.0.1:8000`.
Now start Traefik:
diff --git a/docs/en/docs/advanced/custom-response.md b/docs/en/docs/advanced/custom-response.md
index f31127efe..79f755815 100644
--- a/docs/en/docs/advanced/custom-response.md
+++ b/docs/en/docs/advanced/custom-response.md
@@ -4,9 +4,9 @@ By default, **FastAPI** will return the responses using `JSONResponse`.
You can override it by returning a `Response` directly as seen in [Return a Response directly](response-directly.md){.internal-link target=_blank}.
-But if you return a `Response` directly, the data won't be automatically converted, and the documentation won't be automatically generated (for example, including the specific "media type", in the HTTP header `Content-Type` as part of the generated OpenAPI).
+But if you return a `Response` directly (or any subclass, like `JSONResponse`), the data won't be automatically converted (even if you declare a `response_model`), and the documentation won't be automatically generated (for example, including the specific "media type", in the HTTP header `Content-Type` as part of the generated OpenAPI).
-But you can also declare the `Response` that you want to be used, in the *path operation decorator*.
+But you can also declare the `Response` that you want to be used (e.g. any `Response` subclass), in the *path operation decorator* using the `response_class` parameter.
The contents that you return from your *path operation function* will be put inside of that `Response`.
@@ -255,11 +255,11 @@ This includes many libraries to interact with cloud storage, video processing, a
1. This is the generator function. It's a "generator function" because it contains `yield` statements inside.
2. By using a `with` block, we make sure that the file-like object is closed after the generator function is done. So, after it finishes sending the response.
-3. This `yield from` tells the function to iterate over that thing named `file_like`. And then, for each part iterated, yield that part as coming from this generator function.
+3. This `yield from` tells the function to iterate over that thing named `file_like`. And then, for each part iterated, yield that part as coming from this generator function (`iterfile`).
So, it is a generator function that transfers the "generating" work to something else internally.
- By doing it this way, we can put it in a `with` block, and that way, ensure that it is closed after finishing.
+ By doing it this way, we can put it in a `with` block, and that way, ensure that the file-like object is closed after finishing.
/// tip
diff --git a/docs/en/docs/advanced/middleware.md b/docs/en/docs/advanced/middleware.md
index 4b273fd89..70415adca 100644
--- a/docs/en/docs/advanced/middleware.md
+++ b/docs/en/docs/advanced/middleware.md
@@ -88,6 +88,7 @@ The middleware will handle both standard and streaming responses.
The following arguments are supported:
* `minimum_size` - Do not GZip responses that are smaller than this minimum size in bytes. Defaults to `500`.
+* `compresslevel` - Used during GZip compression. It is an integer ranging from 1 to 9. Defaults to `9`. Lower value results in faster compression but larger file sizes, while higher value results in slower compression but smaller file sizes.
## Other middlewares
@@ -95,7 +96,6 @@ There are many other ASGI middlewares.
For example:
-* Sentry
* Uvicorn's `ProxyHeadersMiddleware`
* MessagePack
diff --git a/docs/en/docs/advanced/openapi-callbacks.md b/docs/en/docs/advanced/openapi-callbacks.md
index e74af3d3e..7fead2ed9 100644
--- a/docs/en/docs/advanced/openapi-callbacks.md
+++ b/docs/en/docs/advanced/openapi-callbacks.md
@@ -37,7 +37,7 @@ This part is pretty normal, most of the code is probably already familiar to you
/// tip
-The `callback_url` query parameter uses a Pydantic URL type.
+The `callback_url` query parameter uses a Pydantic Url type.
///
diff --git a/docs/en/docs/advanced/response-directly.md b/docs/en/docs/advanced/response-directly.md
index 33e10d091..2251659c5 100644
--- a/docs/en/docs/advanced/response-directly.md
+++ b/docs/en/docs/advanced/response-directly.md
@@ -28,7 +28,7 @@ This gives you a lot of flexibility. You can return any data type, override any
## Using the `jsonable_encoder` in a `Response`
-Because **FastAPI** doesn't do any change to a `Response` you return, you have to make sure it's contents are ready for it.
+Because **FastAPI** doesn't do any change to a `Response` you return, you have to make sure its contents are ready for it.
For example, you cannot put a Pydantic model in a `JSONResponse` without first converting it to a `dict` with all the data types (like `datetime`, `UUID`, etc) converted to JSON-compatible types.
@@ -54,7 +54,7 @@ Now, let's see how you could use that to return a custom response.
Let's say that you want to return an XML response.
-You could put your XML content in a string, put it in a `Response`, and return it:
+You could put your XML content in a string, put that in a `Response`, and return it:
```Python hl_lines="1 18"
{!../../../docs_src/response_directly/tutorial002.py!}
diff --git a/docs/en/docs/advanced/security/oauth2-scopes.md b/docs/en/docs/advanced/security/oauth2-scopes.md
index 69b8fa7d2..ff52d7bb8 100644
--- a/docs/en/docs/advanced/security/oauth2-scopes.md
+++ b/docs/en/docs/advanced/security/oauth2-scopes.md
@@ -398,7 +398,7 @@ Now update the dependency `get_current_user`.
This is the one used by the dependencies above.
-Here's were we are using the same OAuth2 scheme we created before, declaring it as a dependency: `oauth2_scheme`.
+Here's where we are using the same OAuth2 scheme we created before, declaring it as a dependency: `oauth2_scheme`.
Because this dependency function doesn't have any scope requirements itself, we can use `Depends` with `oauth2_scheme`, we don't have to use `Security` when we don't need to specify security scopes.
@@ -725,7 +725,7 @@ Here's how the hierarchy of dependencies and scopes looks like:
* This `security_scopes` parameter has a property `scopes` with a `list` containing all these scopes declared above, so:
* `security_scopes.scopes` will contain `["me", "items"]` for the *path operation* `read_own_items`.
* `security_scopes.scopes` will contain `["me"]` for the *path operation* `read_users_me`, because it is declared in the dependency `get_current_active_user`.
- * `security_scopes.scopes` will contain `[]` (nothing) for the *path operation* `read_system_status`, because it didn't declare any `Security` with `scopes`, and its dependency, `get_current_user`, doesn't declare any `scope` either.
+ * `security_scopes.scopes` will contain `[]` (nothing) for the *path operation* `read_system_status`, because it didn't declare any `Security` with `scopes`, and its dependency, `get_current_user`, doesn't declare any `scopes` either.
/// tip
diff --git a/docs/en/docs/advanced/settings.md b/docs/en/docs/advanced/settings.md
index b77557361..22bf7de20 100644
--- a/docs/en/docs/advanced/settings.md
+++ b/docs/en/docs/advanced/settings.md
@@ -6,143 +6,25 @@ Most of these settings are variable (can change), like database URLs. And many c
For this reason it's common to provide them in environment variables that are read by the application.
-## Environment Variables
-
-/// tip
-
-If you already know what "environment variables" are and how to use them, feel free to skip to the next section below.
-
-///
-
-An environment variable (also known as "env var") is a variable that lives outside of the Python code, in the operating system, and could be read by your Python code (or by other programs as well).
-
-You can create and use environment variables in the shell, without needing Python:
-
-//// tab | Linux, macOS, Windows Bash
-
-fastapi run --workers 4 main.py +INFO Using path main.py +INFO Resolved absolute path /home/user/code/awesomeapp/main.py +INFO Searching for package file structure from directories with __init__.py files +INFO Importing from /home/user/code/awesomeapp + + ╭─ Python module file ─╮ + │ │ + │ 🐍 main.py │ + │ │ + ╰──────────────────────╯ + +INFO Importing module main +INFO Found importable FastAPI app + + ╭─ Importable FastAPI app ─╮ + │ │ + │ from main import app │ + │ │ + ╰──────────────────────────╯ + +INFO Using import string main:app + + ╭─────────── FastAPI CLI - Production mode ───────────╮ + │ │ + │ Serving at: http://0.0.0.0:8000 │ + │ │ + │ API docs: http://0.0.0.0:8000/docs │ + │ │ + │ Running in production mode, for development use: │ + │ │ + │ fastapi dev │ + │ │ + ╰─────────────────────────────────────────────────────╯ + +INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit) +INFO: Started parent process [27365] +INFO: Started server process [27368] +INFO: Waiting for application startup. +INFO: Application startup complete. +INFO: Started server process [27369] +INFO: Waiting for application startup. +INFO: Application startup complete. +INFO: Started server process [27370] +INFO: Waiting for application startup. +INFO: Application startup complete. +INFO: Started server process [27367] +INFO: Waiting for application startup. +INFO: Application startup complete. +```
email_validator
- for email validation.
+* email-validator
- for email validation.
Used by Starlette:
diff --git a/docs/en/docs/management-tasks.md b/docs/en/docs/management-tasks.md
index 2c91cab72..7e7aa3baf 100644
--- a/docs/en/docs/management-tasks.md
+++ b/docs/en/docs/management-tasks.md
@@ -113,7 +113,7 @@ For the other languages, confirm that:
* The title is correct following the instructions above.
* It has the labels `lang-all` and `lang-{lang code}`.
* The PR changes only one Markdown file adding a translation.
- * Or in some cases, at most two files, if they are small and people reviewed them.
+ * Or in some cases, at most two files, if they are small, for the same language, and people reviewed them.
* If it's the first translation for that language, it will have additional `mkdocs.yml` files, for those cases follow the instructions below.
* The PR doesn't add any additional or extraneous files.
* The translation seems to have a similar structure as the original English file.
@@ -280,8 +280,4 @@ Dependabot will create PRs to update dependencies for several things, and those
When a question in GitHub Discussions has been answered, mark the answer by clicking "Mark as answer".
-Many of the current Discussion Questions were migrated from old issues. Many have the label `answered`, that means they were answered when they were issues, but now in GitHub Discussions, it's not known what is the actual response from the messages.
-
-You can filter discussions by [`Questions` that are `Unanswered` and have the label `answered`](https://github.com/fastapi/fastapi/discussions/categories/questions?discussions_q=category%3AQuestions+is%3Aopen+label%3Aanswered+is%3Aunanswered).
-
-All of those discussions already have an answer in the conversation, you can find it and mark it with the "Mark as answer" button.
+You can filter discussions by `Questions` that are `Unanswered`.
diff --git a/docs/en/docs/project-generation.md b/docs/en/docs/project-generation.md
index d142862ee..61459ba53 100644
--- a/docs/en/docs/project-generation.md
+++ b/docs/en/docs/project-generation.md
@@ -16,6 +16,7 @@ GitHub Repository: Required Optional fields.
+Pydantic has a special behavior when you use `Optional` or `Union[Something, None]` without a default value, you can read more about it in the Pydantic docs about Required Optional fields.
///
diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md
index a0176364c..01c9fb225 100644
--- a/docs/en/docs/release-notes.md
+++ b/docs/en/docs/release-notes.md
@@ -7,8 +7,236 @@ hide:
## Latest Changes
+### Internal
+
+* ➕ Add inline-snapshot for tests. PR [#12189](https://github.com/fastapi/fastapi/pull/12189) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.114.1
+
+### Refactors
+
+* ⚡️ Improve performance in request body parsing with a cache for internal model fields. PR [#12184](https://github.com/fastapi/fastapi/pull/12184) by [@tiangolo](https://github.com/tiangolo).
+
+### Docs
+
+* 📝 Remove duplicate line in docs for `docs/en/docs/environment-variables.md`. PR [#12169](https://github.com/fastapi/fastapi/pull/12169) by [@prometek](https://github.com/prometek).
+
+### Translations
+
+* 🌐 Add Portuguese translation for `docs/pt/docs/virtual-environments.md`. PR [#12163](https://github.com/fastapi/fastapi/pull/12163) by [@marcelomarkus](https://github.com/marcelomarkus).
+* 🌐 Add Portuguese translation for `docs/pt/docs/environment-variables.md`. PR [#12162](https://github.com/fastapi/fastapi/pull/12162) by [@marcelomarkus](https://github.com/marcelomarkus).
+* 🌐 Add Portuguese translation for `docs/pt/docs/tutorial/testing.md`. PR [#12164](https://github.com/fastapi/fastapi/pull/12164) by [@marcelomarkus](https://github.com/marcelomarkus).
+* 🌐 Add Portuguese translation for `docs/pt/docs/tutorial/debugging.md`. PR [#12165](https://github.com/fastapi/fastapi/pull/12165) by [@marcelomarkus](https://github.com/marcelomarkus).
+* 🌐 Add Korean translation for `docs/ko/docs/project-generation.md`. PR [#12157](https://github.com/fastapi/fastapi/pull/12157) by [@BORA040126](https://github.com/BORA040126).
+
+### Internal
+
+* ⬆ Bump tiangolo/issue-manager from 0.5.0 to 0.5.1. PR [#12173](https://github.com/fastapi/fastapi/pull/12173) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#12176](https://github.com/fastapi/fastapi/pull/12176) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
+* 👷 Update `issue-manager.yml`. PR [#12159](https://github.com/fastapi/fastapi/pull/12159) by [@tiangolo](https://github.com/tiangolo).
+* ✏️ Fix typo in `fastapi/params.py`. PR [#12143](https://github.com/fastapi/fastapi/pull/12143) by [@surreal30](https://github.com/surreal30).
+
+## 0.114.0
+
+You can restrict form fields to only include those declared in a Pydantic model and forbid any extra field sent in the request using Pydantic's `model_config = {"extra": "forbid"}`:
+
+```python
+from typing import Annotated
+
+from fastapi import FastAPI, Form
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class FormData(BaseModel):
+ username: str
+ password: str
+ model_config = {"extra": "forbid"}
+
+
+@app.post("/login/")
+async def login(data: Annotated[FormData, Form()]):
+ return data
+```
+
+Read the new docs: [Form Models - Forbid Extra Form Fields](https://fastapi.tiangolo.com/tutorial/request-form-models/#forbid-extra-form-fields).
+
+### Features
+
+* ✨ Add support for forbidding extra form fields with Pydantic models. PR [#12134](https://github.com/fastapi/fastapi/pull/12134) by [@tiangolo](https://github.com/tiangolo).
+
+### Docs
+
+* 📝 Update docs, Form Models section title, to match config name. PR [#12152](https://github.com/fastapi/fastapi/pull/12152) by [@tiangolo](https://github.com/tiangolo).
+
+### Internal
+
+* ✅ Update internal tests for latest Pydantic, including CI tweaks to install the latest Pydantic. PR [#12147](https://github.com/fastapi/fastapi/pull/12147) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.113.0
+
+Now you can declare form fields with Pydantic models:
+
+```python
+from typing import Annotated
+
+from fastapi import FastAPI, Form
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class FormData(BaseModel):
+ username: str
+ password: str
+
+
+@app.post("/login/")
+async def login(data: Annotated[FormData, Form()]):
+ return data
+```
+
+Read the new docs: [Form Models](https://fastapi.tiangolo.com/tutorial/request-form-models/).
+
+### Features
+
+* ✨ Add support for Pydantic models in `Form` parameters. PR [#12129](https://github.com/fastapi/fastapi/pull/12129) by [@tiangolo](https://github.com/tiangolo).
+
+### Internal
+
+* 🔧 Update sponsors: Coherence link. PR [#12130](https://github.com/fastapi/fastapi/pull/12130) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.112.4
+
+This release is mainly a big internal refactor to enable adding support for Pydantic models for `Form` fields, but that feature comes in the next release.
+
+This release shouldn't affect apps using FastAPI in any way. You don't even have to upgrade to this version yet. It's just a checkpoint. 🤓
+
+### Refactors
+
+* ♻️ Refactor deciding if `embed` body fields, do not overwrite fields, compute once per router, refactor internals in preparation for Pydantic models in `Form`, `Query` and others. PR [#12117](https://github.com/fastapi/fastapi/pull/12117) by [@tiangolo](https://github.com/tiangolo).
+
+### Internal
+
+* ⏪️ Temporarily revert "✨ Add support for Pydantic models in `Form` parameters" to make a checkpoint release. PR [#12128](https://github.com/fastapi/fastapi/pull/12128) by [@tiangolo](https://github.com/tiangolo). Restored by PR [#12129](https://github.com/fastapi/fastapi/pull/12129).
+* ✨ Add support for Pydantic models in `Form` parameters. PR [#12127](https://github.com/fastapi/fastapi/pull/12127) by [@tiangolo](https://github.com/tiangolo). Reverted by PR [#12128](https://github.com/fastapi/fastapi/pull/12128) to make a checkpoint release with only refactors. Restored by PR [#12129](https://github.com/fastapi/fastapi/pull/12129).
+
+## 0.112.3
+
+This release is mainly internal refactors, it shouldn't affect apps using FastAPI in any way. You don't even have to upgrade to this version yet. There are a few bigger releases coming right after. 🚀
+
+### Refactors
+
+* ♻️ Refactor internal `check_file_field()`, rename to `ensure_multipart_is_installed()` to clarify its purpose. PR [#12106](https://github.com/fastapi/fastapi/pull/12106) by [@tiangolo](https://github.com/tiangolo).
+* ♻️ Rename internal `create_response_field()` to `create_model_field()` as it's used for more than response models. PR [#12103](https://github.com/fastapi/fastapi/pull/12103) by [@tiangolo](https://github.com/tiangolo).
+* ♻️ Refactor and simplify internal data from `solve_dependencies()` using dataclasses. PR [#12100](https://github.com/fastapi/fastapi/pull/12100) by [@tiangolo](https://github.com/tiangolo).
+* ♻️ Refactor and simplify internal `analyze_param()` to structure data with dataclasses instead of tuple. PR [#12099](https://github.com/fastapi/fastapi/pull/12099) by [@tiangolo](https://github.com/tiangolo).
+* ♻️ Refactor and simplify dependencies data structures with dataclasses. PR [#12098](https://github.com/fastapi/fastapi/pull/12098) by [@tiangolo](https://github.com/tiangolo).
+
+### Docs
+
+* 📝 Add External Link: Techniques and applications of SQLAlchemy global filters in FastAPI. PR [#12109](https://github.com/fastapi/fastapi/pull/12109) by [@TheShubhendra](https://github.com/TheShubhendra).
+* 📝 Add note about `time.perf_counter()` in middlewares. PR [#12095](https://github.com/fastapi/fastapi/pull/12095) by [@tiangolo](https://github.com/tiangolo).
+* 📝 Tweak middleware code sample `time.time()` to `time.perf_counter()`. PR [#11957](https://github.com/fastapi/fastapi/pull/11957) by [@domdent](https://github.com/domdent).
+* 🔧 Update sponsors: Coherence. PR [#12093](https://github.com/fastapi/fastapi/pull/12093) by [@tiangolo](https://github.com/tiangolo).
+* 📝 Fix async test example not to trigger DeprecationWarning. PR [#12084](https://github.com/fastapi/fastapi/pull/12084) by [@marcinsulikowski](https://github.com/marcinsulikowski).
+* 📝 Update `docs_src/path_params_numeric_validations/tutorial006.py`. PR [#11478](https://github.com/fastapi/fastapi/pull/11478) by [@MuhammadAshiqAmeer](https://github.com/MuhammadAshiqAmeer).
+* 📝 Update comma in `docs/en/docs/async.md`. PR [#12062](https://github.com/fastapi/fastapi/pull/12062) by [@Alec-Gillis](https://github.com/Alec-Gillis).
+* 📝 Update docs about serving FastAPI: ASGI servers, Docker containers, etc.. PR [#12069](https://github.com/fastapi/fastapi/pull/12069) by [@tiangolo](https://github.com/tiangolo).
+* 📝 Clarify `response_class` parameter, validations, and returning a response directly. PR [#12067](https://github.com/fastapi/fastapi/pull/12067) by [@tiangolo](https://github.com/tiangolo).
+* 📝 Fix minor typos and issues in the documentation. PR [#12063](https://github.com/fastapi/fastapi/pull/12063) by [@svlandeg](https://github.com/svlandeg).
+* 📝 Add note in Docker docs about ensuring graceful shutdowns and lifespan events with `CMD` exec form. PR [#11960](https://github.com/fastapi/fastapi/pull/11960) by [@GPla](https://github.com/GPla).
+
+### Translations
+
+* 🌐 Add Dutch translation for `docs/nl/docs/features.md`. PR [#12101](https://github.com/fastapi/fastapi/pull/12101) by [@maxscheijen](https://github.com/maxscheijen).
+* 🌐 Add Portuguese translation for `docs/pt/docs/advanced/testing-events.md`. PR [#12108](https://github.com/fastapi/fastapi/pull/12108) by [@ceb10n](https://github.com/ceb10n).
+* 🌐 Add Portuguese translation for `docs/pt/docs/advanced/security/index.md`. PR [#12114](https://github.com/fastapi/fastapi/pull/12114) by [@ceb10n](https://github.com/ceb10n).
+* 🌐 Add Dutch translation for `docs/nl/docs/index.md`. PR [#12042](https://github.com/fastapi/fastapi/pull/12042) by [@svlandeg](https://github.com/svlandeg).
+* 🌐 Update Chinese translation for `docs/zh/docs/how-to/index.md`. PR [#12070](https://github.com/fastapi/fastapi/pull/12070) by [@synthpop123](https://github.com/synthpop123).
+
+### Internal
+
+* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#12115](https://github.com/fastapi/fastapi/pull/12115) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
+* ⬆ Bump pypa/gh-action-pypi-publish from 1.10.0 to 1.10.1. PR [#12120](https://github.com/fastapi/fastapi/pull/12120) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* ⬆ Bump pillow from 10.3.0 to 10.4.0. PR [#12105](https://github.com/fastapi/fastapi/pull/12105) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* 💚 Set `include-hidden-files` to `True` when using the `upload-artifact` GH action. PR [#12118](https://github.com/fastapi/fastapi/pull/12118) by [@svlandeg](https://github.com/svlandeg).
+* ⬆ Bump pypa/gh-action-pypi-publish from 1.9.0 to 1.10.0. PR [#12112](https://github.com/fastapi/fastapi/pull/12112) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* 🔧 Update sponsors link: Coherence. PR [#12097](https://github.com/fastapi/fastapi/pull/12097) by [@tiangolo](https://github.com/tiangolo).
+* 🔧 Update labeler config to handle sponsorships data. PR [#12096](https://github.com/fastapi/fastapi/pull/12096) by [@tiangolo](https://github.com/tiangolo).
+* 🔧 Update sponsors, remove Kong. PR [#12085](https://github.com/fastapi/fastapi/pull/12085) by [@tiangolo](https://github.com/tiangolo).
+* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#12076](https://github.com/fastapi/fastapi/pull/12076) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
+* 👷 Update `latest-changes` GitHub Action. PR [#12073](https://github.com/fastapi/fastapi/pull/12073) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.112.2
+
+### Fixes
+
+* 🐛 Fix `allow_inf_nan` option for Param and Body classes. PR [#11867](https://github.com/fastapi/fastapi/pull/11867) by [@giunio-prc](https://github.com/giunio-prc).
+* 🐛 Ensure that `app.include_router` merges nested lifespans. PR [#9630](https://github.com/fastapi/fastapi/pull/9630) by [@Lancetnik](https://github.com/Lancetnik).
+
+### Refactors
+
+* 🎨 Fix typing annotation for semi-internal `FastAPI.add_api_route()`. PR [#10240](https://github.com/fastapi/fastapi/pull/10240) by [@ordinary-jamie](https://github.com/ordinary-jamie).
+* ⬆️ Upgrade version of Ruff and reformat. PR [#12032](https://github.com/fastapi/fastapi/pull/12032) by [@tiangolo](https://github.com/tiangolo).
+
+### Docs
+
+* 📝 Fix a typo in `docs/en/docs/virtual-environments.md`. PR [#12064](https://github.com/fastapi/fastapi/pull/12064) by [@aymenkrifa](https://github.com/aymenkrifa).
+* 📝 Add docs about Environment Variables and Virtual Environments. PR [#12054](https://github.com/fastapi/fastapi/pull/12054) by [@tiangolo](https://github.com/tiangolo).
+* 📝 Add Asyncer mention in async docs. PR [#12037](https://github.com/fastapi/fastapi/pull/12037) by [@tiangolo](https://github.com/tiangolo).
+* 📝 Move the Features docs to the top level to improve the main page menu. PR [#12036](https://github.com/fastapi/fastapi/pull/12036) by [@tiangolo](https://github.com/tiangolo).
+* ✏️ Fix import typo in reference example for `Security`. PR [#11168](https://github.com/fastapi/fastapi/pull/11168) by [@0shah0](https://github.com/0shah0).
+* 📝 Highlight correct line in tutorial `docs/en/docs/tutorial/body-multiple-params.md`. PR [#11978](https://github.com/fastapi/fastapi/pull/11978) by [@svlandeg](https://github.com/svlandeg).
+* 🔥 Remove Sentry link from Advanced Middleware docs. PR [#12031](https://github.com/fastapi/fastapi/pull/12031) by [@alejsdev](https://github.com/alejsdev).
+* 📝 Clarify management tasks for translations, multiples files in one PR. PR [#12030](https://github.com/fastapi/fastapi/pull/12030) by [@tiangolo](https://github.com/tiangolo).
+* 📝 Edit the link to the OpenAPI "Responses Object" and "Response Object" sections in the "Additional Responses in OpenAPI" section. PR [#11996](https://github.com/fastapi/fastapi/pull/11996) by [@VaitoSoi](https://github.com/VaitoSoi).
+* 🔨 Specify `email-validator` dependency with dash. PR [#11515](https://github.com/fastapi/fastapi/pull/11515) by [@jirikuncar](https://github.com/jirikuncar).
+* 🌐 Add Spanish translation for `docs/es/docs/project-generation.md`. PR [#11947](https://github.com/fastapi/fastapi/pull/11947) by [@alejsdev](https://github.com/alejsdev).
+* 📝 Fix minor typo. PR [#12026](https://github.com/fastapi/fastapi/pull/12026) by [@MicaelJarniac](https://github.com/MicaelJarniac).
+* 📝 Several docs improvements, tweaks, and clarifications. PR [#11390](https://github.com/fastapi/fastapi/pull/11390) by [@nilslindemann](https://github.com/nilslindemann).
+* 📝 Add missing `compresslevel` parameter on docs for `GZipMiddleware`. PR [#11350](https://github.com/fastapi/fastapi/pull/11350) by [@junah201](https://github.com/junah201).
+* 📝 Fix inconsistent response code when item already exists in docs for testing. PR [#11818](https://github.com/fastapi/fastapi/pull/11818) by [@lokomilo](https://github.com/lokomilo).
+* 📝 Update `docs/en/docs/tutorial/body.md` with Python 3.10 union type example. PR [#11415](https://github.com/fastapi/fastapi/pull/11415) by [@rangzen](https://github.com/rangzen).
+
+### Translations
+
+* 🌐 Add Portuguese translation for `docs/pt/docs/tutorial/request_file.md`. PR [#12018](https://github.com/fastapi/fastapi/pull/12018) by [@Joao-Pedro-P-Holanda](https://github.com/Joao-Pedro-P-Holanda).
+* 🌐 Add Japanese translation for `docs/ja/docs/learn/index.md`. PR [#11592](https://github.com/fastapi/fastapi/pull/11592) by [@ukwhatn](https://github.com/ukwhatn).
+* 📝 Update Spanish translation docs for consistency. PR [#12044](https://github.com/fastapi/fastapi/pull/12044) by [@alejsdev](https://github.com/alejsdev).
+* 🌐 Update Chinese translation for `docs/zh/docs/tutorial/dependencies/dependencies-with-yield.md`. PR [#12028](https://github.com/fastapi/fastapi/pull/12028) by [@xuvjso](https://github.com/xuvjso).
+* 📝 Update FastAPI People, do not translate to have the most recent info. PR [#12034](https://github.com/fastapi/fastapi/pull/12034) by [@tiangolo](https://github.com/tiangolo).
+* 🌐 Update Urdu translation for `docs/ur/docs/benchmarks.md`. PR [#10046](https://github.com/fastapi/fastapi/pull/10046) by [@AhsanSheraz](https://github.com/AhsanSheraz).
+
+### Internal
+
+* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#12046](https://github.com/fastapi/fastapi/pull/12046) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
+* 🔧 Update coverage config files. PR [#12035](https://github.com/fastapi/fastapi/pull/12035) by [@tiangolo](https://github.com/tiangolo).
+* 🔨 Standardize shebang across shell scripts. PR [#11942](https://github.com/fastapi/fastapi/pull/11942) by [@gitworkflows](https://github.com/gitworkflows).
+* ⬆ Update sqlalchemy requirement from <1.4.43,>=1.3.18 to >=1.3.18,<2.0.33. PR [#11979](https://github.com/fastapi/fastapi/pull/11979) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* 🔊 Remove old ignore warnings. PR [#11950](https://github.com/fastapi/fastapi/pull/11950) by [@tiangolo](https://github.com/tiangolo).
+* ⬆️ Upgrade griffe-typingdoc for the docs. PR [#12029](https://github.com/fastapi/fastapi/pull/12029) by [@tiangolo](https://github.com/tiangolo).
+* 🙈 Add .coverage* to `.gitignore`. PR [#11940](https://github.com/fastapi/fastapi/pull/11940) by [@gitworkflows](https://github.com/gitworkflows).
+* ⚙️ Record and show test coverage contexts (what test covers which line). PR [#11518](https://github.com/fastapi/fastapi/pull/11518) by [@slafs](https://github.com/slafs).
+
+## 0.112.1
+
+### Upgrades
+
+* ⬆️ Allow Starlette 0.38.x, update the pin to `>=0.37.2,<0.39.0`. PR [#11876](https://github.com/fastapi/fastapi/pull/11876) by [@musicinmybrain](https://github.com/musicinmybrain).
+
+### Docs
+
+* 📝 Update docs section about "Don't Translate these Pages". PR [#12022](https://github.com/fastapi/fastapi/pull/12022) by [@tiangolo](https://github.com/tiangolo).
+* 📝 Add documentation for non-translated pages and scripts to verify them. PR [#12020](https://github.com/fastapi/fastapi/pull/12020) by [@tiangolo](https://github.com/tiangolo).
+* 📝 Update docs about discussions questions. PR [#11985](https://github.com/fastapi/fastapi/pull/11985) by [@tiangolo](https://github.com/tiangolo).
+
### Translations
+* 🌐 Add Portuguese translation for `docs/pt/docs/tutorial/bigger-applications.md`. PR [#11971](https://github.com/fastapi/fastapi/pull/11971) by [@marcelomarkus](https://github.com/marcelomarkus).
+* 🌐 Add Portuguese translation for `docs/pt/docs/advanced/testing-websockets.md`. PR [#11994](https://github.com/fastapi/fastapi/pull/11994) by [@ceb10n](https://github.com/ceb10n).
+* 🌐 Add Portuguese translation for `docs/pt/docs/advanced/testing-dependencies.md`. PR [#11995](https://github.com/fastapi/fastapi/pull/11995) by [@ceb10n](https://github.com/ceb10n).
* 🌐 Add Portuguese translation for `docs/pt/docs/advanced/using-request-directly.md`. PR [#11956](https://github.com/fastapi/fastapi/pull/11956) by [@ceb10n](https://github.com/ceb10n).
* 🌐 Add French translation for `docs/fr/docs/tutorial/body-multiple-params.md`. PR [#11796](https://github.com/fastapi/fastapi/pull/11796) by [@pe-brian](https://github.com/pe-brian).
* 🌐 Update Chinese translation for `docs/zh/docs/tutorial/query-params.md`. PR [#11557](https://github.com/fastapi/fastapi/pull/11557) by [@caomingpei](https://github.com/caomingpei).
@@ -19,6 +247,22 @@ hide:
### Internal
+* ⬆ Bump pypa/gh-action-pypi-publish from 1.8.14 to 1.9.0. PR [#11727](https://github.com/fastapi/fastapi/pull/11727) by [@dependabot[bot]](https://github.com/apps/dependabot).
+* 🔧 Add changelog URL to `pyproject.toml`, shows in PyPI. PR [#11152](https://github.com/fastapi/fastapi/pull/11152) by [@Pierre-VF](https://github.com/Pierre-VF).
+* 👷 Do not sync labels as it overrides manually added labels. PR [#12024](https://github.com/fastapi/fastapi/pull/12024) by [@tiangolo](https://github.com/tiangolo).
+* 👷🏻 Update Labeler GitHub Actions. PR [#12019](https://github.com/fastapi/fastapi/pull/12019) by [@tiangolo](https://github.com/tiangolo).
+* 🔧 Update configs for MkDocs for languages and social cards. PR [#12016](https://github.com/fastapi/fastapi/pull/12016) by [@tiangolo](https://github.com/tiangolo).
+* 👷 Update permissions and config for labeler GitHub Action. PR [#12008](https://github.com/fastapi/fastapi/pull/12008) by [@tiangolo](https://github.com/tiangolo).
+* 👷🏻 Add GitHub Action label-checker. PR [#12005](https://github.com/fastapi/fastapi/pull/12005) by [@tiangolo](https://github.com/tiangolo).
+* 👷 Add label checker GitHub Action. PR [#12004](https://github.com/fastapi/fastapi/pull/12004) by [@tiangolo](https://github.com/tiangolo).
+* 👷 Update GitHub Action add-to-project. PR [#12002](https://github.com/fastapi/fastapi/pull/12002) by [@tiangolo](https://github.com/tiangolo).
+* 🔧 Update labeler GitHub Action. PR [#12001](https://github.com/fastapi/fastapi/pull/12001) by [@tiangolo](https://github.com/tiangolo).
+* 👷 Add GitHub Action labeler. PR [#12000](https://github.com/fastapi/fastapi/pull/12000) by [@tiangolo](https://github.com/tiangolo).
+* 👷 Add GitHub Action add-to-project. PR [#11999](https://github.com/fastapi/fastapi/pull/11999) by [@tiangolo](https://github.com/tiangolo).
+* 📝 Update admonitions in docs missing. PR [#11998](https://github.com/fastapi/fastapi/pull/11998) by [@tiangolo](https://github.com/tiangolo).
+* 🔨 Update docs.py script to enable dirty reload conditionally. PR [#11986](https://github.com/fastapi/fastapi/pull/11986) by [@tiangolo](https://github.com/tiangolo).
+* 🔧 Update MkDocs instant previews. PR [#11982](https://github.com/fastapi/fastapi/pull/11982) by [@tiangolo](https://github.com/tiangolo).
+* 🐛 Fix deploy docs previews script to handle mkdocs.yml files. PR [#11984](https://github.com/fastapi/fastapi/pull/11984) by [@tiangolo](https://github.com/tiangolo).
* 💡 Add comment about custom Termynal line-height. PR [#11976](https://github.com/fastapi/fastapi/pull/11976) by [@tiangolo](https://github.com/tiangolo).
* 👷 Add alls-green for test-redistribute. PR [#11974](https://github.com/fastapi/fastapi/pull/11974) by [@tiangolo](https://github.com/tiangolo).
* 👷 Update docs-previews to handle no docs changes. PR [#11975](https://github.com/fastapi/fastapi/pull/11975) by [@tiangolo](https://github.com/tiangolo).
diff --git a/docs/en/docs/tutorial/background-tasks.md b/docs/en/docs/tutorial/background-tasks.md
index 5370b9ba8..8b4476e41 100644
--- a/docs/en/docs/tutorial/background-tasks.md
+++ b/docs/en/docs/tutorial/background-tasks.md
@@ -9,7 +9,7 @@ This includes, for example:
* Email notifications sent after performing an action:
* As connecting to an email server and sending an email tends to be "slow" (several seconds), you can return the response right away and send the email notification in the background.
* Processing data:
- * For example, let's say you receive a file that must go through a slow process, you can return a response of "Accepted" (HTTP 202) and process it in the background.
+ * For example, let's say you receive a file that must go through a slow process, you can return a response of "Accepted" (HTTP 202) and process the file in the background.
## Using `BackgroundTasks`
diff --git a/docs/en/docs/tutorial/bigger-applications.md b/docs/en/docs/tutorial/bigger-applications.md
index 97f6b205b..1c33fd051 100644
--- a/docs/en/docs/tutorial/bigger-applications.md
+++ b/docs/en/docs/tutorial/bigger-applications.md
@@ -1,6 +1,6 @@
# Bigger Applications - Multiple Files
-If you are building an application or a web API, it's rarely the case that you can put everything on a single file.
+If you are building an application or a web API, it's rarely the case that you can put everything in a single file.
**FastAPI** provides a convenience tool to structure your application while keeping all the flexibility.
@@ -478,7 +478,7 @@ We can declare all that without having to modify the original `APIRouter` by pas
{!../../../docs_src/bigger_applications/app/main.py!}
```
-That way, the original `APIRouter` will keep unmodified, so we can still share that same `app/internal/admin.py` file with other projects in the organization.
+That way, the original `APIRouter` will stay unmodified, so we can still share that same `app/internal/admin.py` file with other projects in the organization.
The result is that in our app, each of the *path operations* from the `admin` module will have:
@@ -519,12 +519,12 @@ As we cannot just isolate them and "mount" them independently of the rest, the *
## Check the automatic API docs
-Now, run `uvicorn`, using the module `app.main` and the variable `app`:
+Now, run your app:
email_validator
- para validación de emails.
+* email-validator
- para validación de emails.
Usados por Starlette:
diff --git a/docs/es/docs/newsletter.md b/docs/es/docs/newsletter.md
deleted file mode 100644
index f4dcfe155..000000000
--- a/docs/es/docs/newsletter.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Boletín de Noticias de FastAPI y amigos
-
-
-
-
diff --git a/docs/es/docs/project-generation.md b/docs/es/docs/project-generation.md
new file mode 100644
index 000000000..63febf1ae
--- /dev/null
+++ b/docs/es/docs/project-generation.md
@@ -0,0 +1,28 @@
+# Plantilla de FastAPI Full Stack
+
+Las plantillas, aunque típicamente vienen con una configuración específica, están diseñadas para ser flexibles y personalizables. Esto te permite modificarlas y adaptarlas a los requisitos de tu proyecto, lo que las convierte en un excelente punto de partida. 🏁
+
+Puedes utilizar esta plantilla para comenzar, ya que incluye gran parte de la configuración inicial, seguridad, base de datos y algunos endpoints de API ya realizados.
+
+Repositorio en GitHub: [Full Stack FastAPI Template](https://github.com/tiangolo/full-stack-fastapi-template)
+
+## Plantilla de FastAPI Full Stack - Tecnología y Características
+
+- ⚡ [**FastAPI**](https://fastapi.tiangolo.com) para el backend API en Python.
+ - 🧰 [SQLModel](https://sqlmodel.tiangolo.com) para las interacciones con la base de datos SQL en Python (ORM).
+ - 🔍 [Pydantic](https://docs.pydantic.dev), utilizado por FastAPI, para la validación de datos y la gestión de configuraciones.
+ - 💾 [PostgreSQL](https://www.postgresql.org) como la base de datos SQL.
+- 🚀 [React](https://react.dev) para el frontend.
+ - 💃 Usando TypeScript, hooks, Vite y otras partes de un stack de frontend moderno.
+ - 🎨 [Chakra UI](https://chakra-ui.com) para los componentes del frontend.
+ - 🤖 Un cliente frontend generado automáticamente.
+ - 🧪 Playwright para pruebas End-to-End.
+ - 🦇 Soporte para modo oscuro.
+- 🐋 [Docker Compose](https://www.docker.com) para desarrollo y producción.
+- 🔒 Hashing seguro de contraseñas por defecto.
+- 🔑 Autenticación con token JWT.
+- 📫 Recuperación de contraseñas basada en email.
+- ✅ Tests con [Pytest](https://pytest.org).
+- 📞 [Traefik](https://traefik.io) como proxy inverso / balanceador de carga.
+- 🚢 Instrucciones de despliegue utilizando Docker Compose, incluyendo cómo configurar un proxy frontend Traefik para manejar certificados HTTPS automáticos.
+- 🏭 CI (integración continua) y CD (despliegue continuo) basados en GitHub Actions.
diff --git a/docs/es/docs/python-types.md b/docs/es/docs/python-types.md
index fce434483..4015dbb05 100644
--- a/docs/es/docs/python-types.md
+++ b/docs/es/docs/python-types.md
@@ -12,7 +12,7 @@ Todo **FastAPI** está basado en estos type hints, lo que le da muchas ventajas
Pero, así nunca uses **FastAPI** te beneficiarás de aprender un poco sobre los type hints.
-/// note | "Nota"
+/// note | Nota
Si eres un experto en Python y ya lo sabes todo sobre los type hints, salta al siguiente capítulo.
@@ -256,7 +256,7 @@ Tomado de la documentación oficial de Pydantic:
{!../../../docs_src/python_types/tutorial010.py!}
```
-/// info | "Información"
+/// info | Información
Para aprender más sobre Pydantic mira su documentación.
@@ -288,7 +288,7 @@ Puede que todo esto suene abstracto. Pero no te preocupes que todo lo verás en
Lo importante es que usando los tipos de Python estándar en un único lugar (en vez de añadir más clases, decorator, etc.) **FastAPI** hará mucho del trabajo por ti.
-/// info | "Información"
+/// info | Información
Si ya pasaste por todo el tutorial y volviste a la sección de los tipos, una buena referencia es la "cheat sheet" de `mypy`.
diff --git a/docs/es/docs/tutorial/cookie-params.md b/docs/es/docs/tutorial/cookie-params.md
index 27ba8ed57..3eaea31f9 100644
--- a/docs/es/docs/tutorial/cookie-params.md
+++ b/docs/es/docs/tutorial/cookie-params.md
@@ -32,9 +32,9 @@ Primero importa `Cookie`:
//// tab | Python 3.10+ non-Annotated
-/// tip
+/// tip | Consejo
-Prefer to use the `Annotated` version if possible.
+Es preferible utilizar la versión `Annotated` si es posible.
///
@@ -46,9 +46,9 @@ Prefer to use the `Annotated` version if possible.
//// tab | Python 3.8+ non-Annotated
-/// tip
+/// tip | Consejo
-Prefer to use the `Annotated` version if possible.
+Es preferible utilizar la versión `Annotated` si es posible.
///
@@ -90,9 +90,9 @@ El primer valor es el valor por defecto, puedes pasar todos los parámetros adic
//// tab | Python 3.10+ non-Annotated
-/// tip
+/// tip | Consejo
-Prefer to use the `Annotated` version if possible.
+Es preferible utilizar la versión `Annotated` si es posible.
///
@@ -104,9 +104,9 @@ Prefer to use the `Annotated` version if possible.
//// tab | Python 3.8+ non-Annotated
-/// tip
+/// tip | Consejo
-Prefer to use the `Annotated` version if possible.
+Es preferible utilizar la versión `Annotated` si es posible.
///
diff --git a/docs/es/docs/tutorial/first-steps.md b/docs/es/docs/tutorial/first-steps.md
index affdfebff..8d8909b97 100644
--- a/docs/es/docs/tutorial/first-steps.md
+++ b/docs/es/docs/tutorial/first-steps.md
@@ -24,7 +24,7 @@ $ uvicorn main:app --reload
get
-/// info | "Información sobre `@decorator`"
+/// info | Información sobre `@decorator`
Esa sintaxis `@algo` se llama un "decorador" en Python.
@@ -286,7 +286,7 @@ y las más exóticas:
* `@app.patch()`
* `@app.trace()`
-/// tip | "Consejo"
+/// tip | Consejo
Tienes la libertad de usar cada operación (método de HTTP) como quieras.
@@ -324,7 +324,7 @@ También podrías definirla como una función estándar en lugar de `async def`:
{!../../../docs_src/first_steps/tutorial003.py!}
```
-/// note | "Nota"
+/// note | Nota
Si no sabes la diferencia, revisa el [Async: *"¿Tienes prisa?"*](../async.md#tienes-prisa){.internal-link target=_blank}.
diff --git a/docs/es/docs/tutorial/path-params.md b/docs/es/docs/tutorial/path-params.md
index 73bd586f1..e09e0381f 100644
--- a/docs/es/docs/tutorial/path-params.md
+++ b/docs/es/docs/tutorial/path-params.md
@@ -24,7 +24,7 @@ Puedes declarar el tipo de un parámetro de path en la función usando las anota
En este caso, `item_id` es declarado como un `int`.
-/// check | "Revisa"
+/// check | Revisa
Esto te dará soporte en el editor dentro de tu función, con chequeo de errores, auto-completado, etc.
@@ -38,7 +38,7 @@ Si corres este ejemplo y abres tu navegador en http://127.0.0.1:8000/items/4.2
-/// check | "Revisa"
+/// check | Revisa
Así, con la misma declaración de tipo de Python, **FastAPI** te da validación de datos.
@@ -85,7 +85,7 @@ Cuando abras tu navegador en
-/// check | "Revisa"
+/// check | Revisa
Nuevamente, con la misma declaración de tipo de Python, **FastAPI** te da documentación automática e interactiva (integrándose con Swagger UI)
@@ -143,13 +143,13 @@ Luego crea atributos de clase con valores fijos, que serán los valores disponib
{!../../../docs_src/path_params/tutorial005.py!}
```
-/// info | "Información"
+/// info | Información
Las Enumerations (o enums) están disponibles en Python desde la versión 3.4.
///
-/// tip | "Consejo"
+/// tip | Consejo
Si lo estás dudando, "AlexNet", "ResNet", y "LeNet" son solo nombres de modelos de Machine Learning.
@@ -189,7 +189,7 @@ Puedes obtener el valor exacto (un `str` en este caso) usando `model_name.value`
{!../../../docs_src/path_params/tutorial005.py!}
```
-/// tip | "Consejo"
+/// tip | Consejo
También podrías obtener el valor `"lenet"` con `ModelName.lenet.value`.
@@ -246,7 +246,7 @@ Entonces lo puedes usar con:
{!../../../docs_src/path_params/tutorial004.py!}
```
-/// tip | "Consejo"
+/// tip | Consejo
Podrías necesitar que el parámetro contenga `/home/johndoe/myfile.txt` con un slash inicial (`/`).
diff --git a/docs/es/docs/tutorial/query-params.md b/docs/es/docs/tutorial/query-params.md
index 52a3e66a4..6f88fd617 100644
--- a/docs/es/docs/tutorial/query-params.md
+++ b/docs/es/docs/tutorial/query-params.md
@@ -69,13 +69,13 @@ Del mismo modo puedes declarar parámetros de query opcionales definiendo el val
En este caso el parámetro de la función `q` será opcional y será `None` por defecto.
-/// check | "Revisa"
+/// check | Revisa
También puedes notar que **FastAPI** es lo suficientemente inteligente para darse cuenta de que el parámetro de path `item_id` es un parámetro de path y que `q` no lo es, y por lo tanto es un parámetro de query.
///
-/// note | "Nota"
+/// note | Nota
FastAPI sabrá que `q` es opcional por el `= None`.
@@ -199,7 +199,7 @@ En este caso hay 3 parámetros de query:
* `skip`, un `int` con un valor por defecto de `0`.
* `limit`, un `int` opcional.
-/// tip | "Consejo"
+/// tip | Consejo
También podrías usar los `Enum`s de la misma manera que con los [Parámetros de path](path-params.md#valores-predefinidos){.internal-link target=_blank}.
diff --git a/docs/fa/docs/index.md b/docs/fa/docs/index.md
index bc8b77941..1ae566a9f 100644
--- a/docs/fa/docs/index.md
+++ b/docs/fa/docs/index.md
@@ -442,7 +442,7 @@ item: Item
استفاده شده توسط Pydantic:
-* email_validator
- برای اعتبارسنجی آدرسهای ایمیل.
+* email-validator
- برای اعتبارسنجی آدرسهای ایمیل.
استفاده شده توسط Starlette:
diff --git a/docs/fr/docs/external-links.md b/docs/fr/docs/external-links.md
deleted file mode 100644
index 91a9eae58..000000000
--- a/docs/fr/docs/external-links.md
+++ /dev/null
@@ -1,36 +0,0 @@
-# Articles et liens externes
-
-**FastAPI** possède une grande communauté en constante extension.
-
-Il existe de nombreux articles, outils et projets liés à **FastAPI**.
-
-Voici une liste incomplète de certains d'entre eux.
-
-/// tip | "Astuce"
-
-Si vous avez un article, projet, outil, ou quoi que ce soit lié à **FastAPI** qui n'est actuellement pas listé ici, créez une Pull Request l'ajoutant.
-
-///
-
-{% for section_name, section_content in external_links.items() %}
-
-## {{ section_name }}
-
-{% for lang_name, lang_content in section_content.items() %}
-
-### {{ lang_name }}
-
-{% for item in lang_content %}
-
-* {{ item.title }} by {{ item.author }}.
-
-{% endfor %}
-{% endfor %}
-{% endfor %}
-
-## Projets
-
-Les projets Github avec le topic `fastapi` les plus récents :
-
-email_validator
- pour la validation des adresses email.
+* email-validator
- pour la validation des adresses email.
Utilisées par Starlette :
diff --git a/docs/he/docs/index.md b/docs/he/docs/index.md
index 3af166ab0..23a2eb824 100644
--- a/docs/he/docs/index.md
+++ b/docs/he/docs/index.md
@@ -446,7 +446,7 @@ item: Item
בשימוש Pydantic:
-- email_validator
- לאימות כתובות אימייל.
+- email-validator
- לאימות כתובות אימייל.
בשימוש Starlette:
diff --git a/docs/hu/docs/index.md b/docs/hu/docs/index.md
index e605bbb55..8e326a78b 100644
--- a/docs/hu/docs/index.md
+++ b/docs/hu/docs/index.md
@@ -443,7 +443,7 @@ Ezeknek a további megértéséhez: email_validator
- e-mail validációkra.
+* email-validator
- e-mail validációkra.
* pydantic-settings
- Beállítások követésére.
* pydantic-extra-types
- Extra típusok Pydantic-hoz.
diff --git a/docs/it/docs/index.md b/docs/it/docs/index.md
index 272f9a37e..57940f0ed 100644
--- a/docs/it/docs/index.md
+++ b/docs/it/docs/index.md
@@ -437,7 +437,7 @@ Per approfondire, consulta la sezione email_validator
- per la validazione di email.
+* email-validator
- per la validazione di email.
Usate da Starlette:
diff --git a/docs/ja/docs/deployment/docker.md b/docs/ja/docs/deployment/docker.md
index c294ef88d..53fc851f1 100644
--- a/docs/ja/docs/deployment/docker.md
+++ b/docs/ja/docs/deployment/docker.md
@@ -213,8 +213,11 @@ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
4. 要件ファイルにあるパッケージの依存関係をインストールします
`--no-cache-dir` オプションはダウンロードしたパッケージをローカルに保存しないように `pip` に指示します。これは、同じパッケージをインストールするために `pip` を再度実行する場合にのみ有効ですが、コンテナで作業する場合はそうではないです。
- !!! note
- `--no-cache-dir`は`pip`に関連しているだけで、Dockerやコンテナとは何の関係もないです。
+ /// note
+
+ `--no-cache-dir`は`pip`に関連しているだけで、Dockerやコンテナとは何の関係もないです。
+
+ ///
`--upgrade` オプションは、パッケージが既にインストールされている場合、`pip` にアップグレードするように指示します。
diff --git a/docs/ja/docs/external-links.md b/docs/ja/docs/external-links.md
deleted file mode 100644
index 65cebc8d2..000000000
--- a/docs/ja/docs/external-links.md
+++ /dev/null
@@ -1,36 +0,0 @@
-# 外部リンク・記事
-
-**FastAPI**には、絶えず成長している素晴らしいコミュニティがあります。
-
-**FastAPI**に関連する投稿、記事、ツール、およびプロジェクトは多数あります。
-
-それらの不完全なリストを以下に示します。
-
-/// tip | "豆知識"
-
-ここにまだ載っていない**FastAPI**に関連する記事、プロジェクト、ツールなどがある場合は、 プルリクエストして下さい。
-
-///
-
-{% for section_name, section_content in external_links.items() %}
-
-## {{ section_name }}
-
-{% for lang_name, lang_content in section_content.items() %}
-
-### {{ lang_name }}
-
-{% for item in lang_content %}
-
-* {{ item.title }} by {{ item.author }}.
-
-{% endfor %}
-{% endfor %}
-{% endfor %}
-
-## プロジェクト
-
-`fastapi`トピックの最新のGitHubプロジェクト:
-
-email_validator
- E メールの検証
+- email-validator
- E メールの検証
Starlette によって使用されるもの:
diff --git a/docs/ja/docs/learn/index.md b/docs/ja/docs/learn/index.md
new file mode 100644
index 000000000..2f24c670a
--- /dev/null
+++ b/docs/ja/docs/learn/index.md
@@ -0,0 +1,5 @@
+# 学習
+
+ここでは、**FastAPI** を学習するための入門セクションとチュートリアルを紹介します。
+
+これは、FastAPIを学習するにあたっての**書籍**や**コース**であり、**公式**かつ推奨される方法とみなすことができます 😎
diff --git a/docs/ko/docs/deployment/docker.md b/docs/ko/docs/deployment/docker.md
index edf10b318..502a36fc0 100644
--- a/docs/ko/docs/deployment/docker.md
+++ b/docs/ko/docs/deployment/docker.md
@@ -205,8 +205,11 @@ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
`--no-cache-dir` 옵션은 `pip`에게 다운로드한 패키지들을 로컬 환경에 저장하지 않도록 전달합니다. 이는 마치 같은 패키지를 설치하기 위해 오직 `pip`만 다시 실행하면 될 것 같지만, 컨테이너로 작업하는 경우 그렇지는 않습니다.
- !!! note "노트"
- `--no-cache-dir` 는 오직 `pip`와 관련되어 있으며, 도커나 컨테이너와는 무관합니다.
+ /// note | 노트
+
+ `--no-cache-dir` 는 오직 `pip`와 관련되어 있으며, 도커나 컨테이너와는 무관합니다.
+
+ ///
`--upgrade` 옵션은 `pip`에게 설치된 패키지들을 업데이트하도록 합니다.
diff --git a/docs/ko/docs/help-fastapi.md b/docs/ko/docs/help-fastapi.md
index 7c3b15d33..932952b4a 100644
--- a/docs/ko/docs/help-fastapi.md
+++ b/docs/ko/docs/help-fastapi.md
@@ -118,7 +118,11 @@
👥 [디스코드 채팅 서버](https://discord.gg/VQjSZaeJmf) 👥 에 가입하고 FastAPI 커뮤니티에서 다른 사람들과 어울리세요.
- !!! tip 질문이 있는 경우, [GitHub 이슈 ](https://github.com/fastapi/fastapi/issues/new/choose) 에서 질문하십시오, [FastAPI 전문가](https://github.com/fastapi/fastapi/blob/master/docs/en/docs/fastapi-people.md#experts) 의 도움을 받을 가능성이 높습니다{.internal-link target=_blank} .
+ /// tip
+
+ 질문이 있는 경우, [GitHub 이슈 ](https://github.com/fastapi/fastapi/issues/new/choose) 에서 질문하십시오, [FastAPI 전문가](https://github.com/fastapi/fastapi/blob/master/docs/en/docs/fastapi-people.md#experts) 의 도움을 받을 가능성이 높습니다{.internal-link target=_blank} .
+
+ ///
```
다른 일반적인 대화에서만 채팅을 사용하십시오.
diff --git a/docs/ko/docs/index.md b/docs/ko/docs/index.md
index 620fcc881..8b00d90bc 100644
--- a/docs/ko/docs/index.md
+++ b/docs/ko/docs/index.md
@@ -441,7 +441,7 @@ item: Item
Pydantic이 사용하는:
-* email_validator
- 이메일 유효성 검사.
+* email-validator
- 이메일 유효성 검사.
Starlette이 사용하는:
diff --git a/docs/ko/docs/project-generation.md b/docs/ko/docs/project-generation.md
new file mode 100644
index 000000000..019919f77
--- /dev/null
+++ b/docs/ko/docs/project-generation.md
@@ -0,0 +1,28 @@
+# Full Stack FastAPI 템플릿
+
+템플릿은 일반적으로 특정 설정과 함께 제공되지만, 유연하고 커스터마이징이 가능하게 디자인 되었습니다. 이 특성들은 여러분이 프로젝트의 요구사항에 맞춰 수정, 적용을 할 수 있게 해주고, 템플릿이 완벽한 시작점이 되게 해줍니다. 🏁
+
+많은 초기 설정, 보안, 데이터베이스 및 일부 API 엔드포인트가 이미 준비되어 있으므로, 여러분은 이 템플릿을 (프로젝트를) 시작하는 데 사용할 수 있습니다.
+
+GitHub 저장소: Full Stack FastAPI 템플릿
+
+## Full Stack FastAPI 템플릿 - 기술 스택과 기능들
+
+- ⚡ [**FastAPI**](https://fastapi.tiangolo.com): Python 백엔드 API.
+ - 🧰 [SQLModel](https://sqlmodel.tiangolo.com): Python SQL 데이터 상호작용을 위한 (ORM).
+ - 🔍 [Pydantic](https://docs.pydantic.dev): FastAPI에 의해 사용되는, 데이터 검증과 설정관리.
+ - 💾 [PostgreSQL](https://www.postgresql.org): SQL 데이터베이스.
+- 🚀 [React](https://react.dev): 프론트엔드.
+ - 💃 TypeScript, hooks, Vite 및 기타 현대적인 프론트엔드 스택을 사용.
+ - 🎨 [Chakra UI](https://chakra-ui.com): 프론트엔드 컴포넌트.
+ - 🤖 자동으로 생성된 프론트엔드 클라이언트.
+ - 🧪 E2E 테스트를 위한 Playwright.
+ - 🦇 다크 모드 지원.
+- 🐋 [Docker Compose](https://www.docker.com): 개발 환경과 프로덕션(운영).
+- 🔒 기본으로 지원되는 안전한 비밀번호 해싱.
+- 🔑 JWT 토큰 인증.
+- 📫 이메일 기반 비밀번호 복구.
+- ✅ [Pytest]를 이용한 테스트(https://pytest.org).
+- 📞 [Traefik](https://traefik.io): 리버스 프록시 / 로드 밸런서.
+- 🚢 Docker Compose를 이용한 배포 지침: 자동 HTTPS 인증서를 처리하기 위한 프론트엔드 Traefik 프록시 설정 방법을 포함.
+- 🏭 GitHub Actions를 기반으로 CI (지속적인 통합) 및 CD (지속적인 배포).
diff --git a/docs/nl/docs/features.md b/docs/nl/docs/features.md
new file mode 100644
index 000000000..848b155ec
--- /dev/null
+++ b/docs/nl/docs/features.md
@@ -0,0 +1,201 @@
+# Functionaliteit
+
+## FastAPI functionaliteit
+
+**FastAPI** biedt je het volgende:
+
+### Gebaseerd op open standaarden
+
+* OpenAPI voor het maken van API's, inclusief declaraties van padbewerkingen, parameters, request bodies, beveiliging, enz.
+* Automatische datamodel documentatie met JSON Schema (aangezien OpenAPI zelf is gebaseerd op JSON Schema).
+* Ontworpen op basis van deze standaarden, na zorgvuldig onderzoek. In plaats van achteraf deze laag er bovenop te bouwen.
+* Dit maakt het ook mogelijk om automatisch **clientcode te genereren** in verschillende programmeertalen.
+
+### Automatische documentatie
+
+Interactieve API-documentatie en verkenning van webgebruikersinterfaces. Aangezien dit framework is gebaseerd op OpenAPI, zijn er meerdere documentatie opties mogelijk, waarvan er standaard 2 zijn inbegrepen.
+
+* Swagger UI, met interactieve interface, maakt het mogelijk je API rechtstreeks vanuit de browser aan te roepen en te testen.
+
+
+
+* Alternatieve API-documentatie met ReDoc.
+
+
+
+### Gewoon Moderne Python
+
+Het is allemaal gebaseerd op standaard **Python type** declaraties (dankzij Pydantic). Je hoeft dus geen nieuwe syntax te leren. Het is gewoon standaard moderne Python.
+
+Als je een opfriscursus van 2 minuten nodig hebt over het gebruik van Python types (zelfs als je FastAPI niet gebruikt), bekijk dan deze korte tutorial: [Python Types](python-types.md){.internal-link target=_blank}.
+
+Je schrijft gewoon standaard Python met types:
+
+```Python
+from datetime import date
+
+from pydantic import BaseModel
+
+# Declareer een variabele als een str
+# en krijg editorondersteuning in de functie
+def main(user_id: str):
+ return user_id
+
+
+# Een Pydantic model
+class User(BaseModel):
+ id: int
+ name: str
+ joined: date
+```
+
+Vervolgens kan je het op deze manier gebruiken:
+
+```Python
+my_user: User = User(id=3, name="John Doe", joined="2018-07-19")
+
+second_user_data = {
+ "id": 4,
+ "name": "Mary",
+ "joined": "2018-11-30",
+}
+
+my_second_user: User = User(**second_user_data)
+```
+
+/// info
+
+`**second_user_data` betekent:
+
+Geef de sleutels (keys) en waarden (values) van de `second_user_data` dict direct door als sleutel-waarden argumenten, gelijk aan: `User(id=4, name=“Mary”, joined=“2018-11-30”)`
+
+///
+
+### Editor-ondersteuning
+
+Het gehele framework is ontworpen om eenvoudig en intuïtief te zijn in gebruik. Alle beslissingen zijn getest op meerdere code-editors nog voordat het daadwerkelijke ontwikkelen begon, om zo de beste ontwikkelervaring te garanderen.
+
+Uit enquêtes onder Python ontwikkelaars blijkt maar al te duidelijk dat "(automatische) code aanvulling" een van de meest gebruikte functionaliteiten is.
+
+Het hele **FastAPI** framework is daarop gebaseerd. Automatische code aanvulling werkt overal.
+
+Je hoeft zelden terug te vallen op de documentatie.
+
+Zo kan je editor je helpen:
+
+* in Visual Studio Code:
+
+
+
+* in PyCharm:
+
+
+
+Je krijgt autocomletion die je voorheen misschien zelfs voor onmogelijk had gehouden. Zoals bijvoorbeeld de `price` key in een JSON body (die genest had kunnen zijn) die afkomstig is van een request.
+
+Je hoeft niet langer de verkeerde keys in te typen, op en neer te gaan tussen de documentatie, of heen en weer te scrollen om te checken of je `username` of toch `user_name` had gebruikt.
+
+### Kort
+
+Dit framework heeft voor alles verstandige **standaardinstellingen**, met overal optionele configuraties. Alle parameters kunnen worden verfijnd zodat het past bij wat je nodig hebt, om zo de API te kunnen definiëren die jij nodig hebt.
+
+Maar standaard werkt alles **“gewoon”**.
+
+### Validatie
+
+* Validatie voor de meeste (of misschien wel alle?) Python **datatypes**, inclusief:
+ * JSON objecten (`dict`).
+ * JSON array (`list`) die itemtypes definiëren.
+ * String (`str`) velden, die min en max lengtes hebben.
+ * Getallen (`int`, `float`) met min en max waarden, enz.
+
+* Validatie voor meer exotische typen, zoals:
+ * URL.
+ * E-mail.
+ * UUID.
+ * ...en anderen.
+
+Alle validatie wordt uitgevoerd door het beproefde en robuuste **Pydantic**.
+
+### Beveiliging en authenticatie
+
+Beveiliging en authenticatie is geïntegreerd. Zonder compromissen te doen naar databases of datamodellen.
+
+Alle beveiligingsschema's gedefinieerd in OpenAPI, inclusief:
+
+* HTTP Basic.
+* **OAuth2** (ook met **JWT tokens**). Bekijk de tutorial over [OAuth2 with JWT](tutorial/security/oauth2-jwt.md){.internal-link target=_blank}.
+* API keys in:
+ * Headers.
+ * Query parameters.
+ * Cookies, enz.
+
+Plus alle beveiligingsfuncties van Starlette (inclusief **sessiecookies**).
+
+Gebouwd als een herbruikbare tool met componenten die makkelijk te integreren zijn in en met je systemen, datastores, relationele en NoSQL databases, enz.
+
+### Dependency Injection
+
+FastAPI bevat een uiterst eenvoudig, maar uiterst krachtig Dependency Injection systeem.
+
+* Zelfs dependencies kunnen dependencies hebben, waardoor een hiërarchie of **“graph” van dependencies** ontstaat.
+* Allemaal **automatisch afgehandeld** door het framework.
+* Alle dependencies kunnen data nodig hebben van request, de vereiste **padoperaties veranderen** en automatische documentatie verstrekken.
+* **Automatische validatie** zelfs voor *padoperatie* parameters gedefinieerd in dependencies.
+* Ondersteuning voor complexe gebruikersauthenticatiesystemen, **databaseverbindingen**, enz.
+* **Geen compromisen** met databases, gebruikersinterfaces, enz. Maar eenvoudige integratie met ze allemaal.
+
+### Ongelimiteerde "plug-ins"
+
+Of anders gezegd, je hebt ze niet nodig, importeer en gebruik de code die je nodig hebt.
+
+Elke integratie is ontworpen om eenvoudig te gebruiken (met afhankelijkheden), zodat je een “plug-in" kunt maken in 2 regels code, met dezelfde structuur en syntax die wordt gebruikt voor je *padbewerkingen*.
+
+### Getest
+
+* 100% van de code is getest.
+* 100% type geannoteerde codebase.
+* Wordt gebruikt in productietoepassingen.
+
+## Starlette functies
+
+**FastAPI** is volledig verenigbaar met (en gebaseerd op) Starlette.
+
+`FastAPI` is eigenlijk een subklasse van `Starlette`. Dus als je Starlette al kent of gebruikt, zal de meeste functionaliteit op dezelfde manier werken.
+
+Met **FastAPI** krijg je alle functies van **Starlette** (FastAPI is gewoon Starlette op steroïden):
+
+* Zeer indrukwekkende prestaties. Het is een van de snelste Python frameworks, vergelijkbaar met **NodeJS** en **Go**.
+* **WebSocket** ondersteuning.
+* Taken in de achtergrond tijdens het proces.
+* Opstart- en afsluit events.
+* Test client gebouwd op HTTPX.
+* **CORS**, GZip, Statische bestanden, Streaming reacties.
+* **Sessie en Cookie** ondersteuning.
+* 100% van de code is getest.
+* 100% type geannoteerde codebase.
+
+## Pydantic functionaliteit
+
+**FastAPI** is volledig verenigbaar met (en gebaseerd op) Pydantic. Dus alle extra Pydantic code die je nog hebt werkt ook.
+
+Inclusief externe pakketten die ook gebaseerd zijn op Pydantic, zoals ORMs, ODMs voor databases.
+
+Dit betekent ook dat je in veel gevallen het object dat je van een request krijgt **direct naar je database** kunt sturen, omdat alles automatisch wordt gevalideerd.
+
+Hetzelfde geldt ook andersom, in veel gevallen kun je dus het object dat je krijgt van de database **direct doorgeven aan de client**.
+
+Met **FastAPI** krijg je alle functionaliteit van **Pydantic** (omdat FastAPI is gebaseerd op Pydantic voor alle dataverwerking):
+
+* **Geen brainfucks**:
+ * Je hoeft geen nieuwe microtaal voor schemadefinities te leren.
+ * Als je bekend bent Python types, weet je hoe je Pydantic moet gebruiken.
+* Werkt goed samen met je **IDE/linter/hersenen**:
+ * Doordat pydantic's datastructuren enkel instanties zijn van klassen, die je definieert, werkt automatische aanvulling, linting, mypy en je intuïtie allemaal goed met je gevalideerde data.
+* Valideer **complexe structuren**:
+ * Gebruik van hiërarchische Pydantic modellen, Python `typing`'s `List` en `Dict`, enz.
+ * Met validators kunnen complexe dataschema's duidelijk en eenvoudig worden gedefinieerd, gecontroleerd en gedocumenteerd als JSON Schema.
+ * Je kunt diep **geneste JSON** objecten laten valideren en annoteren.
+* **Uitbreidbaar**:
+ * Met Pydantic kunnen op maat gemaakte datatypen worden gedefinieerd of je kunt validatie uitbreiden met methoden op een model dat is ingericht met de decorator validator.
+* 100% van de code is getest.
diff --git a/docs/nl/docs/index.md b/docs/nl/docs/index.md
new file mode 100644
index 000000000..8edc3ba0c
--- /dev/null
+++ b/docs/nl/docs/index.md
@@ -0,0 +1,494 @@
+# FastAPI
+
+
+
+
++ FastAPI framework, zeer goede prestaties, eenvoudig te leren, snel te programmeren, klaar voor productie +
+ + +--- + +**Documentatie**: https://fastapi.tiangolo.com + +**Broncode**: https://github.com/tiangolo/fastapi + +--- + +FastAPI is een modern, snel (zeer goede prestaties), web framework voor het bouwen van API's in Python, gebruikmakend van standaard Python type-hints. + +De belangrijkste kenmerken zijn: + +* **Snel**: Zeer goede prestaties, vergelijkbaar met **NodeJS** en **Go** (dankzij Starlette en Pydantic). [Een van de snelste beschikbare Python frameworks](#prestaties). +* **Snel te programmeren**: Verhoog de snelheid om functionaliteit te ontwikkelen met ongeveer 200% tot 300%. * +* **Minder bugs**: Verminder ongeveer 40% van de door mensen (ontwikkelaars) veroorzaakte fouten. * +* **Intuïtief**: Buitengewoon goede ondersteuning voor editors. Overal automische code aanvulling. Minder tijd kwijt aan debuggen. +* **Eenvoudig**: Ontworpen om gemakkelijk te gebruiken en te leren. Minder tijd nodig om documentatie te lezen. +* **Kort**: Minimaliseer codeduplicatie. Elke parameterdeclaratie ondersteunt meerdere functionaliteiten. Minder bugs. +* **Robust**: Code gereed voor productie. Met automatische interactieve documentatie. +* **Standards-based**: Gebaseerd op (en volledig verenigbaar met) open standaarden voor API's: OpenAPI (voorheen bekend als Swagger) en JSON Schema. + +* schatting op basis van testen met een intern ontwikkelteam en bouwen van productieapplicaties. + +## Sponsors + + + +{% if sponsors %} +{% for sponsor in sponsors.gold -%} +async def
...fastapi dev main.py
...email_validator
- voor email validatie.
+
+Gebruikt door Starlette:
+
+* httpx
- Vereist indien je de `TestClient` wil gebruiken.
+* jinja2
- Vereist als je de standaard templateconfiguratie wil gebruiken.
+* python-multipart
- Vereist indien je "parsen" van formulieren wil ondersteunen met `requests.form()`.
+
+Gebruikt door FastAPI / Starlette:
+
+* uvicorn
- voor de server die jouw applicatie laadt en bedient.
+* `fastapi-cli` - om het `fastapi` commando te voorzien.
+
+### Zonder `standard` Afhankelijkheden
+
+Indien je de optionele `standard` afhankelijkheden niet wenst te installeren, kan je installeren met `pip install fastapi` in plaats van `pip install "fastapi[standard]"`.
+
+### Bijkomende Optionele Afhankelijkheden
+
+Er zijn nog een aantal bijkomende afhankelijkheden die je eventueel kan installeren.
+
+Bijkomende optionele afhankelijkheden voor Pydantic:
+
+* pydantic-settings
- voor het beheren van settings.
+* pydantic-extra-types
- voor extra data types die gebruikt kunnen worden met Pydantic.
+
+Bijkomende optionele afhankelijkheden voor FastAPI:
+
+* orjson
- Vereist indien je `ORJSONResponse` wil gebruiken.
+* ujson
- Vereist indien je `UJSONResponse` wil gebruiken.
+
+## Licentie
+
+Dit project is gelicenseerd onder de voorwaarden van de MIT licentie.
diff --git a/docs/nl/mkdocs.yml b/docs/nl/mkdocs.yml
new file mode 100644
index 000000000..de18856f4
--- /dev/null
+++ b/docs/nl/mkdocs.yml
@@ -0,0 +1 @@
+INHERIT: ../en/mkdocs.yml
diff --git a/docs/pl/docs/fastapi-people.md b/docs/pl/docs/fastapi-people.md
deleted file mode 100644
index 6c431b401..000000000
--- a/docs/pl/docs/fastapi-people.md
+++ /dev/null
@@ -1,178 +0,0 @@
-# Ludzie FastAPI
-
-FastAPI posiada wspaniałą społeczność, która jest otwarta dla ludzi z każdego środowiska.
-
-## Twórca - Opienik
-
-Cześć! 👋
-
-To ja:
-
-{% if people %}
-email_validator
- dla walidacji adresów email.
+* email-validator
- dla walidacji adresów email.
Używane przez Starlette:
diff --git a/docs/pt/docs/advanced/async-tests.md b/docs/pt/docs/advanced/async-tests.md
index ab5bfa648..7cac26262 100644
--- a/docs/pt/docs/advanced/async-tests.md
+++ b/docs/pt/docs/advanced/async-tests.md
@@ -72,7 +72,7 @@ Note que a função de teste é `async def` agora, no lugar de apenas `def` como
Então podemos criar um `AsyncClient` com a aplicação, e enviar requisições assíncronas para ela utilizando `await`.
-```Python hl_lines="9-10"
+```Python hl_lines="9-12"
{!../../../docs_src/async_tests/test_main.py!}
```
diff --git a/docs/pt/docs/advanced/security/index.md b/docs/pt/docs/advanced/security/index.md
new file mode 100644
index 000000000..ae63f1c96
--- /dev/null
+++ b/docs/pt/docs/advanced/security/index.md
@@ -0,0 +1,19 @@
+# Segurança Avançada
+
+## Funcionalidades Adicionais
+
+Existem algumas funcionalidades adicionais para lidar com segurança além das cobertas em [Tutorial - Guia de Usuário: Segurança](../../tutorial/security/index.md){.internal-link target=_blank}.
+
+/// tip | "Dica"
+
+As próximas seções **não são necessariamente "avançadas"**.
+
+E é possível que para o seu caso de uso, a solução está em uma delas.
+
+///
+
+## Leia o Tutorial primeiro
+
+As próximas seções pressupõem que você já leu o principal [Tutorial - Guia de Usuário: Segurança](../../tutorial/security/index.md){.internal-link target=_blank}.
+
+Todas elas são baseadas nos mesmos conceitos, mas permitem algumas funcionalidades extras.
diff --git a/docs/pt/docs/advanced/testing-dependencies.md b/docs/pt/docs/advanced/testing-dependencies.md
new file mode 100644
index 000000000..747dd7d06
--- /dev/null
+++ b/docs/pt/docs/advanced/testing-dependencies.md
@@ -0,0 +1,103 @@
+# Testando Dependências com Sobreposição (Overrides)
+
+## Sobrepondo dependências durante os testes
+
+Existem alguns cenários onde você deseje sobrepor uma dependência durante os testes.
+
+Você não quer que a dependência original execute (e nenhuma das subdependências que você possa ter).
+
+Em vez disso, você deseja fornecer uma dependência diferente que será usada somente durante os testes (possivelmente apenas para alguns testes específicos) e fornecerá um valor que pode ser usado onde o valor da dependência original foi usado.
+
+### Casos de uso: serviço externo
+
+Um exemplo pode ser que você possua um provedor de autenticação externo que você precisa chamar.
+
+Você envia ao serviço um *token* e ele retorna um usuário autenticado.
+
+Este provedor pode cobrar por requisição, e chamá-lo pode levar mais tempo do que se você tivesse um usuário fixo para os testes.
+
+Você provavelmente quer testar o provedor externo uma vez, mas não necessariamente chamá-lo em todos os testes que executarem.
+
+Neste caso, você pode sobrepor (*override*) a dependência que chama o provedor, e utilizar uma dependência customizada que retorna um *mock* do usuário, apenas para os seus testes.
+
+### Utilize o atributo `app.dependency_overrides`
+
+Para estes casos, a sua aplicação **FastAPI** possui o atributo `app.dependency_overrides`. Ele é um simples `dict`.
+
+Para sobrepor a dependência para os testes, você coloca como chave a dependência original (a função), e como valor, a sua sobreposição da dependência (outra função).
+
+E então o **FastAPI** chamará a sobreposição no lugar da dependência original.
+
+//// tab | Python 3.10+
+
+```Python hl_lines="26-27 30"
+{!> ../../../docs_src/dependency_testing/tutorial001_an_py310.py!}
+```
+
+////
+
+//// tab | Python 3.9+
+
+```Python hl_lines="28-29 32"
+{!> ../../../docs_src/dependency_testing/tutorial001_an_py39.py!}
+```
+
+////
+
+//// tab | Python 3.8+
+
+```Python hl_lines="29-30 33"
+{!> ../../../docs_src/dependency_testing/tutorial001_an.py!}
+```
+
+////
+
+//// tab | Python 3.10+ non-Annotated
+
+/// tip | "Dica"
+
+Prefira utilizar a versão `Annotated` se possível.
+
+///
+
+```Python hl_lines="24-25 28"
+{!> ../../../docs_src/dependency_testing/tutorial001_py310.py!}
+```
+
+////
+
+//// tab | Python 3.8+ non-Annotated
+
+/// tip | "Dica"
+
+Prefira utilizar a versão `Annotated` se possível.
+
+///
+
+```Python hl_lines="28-29 32"
+{!> ../../../docs_src/dependency_testing/tutorial001.py!}
+```
+
+////
+
+/// tip | "Dica"
+
+Você pode definir uma sobreposição de dependência para uma dependência que é utilizada em qualquer lugar da sua aplicação **FastAPI**.
+
+A dependência original pode estar sendo utilizada em uma *função de operação de rota*, um *docorador de operação de rota* (quando você não utiliza o valor retornado), uma chamada ao `.include_router()`, etc.
+
+O FastAPI ainda poderá sobrescrevê-lo.
+
+///
+
+E então você pode redefinir as suas sobreposições (removê-las) definindo o `app.dependency_overrides` como um `dict` vazio:
+
+```Python
+app.dependency_overrides = {}
+```
+
+/// tip | "Dica"
+
+Se você quer sobrepor uma dependência apenas para alguns testes, você pode definir a sobreposição no início do testes (dentro da função de teste) e reiniciá-la ao final (no final da função de teste).
+
+///
diff --git a/docs/pt/docs/advanced/testing-events.md b/docs/pt/docs/advanced/testing-events.md
new file mode 100644
index 000000000..392fb741c
--- /dev/null
+++ b/docs/pt/docs/advanced/testing-events.md
@@ -0,0 +1,7 @@
+# Testando Eventos: inicialização - encerramento
+
+Quando você precisa que os seus manipuladores de eventos (`startup` e `shutdown`) sejam executados em seus testes, você pode utilizar o `TestClient` usando a instrução `with`:
+
+```Python hl_lines="9-12 20-24"
+{!../../../docs_src/app_testing/tutorial003.py!}
+```
diff --git a/docs/pt/docs/advanced/testing-websockets.md b/docs/pt/docs/advanced/testing-websockets.md
new file mode 100644
index 000000000..f458a05d4
--- /dev/null
+++ b/docs/pt/docs/advanced/testing-websockets.md
@@ -0,0 +1,15 @@
+# Testando WebSockets
+
+Você pode usar o mesmo `TestClient` para testar WebSockets.
+
+Para isso, você utiliza o `TestClient` dentro de uma instrução `with`, conectando com o WebSocket:
+
+```Python hl_lines="27-31"
+{!../../../docs_src/app_testing/tutorial002.py!}
+```
+
+/// note | "Nota"
+
+Para mais detalhes, confira a documentação do Starlette para testar WebSockets.
+
+///
diff --git a/docs/pt/docs/deployment/docker.md b/docs/pt/docs/deployment/docker.md
index fa1a6b9c2..df93bac2c 100644
--- a/docs/pt/docs/deployment/docker.md
+++ b/docs/pt/docs/deployment/docker.md
@@ -205,8 +205,11 @@ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
A opção `--no-cache-dir` diz ao `pip` para não salvar os pacotes baixados localmente, pois isso só aconteceria se `pip` fosse executado novamente para instalar os mesmos pacotes, mas esse não é o caso quando trabalhamos com contêineres.
- !!! note
- `--no-cache-dir` é apenas relacionado ao `pip`, não tem nada a ver com Docker ou contêineres.
+ /// note
+
+ `--no-cache-dir` é apenas relacionado ao `pip`, não tem nada a ver com Docker ou contêineres.
+
+ ///
A opção `--upgrade` diz ao `pip` para atualizar os pacotes se eles já estiverem instalados.
diff --git a/docs/pt/docs/environment-variables.md b/docs/pt/docs/environment-variables.md
new file mode 100644
index 000000000..360d1c496
--- /dev/null
+++ b/docs/pt/docs/environment-variables.md
@@ -0,0 +1,298 @@
+# Variáveis de Ambiente
+
+/// tip | "Dica"
+
+Se você já sabe o que são "variáveis de ambiente" e como usá-las, pode pular esta seção.
+
+///
+
+Uma variável de ambiente (também conhecida como "**env var**") é uma variável que existe **fora** do código Python, no **sistema operacional**, e pode ser lida pelo seu código Python (ou por outros programas também).
+
+Variáveis de ambiente podem ser úteis para lidar com **configurações** do aplicativo, como parte da **instalação** do Python, etc.
+
+## Criar e Usar Variáveis de Ambiente
+
+Você pode **criar** e usar variáveis de ambiente no **shell (terminal)**, sem precisar do Python:
+
+//// tab | Linux, macOS, Windows Bash
+
+email_validator
- para validação de email.
+* email-validator
- para validação de email.
Usados por Starlette:
diff --git a/docs/pt/docs/reference/apirouter.md b/docs/pt/docs/reference/apirouter.md
deleted file mode 100644
index 7568601c9..000000000
--- a/docs/pt/docs/reference/apirouter.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Classe `APIRouter`
-
-Aqui está a informação de referência para a classe `APIRouter`, com todos os seus parâmetros, atributos e métodos.
-
-Você pode importar a classe `APIRouter` diretamente do `fastapi`:
-
-```python
-from fastapi import APIRouter
-```
-
-::: fastapi.APIRouter
- options:
- members:
- - websocket
- - include_router
- - get
- - put
- - post
- - delete
- - options
- - head
- - patch
- - trace
- - on_event
diff --git a/docs/pt/docs/reference/background.md b/docs/pt/docs/reference/background.md
deleted file mode 100644
index bfc15aa76..000000000
--- a/docs/pt/docs/reference/background.md
+++ /dev/null
@@ -1,11 +0,0 @@
-# Tarefas em Segundo Plano - `BackgroundTasks`
-
-Você pode declarar um parâmetro em uma *função de operação de rota* ou em uma função de dependência com o tipo `BackgroundTasks`, e então utilizá-lo para agendar a execução de tarefas em segundo plano após o envio da resposta.
-
-Você pode importá-lo diretamente do `fastapi`:
-
-```python
-from fastapi import BackgroundTasks
-```
-
-::: fastapi.BackgroundTasks
diff --git a/docs/pt/docs/reference/exceptions.md b/docs/pt/docs/reference/exceptions.md
deleted file mode 100644
index d6b5d2613..000000000
--- a/docs/pt/docs/reference/exceptions.md
+++ /dev/null
@@ -1,20 +0,0 @@
-# Exceções - `HTTPException` e `WebSocketException`
-
-Essas são as exceções que você pode lançar para mostrar erros ao cliente.
-
-Quando você lança uma exceção, como aconteceria com o Python normal, o restante da execução é abortado. Dessa forma, você pode lançar essas exceções de qualquer lugar do código para abortar uma solicitação e mostrar o erro ao cliente.
-
-Você pode usar:
-
-* `HTTPException`
-* `WebSocketException`
-
-Essas exceções podem ser importadas diretamente do `fastapi`:
-
-```python
-from fastapi import HTTPException, WebSocketException
-```
-
-::: fastapi.HTTPException
-
-::: fastapi.WebSocketException
diff --git a/docs/pt/docs/reference/index.md b/docs/pt/docs/reference/index.md
deleted file mode 100644
index 533a6a996..000000000
--- a/docs/pt/docs/reference/index.md
+++ /dev/null
@@ -1,6 +0,0 @@
-# Referência - API de Código
-
-Aqui está a referência ou API de código, as classes, funções, parâmetros, atributos e todas as partes do FastAPI que você pode usar em suas aplicações.
-
-Se você quer **aprender FastAPI**, é muito melhor ler o
-[FastAPI Tutorial](https://fastapi.tiangolo.com/tutorial/).
diff --git a/docs/pt/docs/tutorial/bigger-applications.md b/docs/pt/docs/tutorial/bigger-applications.md
new file mode 100644
index 000000000..7137bf865
--- /dev/null
+++ b/docs/pt/docs/tutorial/bigger-applications.md
@@ -0,0 +1,556 @@
+# Aplicações Maiores - Múltiplos Arquivos
+
+Se você está construindo uma aplicação ou uma API web, é raro que você possa colocar tudo em um único arquivo.
+
+**FastAPI** oferece uma ferramenta conveniente para estruturar sua aplicação, mantendo toda a flexibilidade.
+
+/// info | "Informação"
+
+Se você vem do Flask, isso seria o equivalente aos Blueprints do Flask.
+
+///
+
+## Um exemplo de estrutura de arquivos
+
+Digamos que você tenha uma estrutura de arquivos como esta:
+
+```
+.
+├── app
+│ ├── __init__.py
+│ ├── main.py
+│ ├── dependencies.py
+│ └── routers
+│ │ ├── __init__.py
+│ │ ├── items.py
+│ │ └── users.py
+│ └── internal
+│ ├── __init__.py
+│ └── admin.py
+```
+
+/// tip | "Dica"
+
+Existem vários arquivos `__init__.py` presentes em cada diretório ou subdiretório.
+
+Isso permite a importação de código de um arquivo para outro.
+
+Por exemplo, no arquivo `app/main.py`, você poderia ter uma linha como:
+
+```
+from app.routers import items
+```
+
+///
+
+* O diretório `app` contém todo o código da aplicação. Ele possui um arquivo `app/__init__.py` vazio, o que o torna um "pacote Python" (uma coleção de "módulos Python"): `app`.
+* Dentro dele, o arquivo `app/main.py` está localizado em um pacote Python (diretório com `__init__.py`). Portanto, ele é um "módulo" desse pacote: `app.main`.
+* Existem também um arquivo `app/dependencies.py`, assim como o `app/main.py`, ele é um "módulo": `app.dependencies`.
+* Há um subdiretório `app/routers/` com outro arquivo `__init__.py`, então ele é um "subpacote Python": `app.routers`.
+* O arquivo `app/routers/items.py` está dentro de um pacote, `app/routers/`, portanto, é um "submódulo": `app.routers.items`.
+* O mesmo com `app/routers/users.py`, ele é outro submódulo: `app.routers.users`.
+* Há também um subdiretório `app/internal/` com outro arquivo `__init__.py`, então ele é outro "subpacote Python":`app.internal`.
+* E o arquivo `app/internal/admin.py` é outro submódulo: `app.internal.admin`.
+
+ POST
.
+
+///
+
+/// warning | Aviso
+
+Você pode declarar múltiplos parâmetros `File` e `Form` em uma *operação de rota*, mas você não pode declarar campos `Body`que seriam recebidos como JSON junto desses parâmetros, por que a codificação do corpo da requisição será `multipart/form-data` em vez de `application/json`.
+
+Isso não é uma limitação do **FastAPI**, é uma parte do protocolo HTTP.
+
+///
+
+## Arquivo de upload opcional
+
+Você pode definir um arquivo como opcional utilizando as anotações de tipo padrão e definindo o valor padrão como `None`:
+
+//// tab | Python 3.10+
+
+```Python hl_lines="9 17"
+{!> ../../../docs_src/request_files/tutorial001_02_an_py310.py!}
+```
+
+////
+
+//// tab | Python 3.9+
+
+```Python hl_lines="9 17"
+{!> ../../../docs_src/request_files/tutorial001_02_an_py39.py!}
+```
+
+////
+
+//// tab | Python 3.8+
+
+```Python hl_lines="10 18"
+{!> ../../../docs_src/request_files/tutorial001_02_an.py!}
+```
+
+////
+
+//// tab | Python 3.10+ non-Annotated
+
+/// tip | Dica
+
+Utilize a versão com `Annotated`, se possível
+
+///
+
+```Python hl_lines="7 15"
+{!> ../../../docs_src/request_files/tutorial001_02_py310.py!}
+```
+
+////
+
+//// tab | Python 3.8+ non-Annotated
+
+/// tip | Dica
+
+Utilize a versão com `Annotated`, se possível
+
+///
+
+```Python hl_lines="9 17"
+{!> ../../../docs_src/request_files/tutorial001_02.py!}
+```
+
+////
+
+## `UploadFile` com Metadados Adicionais
+
+Você também pode utilizar `File()` com `UploadFile`, por exemplo, para definir metadados adicionais:
+
+//// tab | Python 3.9+
+
+```Python hl_lines="9 15"
+{!> ../../../docs_src/request_files/tutorial001_03_an_py39.py!}
+```
+
+////
+
+//// tab | Python 3.8+
+
+```Python hl_lines="8 14"
+{!> ../../../docs_src/request_files/tutorial001_03_an.py!}
+```
+
+////
+
+//// tab | Python 3.8+ non-Annotated
+
+/// tip | Dica
+
+Utilize a versão com `Annotated` se possível
+
+///
+
+```Python hl_lines="7 13"
+{!> ../../../docs_src/request_files/tutorial001_03.py!}
+```
+
+////
+
+## Envio de Múltiplos Arquivos
+
+É possível enviar múltiplos arquivos ao mesmo tmepo.
+
+Ele ficam associados ao mesmo "campo do formulário" enviado com "form data".
+
+Para usar isso, declare uma lista de `bytes` ou `UploadFile`:
+
+//// tab | Python 3.9+
+
+```Python hl_lines="10 15"
+{!> ../../../docs_src/request_files/tutorial002_an_py39.py!}
+```
+
+////
+
+//// tab | Python 3.8+
+
+```Python hl_lines="11 16"
+{!> ../../../docs_src/request_files/tutorial002_an.py!}
+```
+
+////
+
+//// tab | Python 3.9+ non-Annotated
+
+/// tip | Dica
+
+Utilize a versão com `Annotated` se possível
+
+///
+
+```Python hl_lines="8 13"
+{!> ../../../docs_src/request_files/tutorial002_py39.py!}
+```
+
+////
+
+//// tab | Python 3.8+ non-Annotated
+
+/// tip | Dica
+
+Utilize a versão com `Annotated` se possível
+
+///
+
+```Python hl_lines="10 15"
+{!> ../../../docs_src/request_files/tutorial002.py!}
+```
+
+////
+
+Você irá receber, como delcarado uma lista (`list`) de `bytes` ou `UploadFile`s,
+
+/// note | Detalhes Técnicos
+
+Você também poderia utilizar `from starlette.responses import HTMLResponse`.
+
+O **FastAPI** fornece as mesmas `starlette.responses` como `fastapi.responses` apenas como um facilitador para você, desenvolvedor. Mas a maior parte das respostas vem diretamente do Starlette.
+
+///
+
+### Enviando Múltiplos Arquivos com Metadados Adicionais
+
+E da mesma forma que antes, você pode utilizar `File()` para definir parâmetros adicionais, até mesmo para `UploadFile`:
+
+//// tab | Python 3.9+
+
+```Python hl_lines="11 18-20"
+{!> ../../../docs_src/request_files/tutorial003_an_py39.py!}
+```
+
+////
+
+//// tab | Python 3.8+
+
+```Python hl_lines="12 19-21"
+{!> ../../../docs_src/request_files/tutorial003_an.py!}
+```
+
+////
+
+//// tab | Python 3.9+ non-Annotated
+
+/// tip | Dica
+
+Utilize a versão com `Annotated` se possível.
+
+///
+
+```Python hl_lines="9 16"
+{!> ../../../docs_src/request_files/tutorial003_py39.py!}
+```
+
+////
+
+//// tab | Python 3.8+ non-Annotated
+
+/// tip | Dica
+
+Utilize a versão com `Annotated` se possível.
+
+///
+
+```Python hl_lines="11 18"
+{!> ../../../docs_src/request_files/tutorial003.py!}
+```
+
+////
+
+## Recapitulando
+
+Use `File`, `bytes` e `UploadFile` para declarar arquivos que serão enviados na requisição, enviados como dados do formulário.
diff --git a/docs/pt/docs/tutorial/testing.md b/docs/pt/docs/tutorial/testing.md
new file mode 100644
index 000000000..f734a7d9a
--- /dev/null
+++ b/docs/pt/docs/tutorial/testing.md
@@ -0,0 +1,249 @@
+# Testando
+
+Graças ao Starlette, testar aplicativos **FastAPI** é fácil e agradável.
+
+Ele é baseado no HTTPX, que por sua vez é projetado com base em Requests, por isso é muito familiar e intuitivo.
+
+Com ele, você pode usar o pytest diretamente com **FastAPI**.
+
+## Usando `TestClient`
+
+/// info | "Informação"
+
+Para usar o `TestClient`, primeiro instale o `httpx`.
+
+Certifique-se de criar um [ambiente virtual](../virtual-environments.md){.internal-link target=_blank}, ativá-lo e instalá-lo, por exemplo:
+
+```console
+$ pip install httpx
+```
+
+///
+
+Importe `TestClient`.
+
+Crie um `TestClient` passando seu aplicativo **FastAPI** para ele.
+
+Crie funções com um nome que comece com `test_` (essa é a convenção padrão do `pytest`).
+
+Use o objeto `TestClient` da mesma forma que você faz com `httpx`.
+
+Escreva instruções `assert` simples com as expressões Python padrão que você precisa verificar (novamente, `pytest` padrão).
+
+```Python hl_lines="2 12 15-18"
+{!../../../docs_src/app_testing/tutorial001.py!}
+```
+
+/// tip | "Dica"
+
+Observe que as funções de teste são `def` normais, não `async def`.
+
+E as chamadas para o cliente também são chamadas normais, não usando `await`.
+
+Isso permite que você use `pytest` diretamente sem complicações.
+
+///
+
+/// note | "Detalhes técnicos"
+
+Você também pode usar `from starlette.testclient import TestClient`.
+
+**FastAPI** fornece o mesmo `starlette.testclient` que `fastapi.testclient` apenas como uma conveniência para você, o desenvolvedor. Mas ele vem diretamente da Starlette.
+
+///
+
+/// tip | "Dica"
+
+Se você quiser chamar funções `async` em seus testes além de enviar solicitações ao seu aplicativo FastAPI (por exemplo, funções de banco de dados assíncronas), dê uma olhada em [Testes assíncronos](../advanced/async-tests.md){.internal-link target=_blank} no tutorial avançado.
+
+///
+
+## Separando testes
+
+Em uma aplicação real, você provavelmente teria seus testes em um arquivo diferente.
+
+E seu aplicativo **FastAPI** também pode ser composto de vários arquivos/módulos, etc.
+
+### Arquivo do aplicativo **FastAPI**
+
+Digamos que você tenha uma estrutura de arquivo conforme descrito em [Aplicativos maiores](bigger-applications.md){.internal-link target=_blank}:
+
+```
+.
+├── app
+│ ├── __init__.py
+│ └── main.py
+```
+
+No arquivo `main.py` você tem seu aplicativo **FastAPI**:
+
+
+```Python
+{!../../../docs_src/app_testing/main.py!}
+```
+
+### Arquivo de teste
+
+Então você poderia ter um arquivo `test_main.py` com seus testes. Ele poderia estar no mesmo pacote Python (o mesmo diretório com um arquivo `__init__.py`):
+
+``` hl_lines="5"
+.
+├── app
+│ ├── __init__.py
+│ ├── main.py
+│ └── test_main.py
+```
+
+Como esse arquivo está no mesmo pacote, você pode usar importações relativas para importar o objeto `app` do módulo `main` (`main.py`):
+
+```Python hl_lines="3"
+{!../../../docs_src/app_testing/test_main.py!}
+```
+
+...e ter o código para os testes como antes.
+
+## Testando: exemplo estendido
+
+Agora vamos estender este exemplo e adicionar mais detalhes para ver como testar diferentes partes.
+
+### Arquivo de aplicativo **FastAPI** estendido
+
+Vamos continuar com a mesma estrutura de arquivo de antes:
+
+```
+.
+├── app
+│ ├── __init__.py
+│ ├── main.py
+│ └── test_main.py
+```
+
+Digamos que agora o arquivo `main.py` com seu aplicativo **FastAPI** tenha algumas outras **operações de rotas**.
+
+Ele tem uma operação `GET` que pode retornar um erro.
+
+Ele tem uma operação `POST` que pode retornar vários erros.
+
+Ambas as *operações de rotas* requerem um cabeçalho `X-Token`.
+
+//// tab | Python 3.10+
+
+```Python
+{!> ../../../docs_src/app_testing/app_b_an_py310/main.py!}
+```
+
+////
+
+//// tab | Python 3.9+
+
+```Python
+{!> ../../../docs_src/app_testing/app_b_an_py39/main.py!}
+```
+
+////
+
+//// tab | Python 3.8+
+
+```Python
+{!> ../../../docs_src/app_testing/app_b_an/main.py!}
+```
+
+////
+
+//// tab | Python 3.10+ non-Annotated
+
+/// tip | "Dica"
+
+Prefira usar a versão `Annotated` se possível.
+
+///
+
+```Python
+{!> ../../../docs_src/app_testing/app_b_py310/main.py!}
+```
+
+////
+
+//// tab | Python 3.8+ non-Annotated
+
+/// tip | "Dica"
+
+Prefira usar a versão `Annotated` se possível.
+
+///
+
+```Python
+{!> ../../../docs_src/app_testing/app_b/main.py!}
+```
+
+////
+
+### Arquivo de teste estendido
+
+Você pode então atualizar `test_main.py` com os testes estendidos:
+
+```Python
+{!> ../../../docs_src/app_testing/app_b/test_main.py!}
+```
+
+Sempre que você precisar que o cliente passe informações na requisição e não souber como, você pode pesquisar (no Google) como fazer isso no `httpx`, ou até mesmo como fazer isso com `requests`, já que o design do HTTPX é baseado no design do Requests.
+
+Depois é só fazer o mesmo nos seus testes.
+
+Por exemplo:
+
+* Para passar um parâmetro *path* ou *query*, adicione-o à própria URL.
+* Para passar um corpo JSON, passe um objeto Python (por exemplo, um `dict`) para o parâmetro `json`.
+* Se você precisar enviar *Dados de Formulário* em vez de JSON, use o parâmetro `data`.
+* Para passar *headers*, use um `dict` no parâmetro `headers`.
+* Para *cookies*, um `dict` no parâmetro `cookies`.
+
+Para mais informações sobre como passar dados para o backend (usando `httpx` ou `TestClient`), consulte a documentação do HTTPX.
+
+/// info | "Informação"
+
+Observe que o `TestClient` recebe dados que podem ser convertidos para JSON, não para modelos Pydantic.
+
+Se você tiver um modelo Pydantic em seu teste e quiser enviar seus dados para o aplicativo durante o teste, poderá usar o `jsonable_encoder` descrito em [Codificador compatível com JSON](encoder.md){.internal-link target=_blank}.
+
+///
+
+## Execute-o
+
+Depois disso, você só precisa instalar o `pytest`.
+
+Certifique-se de criar um [ambiente virtual](../virtual-environments.md){.internal-link target=_blank}, ativá-lo e instalá-lo, por exemplo:
+
+email_validator
- для проверки электронной почты.
+* email-validator
- для проверки электронной почты.
Используется Starlette:
diff --git a/docs/ru/docs/tutorial/response-model.md b/docs/ru/docs/tutorial/response-model.md
index 38d185b98..f8c910fe9 100644
--- a/docs/ru/docs/tutorial/response-model.md
+++ b/docs/ru/docs/tutorial/response-model.md
@@ -131,7 +131,7 @@ FastAPI будет использовать значение `response_model` д
/// info | "Информация"
-Чтобы использовать `EmailStr`, прежде необходимо установить `email_validator`.
+Чтобы использовать `EmailStr`, прежде необходимо установить `email-validator`.
Используйте `pip install email-validator`
или `pip install pydantic[email]`.
diff --git a/docs/tr/docs/external-links.md b/docs/tr/docs/external-links.md
deleted file mode 100644
index 6e8af4025..000000000
--- a/docs/tr/docs/external-links.md
+++ /dev/null
@@ -1,36 +0,0 @@
-# Harici Bağlantılar ve Makaleler
-
-**FastAPI** sürekli büyüyen harika bir topluluğa sahiptir.
-
-**FastAPI** ile alakalı birçok yazı, makale, araç ve proje bulunmaktadır.
-
-Bunlardan bazılarının tamamlanmamış bir listesi aşağıda bulunmaktadır.
-
-/// tip | "İpucu"
-
-Eğer **FastAPI** ile alakalı henüz burada listelenmemiş bir makale, proje, araç veya başka bir şeyiniz varsa, bunu eklediğiniz bir Pull Request oluşturabilirsiniz.
-
-///
-
-{% for section_name, section_content in external_links.items() %}
-
-## {{ section_name }}
-
-{% for lang_name, lang_content in section_content.items() %}
-
-### {{ lang_name }}
-
-{% for item in lang_content %}
-
-* {{ item.title }} by {{ item.author }}.
-
-{% endfor %}
-{% endfor %}
-{% endfor %}
-
-## Projeler
-
-`fastapi` konulu en son GitHub projeleri:
-
-email_validator
- email doğrulaması için.
+* email-validator
- email doğrulaması için.
* pydantic-settings
- ayar yönetimi için.
* pydantic-extra-types
- Pydantic ile birlikte kullanılabilecek ek tipler için.
diff --git a/docs/tr/docs/newsletter.md b/docs/tr/docs/newsletter.md
deleted file mode 100644
index 22ca1b1e2..000000000
--- a/docs/tr/docs/newsletter.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# FastAPI ve Arkadaşları Bülteni
-
-
-
-
diff --git a/docs/uk/docs/fastapi-people.md b/docs/uk/docs/fastapi-people.md
deleted file mode 100644
index c6a6451d8..000000000
--- a/docs/uk/docs/fastapi-people.md
+++ /dev/null
@@ -1,183 +0,0 @@
----
-hide:
- - navigation
----
-
-# Люди FastAPI
-
-FastAPI має дивовижну спільноту, яка вітає людей різного походження.
-
-## Творець – Супроводжувач
-
-Привіт! 👋
-
-Це я:
-
-{% if people %}
-email_validator
- для валідації електронної пошти.
+* email-validator
- для валідації електронної пошти.
* pydantic-settings
- для управління налаштуваннями.
* pydantic-extra-types
- для додаткових типів, що можуть бути використані з Pydantic.
diff --git a/docs/ur/docs/benchmarks.md b/docs/ur/docs/benchmarks.md
index 9fc793e6f..8d583de2f 100644
--- a/docs/ur/docs/benchmarks.md
+++ b/docs/ur/docs/benchmarks.md
@@ -1,5 +1,4 @@
# بینچ مارکس
-
انڈیپنڈنٹ ٹیک امپور بینچ مارک **FASTAPI** Uvicorn کے تحت چلنے والی ایپلی کیشنز کو ایک تیز رفتار Python فریم ورک میں سے ایک ، صرف Starlette اور Uvicorn کے نیچے ( FASTAPI کے ذریعہ اندرونی طور پر استعمال کیا جاتا ہے ) (*)
لیکن جب بینچ مارک اور موازنہ کی جانچ پڑتال کرتے ہو تو آپ کو مندرجہ ذیل بات ذہن میں رکھنی چاہئے.
@@ -14,39 +13,39 @@
درجہ بندی کی طرح ہے:
-email_validator
- cho email validation.
+* email-validator
- cho email validation.
Sử dụng Starlette:
diff --git a/docs/yo/docs/index.md b/docs/yo/docs/index.md
index eb20adbb5..ee7f56220 100644
--- a/docs/yo/docs/index.md
+++ b/docs/yo/docs/index.md
@@ -449,7 +449,7 @@ Láti ní òye síi nípa rẹ̀, wo abala àwọn email_validator
- fún ifọwọsi ímeèlì.
+* email-validator
- fún ifọwọsi ímeèlì.
* pydantic-settings
- fún ètò ìsàkóso.
* pydantic-extra-types
- fún àfikún oríṣi láti lọ pẹ̀lú Pydantic.
diff --git a/docs/zh-hant/docs/fastapi-people.md b/docs/zh-hant/docs/fastapi-people.md
deleted file mode 100644
index 99277b419..000000000
--- a/docs/zh-hant/docs/fastapi-people.md
+++ /dev/null
@@ -1,239 +0,0 @@
----
-hide:
- - navigation
----
-
-# FastAPI 社群
-
-FastAPI 有一個非常棒的社群,歡迎來自不同背景的朋友參與。
-
-## 作者
-
-嘿! 👋
-
-關於我:
-
-{% if people %}
-email_validator
- 用於電子郵件驗證。
+- email-validator
- 用於電子郵件驗證。
- pydantic-settings
- 用於設定管理。
- pydantic-extra-types
- 用於與 Pydantic 一起使用的額外型別。
diff --git a/docs/zh/docs/advanced/middleware.md b/docs/zh/docs/advanced/middleware.md
index 764784ce3..926082b94 100644
--- a/docs/zh/docs/advanced/middleware.md
+++ b/docs/zh/docs/advanced/middleware.md
@@ -95,7 +95,6 @@ app.add_middleware(UnicornMiddleware, some_config="rainbow")
例如:
-* Sentry
* Uvicorn 的 `ProxyHeadersMiddleware`
* MessagePack
diff --git a/docs/zh/docs/deployment/docker.md b/docs/zh/docs/deployment/docker.md
index e5f66dba1..f120ebfb8 100644
--- a/docs/zh/docs/deployment/docker.md
+++ b/docs/zh/docs/deployment/docker.md
@@ -213,8 +213,11 @@ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
`--no-cache-dir` 选项告诉 `pip` 不要在本地保存下载的包,因为只有当 `pip` 再次运行以安装相同的包时才会这样,但在与容器一起工作时情况并非如此。
- !!! note "笔记"
- `--no-cache-dir` 仅与 `pip` 相关,与 Docker 或容器无关。
+ /// note | 笔记
+
+ `--no-cache-dir` 仅与 `pip` 相关,与 Docker 或容器无关。
+
+ ///
`--upgrade` 选项告诉 `pip` 升级软件包(如果已经安装)。
diff --git a/docs/zh/docs/deployment/https.md b/docs/zh/docs/deployment/https.md
index e5d66482a..9c963d587 100644
--- a/docs/zh/docs/deployment/https.md
+++ b/docs/zh/docs/deployment/https.md
@@ -4,8 +4,11 @@
但实际情况比这复杂得多。
-!!!提示
- 如果你很赶时间或不在乎,请继续阅读下一部分,下一部分会提供一个step-by-step的教程,告诉你怎么使用不同技术来把一切都配置好。
+/// note | 提示
+
+如果你很赶时间或不在乎,请继续阅读下一部分,下一部分会提供一个step-by-step的教程,告诉你怎么使用不同技术来把一切都配置好。
+
+///
要从用户的视角**了解 HTTPS 的基础知识**,请查看 https://howhttps.works/。
diff --git a/docs/zh/docs/fastapi-people.md b/docs/zh/docs/fastapi-people.md
deleted file mode 100644
index 30a75240c..000000000
--- a/docs/zh/docs/fastapi-people.md
+++ /dev/null
@@ -1,239 +0,0 @@
----
-hide:
- - navigation
----
-
-# FastAPI 社区
-
-FastAPI 有一个非常棒的社区,它欢迎来自各个领域和背景的朋友。
-
-## 创建者 & 维护者
-
-嘿! 👋
-
-这就是我:
-
-{% if people %}
-email_validator
- 用于 email 校验。
+* email-validator
- 用于 email 校验。
用于 Starlette:
diff --git a/docs/zh/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/zh/docs/tutorial/dependencies/dependencies-with-yield.md
index beca95d45..6058f7878 100644
--- a/docs/zh/docs/tutorial/dependencies/dependencies-with-yield.md
+++ b/docs/zh/docs/tutorial/dependencies/dependencies-with-yield.md
@@ -1,12 +1,12 @@
# 使用yield的依赖项
-FastAPI支持在完成后执行一些额外步骤的依赖项.
+FastAPI支持在完成后执行一些额外步骤的依赖项.
-为此,请使用 `yield` 而不是 `return`,然后再编写额外的步骤(代码)。
+为此,你需要使用 `yield` 而不是 `return`,然后再编写这些额外的步骤(代码)。
-/// tip | "提示"
+/// tip | 提示
-确保只使用一次 `yield` 。
+确保在每个依赖中只使用一次 `yield`。
///
@@ -25,7 +25,7 @@ FastAPI支持在完成后执行一些> tasks: Send background tasks
end
opt Raise other exception
- tasks -->> dep: Raise other exception
- end
- Note over dep: After yield
- opt Handle other exception
- dep -->> dep: Handle exception, can't change response. E.g. close DB session.
+ tasks -->> tasks: Handle exceptions in the background task code
end
```
-/// info
+/// info | 说明
-只会向客户端发送**一次响应**,可能是一个错误响应之一,也可能是来自*路径操作*的响应。
+只会向客户端发送 **一次响应** ,可能是一个错误响应,也可能是来自 *路由函数* 的响应。
在发送了其中一个响应之后,就无法再发送其他响应了。
///
-/// tip
+/// tip | 提示
+
+这个时序图展示了 `HTTPException`,除此之外你也可以抛出任何你在使用 `yield` 的依赖项中或者[自定义异常处理器](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}中捕获的异常。
+
+如果你引发任何异常,它将传递给使用 `yield` 的依赖项,包括 `HTTPException`。在大多数情况下你应当从使用 `yield` 的依赖项中重新抛出捕获的异常或者一个新的异常来确保它会被正确的处理。
+
+///
+
+## 包含 `yield`, `HTTPException`, `except` 的依赖项和后台任务
+
+/// warning | 注意
+
+你大概率不需要了解这些技术细节,可以跳过这一章节继续阅读后续的内容。
+
+如果你使用的FastAPI的版本早于0.106.0,并且在使用后台任务中使用了包含 `yield` 的依赖项中的资源,那么这些细节会对你有一些用处。
+
+///
+
+### 包含 `yield` 和 `except` 的依赖项的技术细节
+
+在FastAPI 0.110.0版本之前,如果使用了一个包含 `yield` 的依赖项,你在依赖项中使用 `except` 捕获了一个异常,但是你没有再次抛出该异常,这个异常会被自动抛出/转发到异常处理器或者内部服务错误处理器。
+
+### 后台任务和使用 `yield` 的依赖项的技术细节
+
+在FastAPI 0.106.0版本之前,在 `yield` 后面抛出异常是不可行的,因为 `yield` 之后的退出代码是在响应被发送之后再执行,这个时候异常处理器已经执行过了。
-这个图表展示了`HTTPException`,但你也可以引发任何其他你创建了[自定义异常处理程序](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}的异常。
+这样设计的目的主要是为了允许在后台任务中使用被依赖项`yield`的对象,因为退出代码会在后台任务结束后再执行。
-如果你引发任何异常,它将传递给带有`yield`的依赖,包括`HTTPException`,然后**再次**传递给异常处理程序。如果没有针对该异常的异常处理程序,那么它将被默认的内部`ServerErrorMiddleware`处理,返回500 HTTP状态码,告知客户端服务器发生了错误。
+然而这也意味着在等待响应通过网络传输的同时,非必要的持有一个 `yield` 依赖项中的资源(例如数据库连接),这一行为在FastAPI 0.106.0被改变了。
+
+/// tip | 提示
+
+除此之外,后台任务通常是一组独立的逻辑,应该被单独处理,并且使用它自己的资源(例如它自己的数据库连接)。
+
+这样也会让你的代码更加简洁。
///
+如果你之前依赖于这一行为,那么现在你应该在后台任务中创建并使用它自己的资源,不要在内部使用属于 `yield` 依赖项的资源。
+
+例如,你应该在后台任务中创建一个新的数据库会话用于查询数据,而不是使用相同的会话。你应该将对象的ID作为参数传递给后台任务函数,然后在该函数中重新获取该对象,而不是直接将数据库对象作为参数。
+
## 上下文管理器
-### 什么是“上下文管理器”
+### 什么是"上下文管理器"
-“上下文管理器”是您可以在`with`语句中使用的任何Python对象。
+"上下文管理器"是你可以在 `with` 语句中使用的任何Python对象。
-例如,您可以使用`with`读取文件:
+例如,你可以使用`with`读取文件:
```Python
with open("./somefile.txt") as f:
@@ -254,38 +379,38 @@ with open("./somefile.txt") as f:
print(contents)
```
-在底层,`open("./somefile.txt")`创建了一个被称为“上下文管理器”的对象。
+在底层,`open("./somefile.txt")`创建了一个被称为"上下文管理器"的对象。
-当`with`块结束时,它会确保关闭文件,即使发生了异常也是如此。
+当 `with` 代码块结束时,它会确保关闭文件,即使发生了异常也是如此。
-当你使用`yield`创建一个依赖项时,**FastAPI**会在内部将其转换为上下文管理器,并与其他相关工具结合使用。
+当你使用 `yield` 创建一个依赖项时,**FastAPI** 会在内部将其转换为上下文管理器,并与其他相关工具结合使用。
-### 在依赖项中使用带有`yield`的上下文管理器
+### 在使用 `yield` 的依赖项中使用上下文管理器
-/// warning
+/// warning | 注意
-这是一个更为“高级”的想法。
+这是一个更为"高级"的想法。
-如果您刚开始使用**FastAPI**,您可能暂时可以跳过它。
+如果你刚开始使用 **FastAPI** ,你可以暂时可以跳过它。
///
在Python中,你可以通过创建一个带有`__enter__()`和`__exit__()`方法的类来创建上下文管理器。
-你也可以在**FastAPI**的依赖项中使用带有`yield`的`with`或`async with`语句,通过在依赖函数内部使用它们。
+你也可以在 **FastAPI** 的 `yield` 依赖项中通过 `with` 或者 `async with` 语句来使用它们:
```Python hl_lines="1-9 13"
{!../../../docs_src/dependencies/tutorial010.py!}
```
-/// tip
+/// tip | 提示
另一种创建上下文管理器的方法是:
* `@contextlib.contextmanager`或者
* `@contextlib.asynccontextmanager`
-使用上下文管理器装饰一个只有单个`yield`的函数。这就是**FastAPI**在内部用于带有`yield`的依赖项的方式。
+使用它们装饰一个只有单个 `yield` 的函数。这就是 **FastAPI** 内部对于 `yield` 依赖项的处理方式。
但是你不需要为FastAPI的依赖项使用这些装饰器(而且也不应该)。FastAPI会在内部为你处理这些。
diff --git a/docs_src/advanced_middleware/tutorial003.py b/docs_src/advanced_middleware/tutorial003.py
index b99e3edd1..e2c87e67d 100644
--- a/docs_src/advanced_middleware/tutorial003.py
+++ b/docs_src/advanced_middleware/tutorial003.py
@@ -3,7 +3,7 @@ from fastapi.middleware.gzip import GZipMiddleware
app = FastAPI()
-app.add_middleware(GZipMiddleware, minimum_size=1000)
+app.add_middleware(GZipMiddleware, minimum_size=1000, compresslevel=5)
@app.get("/")
diff --git a/docs_src/app_testing/app_b_an/main.py b/docs_src/app_testing/app_b_an/main.py
index c63134fc9..c66278fdd 100644
--- a/docs_src/app_testing/app_b_an/main.py
+++ b/docs_src/app_testing/app_b_an/main.py
@@ -34,6 +34,6 @@ async def create_item(item: Item, x_token: Annotated[str, Header()]):
if x_token != fake_secret_token:
raise HTTPException(status_code=400, detail="Invalid X-Token header")
if item.id in fake_db:
- raise HTTPException(status_code=400, detail="Item already exists")
+ raise HTTPException(status_code=409, detail="Item already exists")
fake_db[item.id] = item
return item
diff --git a/docs_src/app_testing/app_b_an/test_main.py b/docs_src/app_testing/app_b_an/test_main.py
index e2eda449d..4e1c51ecc 100644
--- a/docs_src/app_testing/app_b_an/test_main.py
+++ b/docs_src/app_testing/app_b_an/test_main.py
@@ -61,5 +61,5 @@ def test_create_existing_item():
"description": "There goes my stealer",
},
)
- assert response.status_code == 400
+ assert response.status_code == 409
assert response.json() == {"detail": "Item already exists"}
diff --git a/docs_src/app_testing/app_b_an_py310/main.py b/docs_src/app_testing/app_b_an_py310/main.py
index 48c27a0b8..c5952be0b 100644
--- a/docs_src/app_testing/app_b_an_py310/main.py
+++ b/docs_src/app_testing/app_b_an_py310/main.py
@@ -33,6 +33,6 @@ async def create_item(item: Item, x_token: Annotated[str, Header()]):
if x_token != fake_secret_token:
raise HTTPException(status_code=400, detail="Invalid X-Token header")
if item.id in fake_db:
- raise HTTPException(status_code=400, detail="Item already exists")
+ raise HTTPException(status_code=409, detail="Item already exists")
fake_db[item.id] = item
return item
diff --git a/docs_src/app_testing/app_b_an_py310/test_main.py b/docs_src/app_testing/app_b_an_py310/test_main.py
index e2eda449d..4e1c51ecc 100644
--- a/docs_src/app_testing/app_b_an_py310/test_main.py
+++ b/docs_src/app_testing/app_b_an_py310/test_main.py
@@ -61,5 +61,5 @@ def test_create_existing_item():
"description": "There goes my stealer",
},
)
- assert response.status_code == 400
+ assert response.status_code == 409
assert response.json() == {"detail": "Item already exists"}
diff --git a/docs_src/app_testing/app_b_an_py39/main.py b/docs_src/app_testing/app_b_an_py39/main.py
index 935a510b7..142e23a26 100644
--- a/docs_src/app_testing/app_b_an_py39/main.py
+++ b/docs_src/app_testing/app_b_an_py39/main.py
@@ -33,6 +33,6 @@ async def create_item(item: Item, x_token: Annotated[str, Header()]):
if x_token != fake_secret_token:
raise HTTPException(status_code=400, detail="Invalid X-Token header")
if item.id in fake_db:
- raise HTTPException(status_code=400, detail="Item already exists")
+ raise HTTPException(status_code=409, detail="Item already exists")
fake_db[item.id] = item
return item
diff --git a/docs_src/app_testing/app_b_an_py39/test_main.py b/docs_src/app_testing/app_b_an_py39/test_main.py
index e2eda449d..4e1c51ecc 100644
--- a/docs_src/app_testing/app_b_an_py39/test_main.py
+++ b/docs_src/app_testing/app_b_an_py39/test_main.py
@@ -61,5 +61,5 @@ def test_create_existing_item():
"description": "There goes my stealer",
},
)
- assert response.status_code == 400
+ assert response.status_code == 409
assert response.json() == {"detail": "Item already exists"}
diff --git a/docs_src/async_tests/test_main.py b/docs_src/async_tests/test_main.py
index 9f1527d5f..a57a31f7d 100644
--- a/docs_src/async_tests/test_main.py
+++ b/docs_src/async_tests/test_main.py
@@ -1,12 +1,14 @@
import pytest
-from httpx import AsyncClient
+from httpx import ASGITransport, AsyncClient
from .main import app
@pytest.mark.anyio
async def test_root():
- async with AsyncClient(app=app, base_url="http://test") as ac:
+ async with AsyncClient(
+ transport=ASGITransport(app=app), base_url="http://test"
+ ) as ac:
response = await ac.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Tomato"}
diff --git a/docs_src/middleware/tutorial001.py b/docs_src/middleware/tutorial001.py
index 6bab3410a..e65a7dade 100644
--- a/docs_src/middleware/tutorial001.py
+++ b/docs_src/middleware/tutorial001.py
@@ -7,8 +7,8 @@ app = FastAPI()
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
- start_time = time.time()
+ start_time = time.perf_counter()
response = await call_next(request)
- process_time = time.time() - start_time
+ process_time = time.perf_counter() - start_time
response.headers["X-Process-Time"] = str(process_time)
return response
diff --git a/docs_src/path_params_numeric_validations/tutorial006.py b/docs_src/path_params_numeric_validations/tutorial006.py
index 0ea32694a..f07629aa0 100644
--- a/docs_src/path_params_numeric_validations/tutorial006.py
+++ b/docs_src/path_params_numeric_validations/tutorial006.py
@@ -13,4 +13,6 @@ async def read_items(
results = {"item_id": item_id}
if q:
results.update({"q": q})
+ if size:
+ results.update({"size": size})
return results
diff --git a/docs_src/path_params_numeric_validations/tutorial006_an.py b/docs_src/path_params_numeric_validations/tutorial006_an.py
index 22a143623..ac4732573 100644
--- a/docs_src/path_params_numeric_validations/tutorial006_an.py
+++ b/docs_src/path_params_numeric_validations/tutorial006_an.py
@@ -14,4 +14,6 @@ async def read_items(
results = {"item_id": item_id}
if q:
results.update({"q": q})
+ if size:
+ results.update({"size": size})
return results
diff --git a/docs_src/path_params_numeric_validations/tutorial006_an_py39.py b/docs_src/path_params_numeric_validations/tutorial006_an_py39.py
index 804751893..426ec3776 100644
--- a/docs_src/path_params_numeric_validations/tutorial006_an_py39.py
+++ b/docs_src/path_params_numeric_validations/tutorial006_an_py39.py
@@ -15,4 +15,6 @@ async def read_items(
results = {"item_id": item_id}
if q:
results.update({"q": q})
+ if size:
+ results.update({"size": size})
return results
diff --git a/docs_src/request_form_models/tutorial001.py b/docs_src/request_form_models/tutorial001.py
new file mode 100644
index 000000000..98feff0b9
--- /dev/null
+++ b/docs_src/request_form_models/tutorial001.py
@@ -0,0 +1,14 @@
+from fastapi import FastAPI, Form
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class FormData(BaseModel):
+ username: str
+ password: str
+
+
+@app.post("/login/")
+async def login(data: FormData = Form()):
+ return data
diff --git a/docs_src/request_form_models/tutorial001_an.py b/docs_src/request_form_models/tutorial001_an.py
new file mode 100644
index 000000000..30483d445
--- /dev/null
+++ b/docs_src/request_form_models/tutorial001_an.py
@@ -0,0 +1,15 @@
+from fastapi import FastAPI, Form
+from pydantic import BaseModel
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+class FormData(BaseModel):
+ username: str
+ password: str
+
+
+@app.post("/login/")
+async def login(data: Annotated[FormData, Form()]):
+ return data
diff --git a/docs_src/request_form_models/tutorial001_an_py39.py b/docs_src/request_form_models/tutorial001_an_py39.py
new file mode 100644
index 000000000..7cc81aae9
--- /dev/null
+++ b/docs_src/request_form_models/tutorial001_an_py39.py
@@ -0,0 +1,16 @@
+from typing import Annotated
+
+from fastapi import FastAPI, Form
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class FormData(BaseModel):
+ username: str
+ password: str
+
+
+@app.post("/login/")
+async def login(data: Annotated[FormData, Form()]):
+ return data
diff --git a/docs_src/request_form_models/tutorial002.py b/docs_src/request_form_models/tutorial002.py
new file mode 100644
index 000000000..59b329e8d
--- /dev/null
+++ b/docs_src/request_form_models/tutorial002.py
@@ -0,0 +1,15 @@
+from fastapi import FastAPI, Form
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class FormData(BaseModel):
+ username: str
+ password: str
+ model_config = {"extra": "forbid"}
+
+
+@app.post("/login/")
+async def login(data: FormData = Form()):
+ return data
diff --git a/docs_src/request_form_models/tutorial002_an.py b/docs_src/request_form_models/tutorial002_an.py
new file mode 100644
index 000000000..bcb022795
--- /dev/null
+++ b/docs_src/request_form_models/tutorial002_an.py
@@ -0,0 +1,16 @@
+from fastapi import FastAPI, Form
+from pydantic import BaseModel
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+class FormData(BaseModel):
+ username: str
+ password: str
+ model_config = {"extra": "forbid"}
+
+
+@app.post("/login/")
+async def login(data: Annotated[FormData, Form()]):
+ return data
diff --git a/docs_src/request_form_models/tutorial002_an_py39.py b/docs_src/request_form_models/tutorial002_an_py39.py
new file mode 100644
index 000000000..3004e0852
--- /dev/null
+++ b/docs_src/request_form_models/tutorial002_an_py39.py
@@ -0,0 +1,17 @@
+from typing import Annotated
+
+from fastapi import FastAPI, Form
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class FormData(BaseModel):
+ username: str
+ password: str
+ model_config = {"extra": "forbid"}
+
+
+@app.post("/login/")
+async def login(data: Annotated[FormData, Form()]):
+ return data
diff --git a/docs_src/request_form_models/tutorial002_pv1.py b/docs_src/request_form_models/tutorial002_pv1.py
new file mode 100644
index 000000000..d5f7db2a6
--- /dev/null
+++ b/docs_src/request_form_models/tutorial002_pv1.py
@@ -0,0 +1,17 @@
+from fastapi import FastAPI, Form
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class FormData(BaseModel):
+ username: str
+ password: str
+
+ class Config:
+ extra = "forbid"
+
+
+@app.post("/login/")
+async def login(data: FormData = Form()):
+ return data
diff --git a/docs_src/request_form_models/tutorial002_pv1_an.py b/docs_src/request_form_models/tutorial002_pv1_an.py
new file mode 100644
index 000000000..fe9dbc344
--- /dev/null
+++ b/docs_src/request_form_models/tutorial002_pv1_an.py
@@ -0,0 +1,18 @@
+from fastapi import FastAPI, Form
+from pydantic import BaseModel
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+class FormData(BaseModel):
+ username: str
+ password: str
+
+ class Config:
+ extra = "forbid"
+
+
+@app.post("/login/")
+async def login(data: Annotated[FormData, Form()]):
+ return data
diff --git a/docs_src/request_form_models/tutorial002_pv1_an_py39.py b/docs_src/request_form_models/tutorial002_pv1_an_py39.py
new file mode 100644
index 000000000..942d5d411
--- /dev/null
+++ b/docs_src/request_form_models/tutorial002_pv1_an_py39.py
@@ -0,0 +1,19 @@
+from typing import Annotated
+
+from fastapi import FastAPI, Form
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class FormData(BaseModel):
+ username: str
+ password: str
+
+ class Config:
+ extra = "forbid"
+
+
+@app.post("/login/")
+async def login(data: Annotated[FormData, Form()]):
+ return data
diff --git a/fastapi/__init__.py b/fastapi/__init__.py
index 3413dffc8..c2ed4859a 100644
--- a/fastapi/__init__.py
+++ b/fastapi/__init__.py
@@ -1,6 +1,6 @@
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
-__version__ = "0.112.0"
+__version__ = "0.114.1"
from starlette import status as status
diff --git a/fastapi/_compat.py b/fastapi/_compat.py
index 2e0194d12..38ffc667c 100644
--- a/fastapi/_compat.py
+++ b/fastapi/_compat.py
@@ -2,6 +2,7 @@ from collections import deque
from copy import copy
from dataclasses import dataclass, is_dataclass
from enum import Enum
+from functools import lru_cache
from typing import (
Any,
Callable,
@@ -296,6 +297,12 @@ if PYDANTIC_V2:
BodyModel: Type[BaseModel] = create_model(model_name, **field_params) # type: ignore[call-overload]
return BodyModel
+ def get_model_fields(model: Type[BaseModel]) -> List[ModelField]:
+ return [
+ ModelField(field_info=field_info, name=name)
+ for name, field_info in model.model_fields.items()
+ ]
+
else:
from fastapi.openapi.constants import REF_PREFIX as REF_PREFIX
from pydantic import AnyUrl as Url # noqa: F401
@@ -530,6 +537,9 @@ else:
BodyModel.__fields__[f.name] = f # type: ignore[index]
return BodyModel
+ def get_model_fields(model: Type[BaseModel]) -> List[ModelField]:
+ return list(model.__fields__.values()) # type: ignore[attr-defined]
+
def _regenerate_error_with_loc(
*, errors: Sequence[Any], loc_prefix: Tuple[Union[str, int], ...]
@@ -549,6 +559,12 @@ def _annotation_is_sequence(annotation: Union[Type[Any], None]) -> bool:
def field_annotation_is_sequence(annotation: Union[Type[Any], None]) -> bool:
+ origin = get_origin(annotation)
+ if origin is Union or origin is UnionType:
+ for arg in get_args(annotation):
+ if field_annotation_is_sequence(arg):
+ return True
+ return False
return _annotation_is_sequence(annotation) or _annotation_is_sequence(
get_origin(annotation)
)
@@ -651,3 +667,8 @@ def is_uploadfile_sequence_annotation(annotation: Any) -> bool:
is_uploadfile_or_nonable_uploadfile_annotation(sub_annotation)
for sub_annotation in get_args(annotation)
)
+
+
+@lru_cache
+def get_cached_model_fields(model: Type[BaseModel]) -> List[ModelField]:
+ return get_model_fields(model)
diff --git a/fastapi/applications.py b/fastapi/applications.py
index b880c6d4f..88ecc80f5 100644
--- a/fastapi/applications.py
+++ b/fastapi/applications.py
@@ -1056,7 +1056,7 @@ class FastAPI(Starlette):
def add_api_route(
self,
path: str,
- endpoint: Callable[..., Coroutine[Any, Any, Response]],
+ endpoint: Callable[..., Any],
*,
response_model: Any = Default(None),
status_code: Optional[int] = None,
diff --git a/fastapi/dependencies/models.py b/fastapi/dependencies/models.py
index 61ef00638..418c11725 100644
--- a/fastapi/dependencies/models.py
+++ b/fastapi/dependencies/models.py
@@ -1,58 +1,37 @@
-from typing import Any, Callable, List, Optional, Sequence
+from dataclasses import dataclass, field
+from typing import Any, Callable, List, Optional, Sequence, Tuple
from fastapi._compat import ModelField
from fastapi.security.base import SecurityBase
+@dataclass
class SecurityRequirement:
- def __init__(
- self, security_scheme: SecurityBase, scopes: Optional[Sequence[str]] = None
- ):
- self.security_scheme = security_scheme
- self.scopes = scopes
+ security_scheme: SecurityBase
+ scopes: Optional[Sequence[str]] = None
+@dataclass
class Dependant:
- def __init__(
- self,
- *,
- path_params: Optional[List[ModelField]] = None,
- query_params: Optional[List[ModelField]] = None,
- header_params: Optional[List[ModelField]] = None,
- cookie_params: Optional[List[ModelField]] = None,
- body_params: Optional[List[ModelField]] = None,
- dependencies: Optional[List["Dependant"]] = None,
- security_schemes: Optional[List[SecurityRequirement]] = None,
- name: Optional[str] = None,
- call: Optional[Callable[..., Any]] = None,
- request_param_name: Optional[str] = None,
- websocket_param_name: Optional[str] = None,
- http_connection_param_name: Optional[str] = None,
- response_param_name: Optional[str] = None,
- background_tasks_param_name: Optional[str] = None,
- security_scopes_param_name: Optional[str] = None,
- security_scopes: Optional[List[str]] = None,
- use_cache: bool = True,
- path: Optional[str] = None,
- ) -> None:
- self.path_params = path_params or []
- self.query_params = query_params or []
- self.header_params = header_params or []
- self.cookie_params = cookie_params or []
- self.body_params = body_params or []
- self.dependencies = dependencies or []
- self.security_requirements = security_schemes or []
- self.request_param_name = request_param_name
- self.websocket_param_name = websocket_param_name
- self.http_connection_param_name = http_connection_param_name
- self.response_param_name = response_param_name
- self.background_tasks_param_name = background_tasks_param_name
- self.security_scopes = security_scopes
- self.security_scopes_param_name = security_scopes_param_name
- self.name = name
- self.call = call
- self.use_cache = use_cache
- # Store the path to be able to re-generate a dependable from it in overrides
- self.path = path
- # Save the cache key at creation to optimize performance
+ path_params: List[ModelField] = field(default_factory=list)
+ query_params: List[ModelField] = field(default_factory=list)
+ header_params: List[ModelField] = field(default_factory=list)
+ cookie_params: List[ModelField] = field(default_factory=list)
+ body_params: List[ModelField] = field(default_factory=list)
+ dependencies: List["Dependant"] = field(default_factory=list)
+ security_requirements: List[SecurityRequirement] = field(default_factory=list)
+ name: Optional[str] = None
+ call: Optional[Callable[..., Any]] = None
+ request_param_name: Optional[str] = None
+ websocket_param_name: Optional[str] = None
+ http_connection_param_name: Optional[str] = None
+ response_param_name: Optional[str] = None
+ background_tasks_param_name: Optional[str] = None
+ security_scopes_param_name: Optional[str] = None
+ security_scopes: Optional[List[str]] = None
+ use_cache: bool = True
+ path: Optional[str] = None
+ cache_key: Tuple[Optional[Callable[..., Any]], Tuple[str, ...]] = field(init=False)
+
+ def __post_init__(self) -> None:
self.cache_key = (self.call, tuple(sorted(set(self.security_scopes or []))))
diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py
index 4f984177a..f18eace9d 100644
--- a/fastapi/dependencies/utils.py
+++ b/fastapi/dependencies/utils.py
@@ -1,6 +1,7 @@
import inspect
from contextlib import AsyncExitStack, contextmanager
from copy import copy, deepcopy
+from dataclasses import dataclass
from typing import (
Any,
Callable,
@@ -31,6 +32,7 @@ from fastapi._compat import (
evaluate_forwardref,
field_annotation_is_scalar,
get_annotation_from_field_info,
+ get_cached_model_fields,
get_missing_field_error,
is_bytes_field,
is_bytes_sequence_field,
@@ -54,11 +56,18 @@ from fastapi.logger import logger
from fastapi.security.base import SecurityBase
from fastapi.security.oauth2 import OAuth2, SecurityScopes
from fastapi.security.open_id_connect_url import OpenIdConnect
-from fastapi.utils import create_response_field, get_path_param_names
+from fastapi.utils import create_model_field, get_path_param_names
+from pydantic import BaseModel
from pydantic.fields import FieldInfo
from starlette.background import BackgroundTasks as StarletteBackgroundTasks
from starlette.concurrency import run_in_threadpool
-from starlette.datastructures import FormData, Headers, QueryParams, UploadFile
+from starlette.datastructures import (
+ FormData,
+ Headers,
+ ImmutableMultiDict,
+ QueryParams,
+ UploadFile,
+)
from starlette.requests import HTTPConnection, Request
from starlette.responses import Response
from starlette.websockets import WebSocket
@@ -79,25 +88,23 @@ multipart_incorrect_install_error = (
)
-def check_file_field(field: ModelField) -> None:
- field_info = field.field_info
- if isinstance(field_info, params.Form):
- try:
- # __version__ is available in both multiparts, and can be mocked
- from multipart import __version__ # type: ignore
+def ensure_multipart_is_installed() -> None:
+ try:
+ # __version__ is available in both multiparts, and can be mocked
+ from multipart import __version__ # type: ignore
- assert __version__
- try:
- # parse_options_header is only available in the right multipart
- from multipart.multipart import parse_options_header # type: ignore
+ assert __version__
+ try:
+ # parse_options_header is only available in the right multipart
+ from multipart.multipart import parse_options_header # type: ignore
- assert parse_options_header
- except ImportError:
- logger.error(multipart_incorrect_install_error)
- raise RuntimeError(multipart_incorrect_install_error) from None
+ assert parse_options_header
except ImportError:
- logger.error(multipart_not_installed_error)
- raise RuntimeError(multipart_not_installed_error) from None
+ logger.error(multipart_incorrect_install_error)
+ raise RuntimeError(multipart_incorrect_install_error) from None
+ except ImportError:
+ logger.error(multipart_not_installed_error)
+ raise RuntimeError(multipart_not_installed_error) from None
def get_param_sub_dependant(
@@ -175,7 +182,7 @@ def get_flat_dependant(
header_params=dependant.header_params.copy(),
cookie_params=dependant.cookie_params.copy(),
body_params=dependant.body_params.copy(),
- security_schemes=dependant.security_requirements.copy(),
+ security_requirements=dependant.security_requirements.copy(),
use_cache=dependant.use_cache,
path=dependant.path,
)
@@ -258,16 +265,16 @@ def get_dependant(
)
for param_name, param in signature_params.items():
is_path_param = param_name in path_param_names
- type_annotation, depends, param_field = analyze_param(
+ param_details = analyze_param(
param_name=param_name,
annotation=param.annotation,
value=param.default,
is_path_param=is_path_param,
)
- if depends is not None:
+ if param_details.depends is not None:
sub_dependant = get_param_sub_dependant(
param_name=param_name,
- depends=depends,
+ depends=param_details.depends,
path=path,
security_scopes=security_scopes,
)
@@ -275,18 +282,18 @@ def get_dependant(
continue
if add_non_field_param_to_dependency(
param_name=param_name,
- type_annotation=type_annotation,
+ type_annotation=param_details.type_annotation,
dependant=dependant,
):
assert (
- param_field is None
+ param_details.field is None
), f"Cannot specify multiple FastAPI annotations for {param_name!r}"
continue
- assert param_field is not None
- if is_body_param(param_field=param_field, is_path_param=is_path_param):
- dependant.body_params.append(param_field)
+ assert param_details.field is not None
+ if isinstance(param_details.field.field_info, params.Body):
+ dependant.body_params.append(param_details.field)
else:
- add_param_to_fields(field=param_field, dependant=dependant)
+ add_param_to_fields(field=param_details.field, dependant=dependant)
return dependant
@@ -314,13 +321,20 @@ def add_non_field_param_to_dependency(
return None
+@dataclass
+class ParamDetails:
+ type_annotation: Any
+ depends: Optional[params.Depends]
+ field: Optional[ModelField]
+
+
def analyze_param(
*,
param_name: str,
annotation: Any,
value: Any,
is_path_param: bool,
-) -> Tuple[Any, Optional[params.Depends], Optional[ModelField]]:
+) -> ParamDetails:
field_info = None
depends = None
type_annotation: Any = Any
@@ -328,6 +342,7 @@ def analyze_param(
if annotation is not inspect.Signature.empty:
use_annotation = annotation
type_annotation = annotation
+ # Extract Annotated info
if get_origin(use_annotation) is Annotated:
annotated_args = get_args(annotation)
type_annotation = annotated_args[0]
@@ -342,11 +357,12 @@ def analyze_param(
if isinstance(arg, (params.Param, params.Body, params.Depends))
]
if fastapi_specific_annotations:
- fastapi_annotation: Union[
- FieldInfo, params.Depends, None
- ] = fastapi_specific_annotations[-1]
+ fastapi_annotation: Union[FieldInfo, params.Depends, None] = (
+ fastapi_specific_annotations[-1]
+ )
else:
fastapi_annotation = None
+ # Set default for Annotated FieldInfo
if isinstance(fastapi_annotation, FieldInfo):
# Copy `field_info` because we mutate `field_info.default` below.
field_info = copy_field_info(
@@ -361,9 +377,10 @@ def analyze_param(
field_info.default = value
else:
field_info.default = Required
+ # Get Annotated Depends
elif isinstance(fastapi_annotation, params.Depends):
depends = fastapi_annotation
-
+ # Get Depends from default value
if isinstance(value, params.Depends):
assert depends is None, (
"Cannot specify `Depends` in `Annotated` and default value"
@@ -374,6 +391,7 @@ def analyze_param(
f" default value together for {param_name!r}"
)
depends = value
+ # Get FieldInfo from default value
elif isinstance(value, FieldInfo):
assert field_info is None, (
"Cannot specify FastAPI annotations in `Annotated` and default value"
@@ -383,11 +401,13 @@ def analyze_param(
if PYDANTIC_V2:
field_info.annotation = type_annotation
+ # Get Depends from type annotation
if depends is not None and depends.dependency is None:
# Copy `depends` before mutating it
depends = copy(depends)
depends.dependency = type_annotation
+ # Handle non-param type annotations like Request
if lenient_issubclass(
type_annotation,
(
@@ -403,6 +423,7 @@ def analyze_param(
assert (
field_info is None
), f"Cannot specify FastAPI annotation for type {type_annotation!r}"
+ # Handle default assignations, neither field_info nor depends was not found in Annotated nor default value
elif field_info is None and depends is None:
default_value = value if value is not inspect.Signature.empty else Required
if is_path_param:
@@ -420,7 +441,9 @@ def analyze_param(
field_info = params.Query(annotation=use_annotation, default=default_value)
field = None
+ # It's a field_info, not a dependency
if field_info is not None:
+ # Handle field_info.in_
if is_path_param:
assert isinstance(field_info, params.Path), (
f"Cannot use `{field_info.__class__.__name__}` for path param"
@@ -436,12 +459,14 @@ def analyze_param(
field_info,
param_name,
)
+ if isinstance(field_info, params.Form):
+ ensure_multipart_is_installed()
if not field_info.alias and getattr(field_info, "convert_underscores", None):
alias = param_name.replace("_", "-")
else:
alias = field_info.alias or param_name
field_info.alias = alias
- field = create_response_field(
+ field = create_model_field(
name=param_name,
type_=use_annotation_from_field_info,
default=field_info.default,
@@ -449,27 +474,14 @@ def analyze_param(
required=field_info.default in (Required, Undefined),
field_info=field_info,
)
+ if is_path_param:
+ assert is_scalar_field(
+ field=field
+ ), "Path params must be of one of the supported types"
+ elif isinstance(field_info, params.Query):
+ assert is_scalar_field(field) or is_scalar_sequence_field(field)
- return type_annotation, depends, field
-
-
-def is_body_param(*, param_field: ModelField, is_path_param: bool) -> bool:
- if is_path_param:
- assert is_scalar_field(
- field=param_field
- ), "Path params must be of one of the supported types"
- return False
- elif is_scalar_field(field=param_field):
- return False
- elif isinstance(
- param_field.field_info, (params.Query, params.Header)
- ) and is_scalar_sequence_field(param_field):
- return False
- else:
- assert isinstance(
- param_field.field_info, params.Body
- ), f"Param: {param_field.name} can only be a request body, using Body()"
- return True
+ return ParamDetails(type_annotation=type_annotation, depends=depends, field=field)
def add_param_to_fields(*, field: ModelField, dependant: Dependant) -> None:
@@ -521,6 +533,15 @@ async def solve_generator(
return await stack.enter_async_context(cm)
+@dataclass
+class SolvedDependency:
+ values: Dict[str, Any]
+ errors: List[Any]
+ background_tasks: Optional[StarletteBackgroundTasks]
+ response: Response
+ dependency_cache: Dict[Tuple[Callable[..., Any], Tuple[str]], Any]
+
+
async def solve_dependencies(
*,
request: Union[Request, WebSocket],
@@ -531,13 +552,8 @@ async def solve_dependencies(
dependency_overrides_provider: Optional[Any] = None,
dependency_cache: Optional[Dict[Tuple[Callable[..., Any], Tuple[str]], Any]] = None,
async_exit_stack: AsyncExitStack,
-) -> Tuple[
- Dict[str, Any],
- List[Any],
- Optional[StarletteBackgroundTasks],
- Response,
- Dict[Tuple[Callable[..., Any], Tuple[str]], Any],
-]:
+ embed_body_fields: bool,
+) -> SolvedDependency:
values: Dict[str, Any] = {}
errors: List[Any] = []
if response is None:
@@ -578,28 +594,23 @@ async def solve_dependencies(
dependency_overrides_provider=dependency_overrides_provider,
dependency_cache=dependency_cache,
async_exit_stack=async_exit_stack,
+ embed_body_fields=embed_body_fields,
)
- (
- sub_values,
- sub_errors,
- background_tasks,
- _, # the subdependency returns the same response we have
- sub_dependency_cache,
- ) = solved_result
- dependency_cache.update(sub_dependency_cache)
- if sub_errors:
- errors.extend(sub_errors)
+ background_tasks = solved_result.background_tasks
+ dependency_cache.update(solved_result.dependency_cache)
+ if solved_result.errors:
+ errors.extend(solved_result.errors)
continue
if sub_dependant.use_cache and sub_dependant.cache_key in dependency_cache:
solved = dependency_cache[sub_dependant.cache_key]
elif is_gen_callable(call) or is_async_gen_callable(call):
solved = await solve_generator(
- call=call, stack=async_exit_stack, sub_values=sub_values
+ call=call, stack=async_exit_stack, sub_values=solved_result.values
)
elif is_coroutine_callable(call):
- solved = await call(**sub_values)
+ solved = await call(**solved_result.values)
else:
- solved = await run_in_threadpool(call, **sub_values)
+ solved = await run_in_threadpool(call, **solved_result.values)
if sub_dependant.name is not None:
values[sub_dependant.name] = solved
if sub_dependant.cache_key not in dependency_cache:
@@ -626,7 +637,9 @@ async def solve_dependencies(
body_values,
body_errors,
) = await request_body_to_args( # body_params checked above
- required_params=dependant.body_params, received_body=body
+ body_fields=dependant.body_params,
+ received_body=body,
+ embed_body_fields=embed_body_fields,
)
values.update(body_values)
errors.extend(body_errors)
@@ -646,142 +659,206 @@ async def solve_dependencies(
values[dependant.security_scopes_param_name] = SecurityScopes(
scopes=dependant.security_scopes
)
- return values, errors, background_tasks, response, dependency_cache
+ return SolvedDependency(
+ values=values,
+ errors=errors,
+ background_tasks=background_tasks,
+ response=response,
+ dependency_cache=dependency_cache,
+ )
+
+
+def _validate_value_with_model_field(
+ *, field: ModelField, value: Any, values: Dict[str, Any], loc: Tuple[str, ...]
+) -> Tuple[Any, List[Any]]:
+ if value is None:
+ if field.required:
+ return None, [get_missing_field_error(loc=loc)]
+ else:
+ return deepcopy(field.default), []
+ v_, errors_ = field.validate(value, values, loc=loc)
+ if isinstance(errors_, ErrorWrapper):
+ return None, [errors_]
+ elif isinstance(errors_, list):
+ new_errors = _regenerate_error_with_loc(errors=errors_, loc_prefix=())
+ return None, new_errors
+ else:
+ return v_, []
+
+
+def _get_multidict_value(field: ModelField, values: Mapping[str, Any]) -> Any:
+ if is_sequence_field(field) and isinstance(values, (ImmutableMultiDict, Headers)):
+ value = values.getlist(field.alias)
+ else:
+ value = values.get(field.alias, None)
+ if (
+ value is None
+ or (
+ isinstance(field.field_info, params.Form)
+ and isinstance(value, str) # For type checks
+ and value == ""
+ )
+ or (is_sequence_field(field) and len(value) == 0)
+ ):
+ if field.required:
+ return
+ else:
+ return deepcopy(field.default)
+ return value
def request_params_to_args(
- required_params: Sequence[ModelField],
+ fields: Sequence[ModelField],
received_params: Union[Mapping[str, Any], QueryParams, Headers],
) -> Tuple[Dict[str, Any], List[Any]]:
- values = {}
+ values: Dict[str, Any] = {}
errors = []
- for field in required_params:
- if is_scalar_sequence_field(field) and isinstance(
- received_params, (QueryParams, Headers)
- ):
- value = received_params.getlist(field.alias) or field.default
- else:
- value = received_params.get(field.alias)
+ for field in fields:
+ value = _get_multidict_value(field, received_params)
field_info = field.field_info
assert isinstance(
field_info, params.Param
), "Params must be subclasses of Param"
loc = (field_info.in_.value, field.alias)
- if value is None:
- if field.required:
- errors.append(get_missing_field_error(loc=loc))
- else:
- values[field.name] = deepcopy(field.default)
- continue
- v_, errors_ = field.validate(value, values, loc=loc)
- if isinstance(errors_, ErrorWrapper):
- errors.append(errors_)
- elif isinstance(errors_, list):
- new_errors = _regenerate_error_with_loc(errors=errors_, loc_prefix=())
- errors.extend(new_errors)
+ v_, errors_ = _validate_value_with_model_field(
+ field=field, value=value, values=values, loc=loc
+ )
+ if errors_:
+ errors.extend(errors_)
else:
values[field.name] = v_
return values, errors
+def _should_embed_body_fields(fields: List[ModelField]) -> bool:
+ if not fields:
+ return False
+ # More than one dependency could have the same field, it would show up as multiple
+ # fields but it's the same one, so count them by name
+ body_param_names_set = {field.name for field in fields}
+ # A top level field has to be a single field, not multiple
+ if len(body_param_names_set) > 1:
+ return True
+ first_field = fields[0]
+ # If it explicitly specifies it is embedded, it has to be embedded
+ if getattr(first_field.field_info, "embed", None):
+ return True
+ # If it's a Form (or File) field, it has to be a BaseModel to be top level
+ # otherwise it has to be embedded, so that the key value pair can be extracted
+ if isinstance(first_field.field_info, params.Form) and not lenient_issubclass(
+ first_field.type_, BaseModel
+ ):
+ return True
+ return False
+
+
+async def _extract_form_body(
+ body_fields: List[ModelField],
+ received_body: FormData,
+) -> Dict[str, Any]:
+ values = {}
+ first_field = body_fields[0]
+ first_field_info = first_field.field_info
+
+ for field in body_fields:
+ value = _get_multidict_value(field, received_body)
+ if (
+ isinstance(first_field_info, params.File)
+ and is_bytes_field(field)
+ and isinstance(value, UploadFile)
+ ):
+ value = await value.read()
+ elif (
+ is_bytes_sequence_field(field)
+ and isinstance(first_field_info, params.File)
+ and value_is_sequence(value)
+ ):
+ # For types
+ assert isinstance(value, sequence_types) # type: ignore[arg-type]
+ results: List[Union[bytes, str]] = []
+
+ async def process_fn(
+ fn: Callable[[], Coroutine[Any, Any, Any]],
+ ) -> None:
+ result = await fn()
+ results.append(result) # noqa: B023
+
+ async with anyio.create_task_group() as tg:
+ for sub_value in value:
+ tg.start_soon(process_fn, sub_value.read)
+ value = serialize_sequence_value(field=field, value=results)
+ if value is not None:
+ values[field.name] = value
+ for key, value in received_body.items():
+ if key not in values:
+ values[key] = value
+ return values
+
+
async def request_body_to_args(
- required_params: List[ModelField],
+ body_fields: List[ModelField],
received_body: Optional[Union[Dict[str, Any], FormData]],
+ embed_body_fields: bool,
) -> Tuple[Dict[str, Any], List[Dict[str, Any]]]:
- values = {}
+ values: Dict[str, Any] = {}
errors: List[Dict[str, Any]] = []
- if required_params:
- field = required_params[0]
- field_info = field.field_info
- embed = getattr(field_info, "embed", None)
- field_alias_omitted = len(required_params) == 1 and not embed
- if field_alias_omitted:
- received_body = {field.alias: received_body}
-
- for field in required_params:
- loc: Tuple[str, ...]
- if field_alias_omitted:
- loc = ("body",)
- else:
- loc = ("body", field.alias)
-
- value: Optional[Any] = None
- if received_body is not None:
- if (is_sequence_field(field)) and isinstance(received_body, FormData):
- value = received_body.getlist(field.alias)
- else:
- try:
- value = received_body.get(field.alias)
- except AttributeError:
- errors.append(get_missing_field_error(loc))
- continue
- if (
- value is None
- or (isinstance(field_info, params.Form) and value == "")
- or (
- isinstance(field_info, params.Form)
- and is_sequence_field(field)
- and len(value) == 0
- )
- ):
- if field.required:
- errors.append(get_missing_field_error(loc))
- else:
- values[field.name] = deepcopy(field.default)
+ assert body_fields, "request_body_to_args() should be called with fields"
+ single_not_embedded_field = len(body_fields) == 1 and not embed_body_fields
+ first_field = body_fields[0]
+ body_to_process = received_body
+
+ fields_to_extract: List[ModelField] = body_fields
+
+ if single_not_embedded_field and lenient_issubclass(first_field.type_, BaseModel):
+ fields_to_extract = get_cached_model_fields(first_field.type_)
+
+ if isinstance(received_body, FormData):
+ body_to_process = await _extract_form_body(fields_to_extract, received_body)
+
+ if single_not_embedded_field:
+ loc: Tuple[str, ...] = ("body",)
+ v_, errors_ = _validate_value_with_model_field(
+ field=first_field, value=body_to_process, values=values, loc=loc
+ )
+ return {first_field.name: v_}, errors_
+ for field in body_fields:
+ loc = ("body", field.alias)
+ value: Optional[Any] = None
+ if body_to_process is not None:
+ try:
+ value = body_to_process.get(field.alias)
+ # If the received body is a list, not a dict
+ except AttributeError:
+ errors.append(get_missing_field_error(loc))
continue
- if (
- isinstance(field_info, params.File)
- and is_bytes_field(field)
- and isinstance(value, UploadFile)
- ):
- value = await value.read()
- elif (
- is_bytes_sequence_field(field)
- and isinstance(field_info, params.File)
- and value_is_sequence(value)
- ):
- # For types
- assert isinstance(value, sequence_types) # type: ignore[arg-type]
- results: List[Union[bytes, str]] = []
-
- async def process_fn(
- fn: Callable[[], Coroutine[Any, Any, Any]],
- ) -> None:
- result = await fn()
- results.append(result) # noqa: B023
-
- async with anyio.create_task_group() as tg:
- for sub_value in value:
- tg.start_soon(process_fn, sub_value.read)
- value = serialize_sequence_value(field=field, value=results)
-
- v_, errors_ = field.validate(value, values, loc=loc)
-
- if isinstance(errors_, list):
- errors.extend(errors_)
- elif errors_:
- errors.append(errors_)
- else:
- values[field.name] = v_
+ v_, errors_ = _validate_value_with_model_field(
+ field=field, value=value, values=values, loc=loc
+ )
+ if errors_:
+ errors.extend(errors_)
+ else:
+ values[field.name] = v_
return values, errors
-def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]:
- flat_dependant = get_flat_dependant(dependant)
+def get_body_field(
+ *, flat_dependant: Dependant, name: str, embed_body_fields: bool
+) -> Optional[ModelField]:
+ """
+ Get a ModelField representing the request body for a path operation, combining
+ all body parameters into a single field if necessary.
+
+ Used to check if it's form data (with `isinstance(body_field, params.Form)`)
+ or JSON and to generate the JSON Schema for a request body.
+
+ This is **not** used to validate/parse the request body, that's done with each
+ individual body parameter.
+ """
if not flat_dependant.body_params:
return None
first_param = flat_dependant.body_params[0]
- field_info = first_param.field_info
- embed = getattr(field_info, "embed", None)
- body_param_names_set = {param.name for param in flat_dependant.body_params}
- if len(body_param_names_set) == 1 and not embed:
- check_file_field(first_param)
+ if not embed_body_fields:
return first_param
- # If one field requires to embed, all have to be embedded
- # in case a sub-dependency is evaluated with a single unique body field
- # That is combined (embedded) with other body fields
- for param in flat_dependant.body_params:
- setattr(param.field_info, "embed", True) # noqa: B010
model_name = "Body_" + name
BodyModel = create_body_model(
fields=flat_dependant.body_params, model_name=model_name
@@ -807,12 +884,11 @@ def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]:
]
if len(set(body_param_media_types)) == 1:
BodyFieldInfo_kwargs["media_type"] = body_param_media_types[0]
- final_field = create_response_field(
+ final_field = create_model_field(
name="body",
type_=BodyModel,
required=required,
alias="body",
field_info=BodyFieldInfo(**BodyFieldInfo_kwargs),
)
- check_file_field(final_field)
return final_field
diff --git a/fastapi/param_functions.py b/fastapi/param_functions.py
index 3b25d774a..7ddaace25 100644
--- a/fastapi/param_functions.py
+++ b/fastapi/param_functions.py
@@ -1282,7 +1282,7 @@ def Body( # noqa: N802
),
] = _Unset,
embed: Annotated[
- bool,
+ Union[bool, None],
Doc(
"""
When `embed` is `True`, the parameter will be expected in a JSON body as a
@@ -1294,7 +1294,7 @@ def Body( # noqa: N802
[FastAPI docs for Body - Multiple Parameters](https://fastapi.tiangolo.com/tutorial/body-multiple-params/#embed-a-single-body-parameter).
"""
),
- ] = False,
+ ] = None,
media_type: Annotated[
str,
Doc(
@@ -2343,7 +2343,7 @@ def Security( # noqa: N802
```python
from typing import Annotated
- from fastapi import Depends, FastAPI
+ from fastapi import Security, FastAPI
from .db import User
from .security import get_current_active_user
diff --git a/fastapi/params.py b/fastapi/params.py
index 860146531..90ca7cb01 100644
--- a/fastapi/params.py
+++ b/fastapi/params.py
@@ -91,7 +91,7 @@ class Param(FieldInfo):
max_length=max_length,
discriminator=discriminator,
multiple_of=multiple_of,
- allow_nan=allow_inf_nan,
+ allow_inf_nan=allow_inf_nan,
max_digits=max_digits,
decimal_places=decimal_places,
**extra,
@@ -479,7 +479,7 @@ class Body(FieldInfo):
*,
default_factory: Union[Callable[[], Any], None] = _Unset,
annotation: Optional[Any] = None,
- embed: bool = False,
+ embed: Union[bool, None] = None,
media_type: str = "application/json",
alias: Optional[str] = None,
alias_priority: Union[int, None] = _Unset,
@@ -547,7 +547,7 @@ class Body(FieldInfo):
max_length=max_length,
discriminator=discriminator,
multiple_of=multiple_of,
- allow_nan=allow_inf_nan,
+ allow_inf_nan=allow_inf_nan,
max_digits=max_digits,
decimal_places=decimal_places,
**extra,
@@ -556,7 +556,7 @@ class Body(FieldInfo):
kwargs["examples"] = examples
if regex is not None:
warnings.warn(
- "`regex` has been depreacated, please use `pattern` instead",
+ "`regex` has been deprecated, please use `pattern` instead",
category=DeprecationWarning,
stacklevel=4,
)
@@ -642,7 +642,6 @@ class Form(Body):
default=default,
default_factory=default_factory,
annotation=annotation,
- embed=True,
media_type=media_type,
alias=alias,
alias_priority=alias_priority,
diff --git a/fastapi/routing.py b/fastapi/routing.py
index 7f7f45611..9d60a9b7b 100644
--- a/fastapi/routing.py
+++ b/fastapi/routing.py
@@ -3,14 +3,16 @@ import dataclasses
import email.message
import inspect
import json
-from contextlib import AsyncExitStack
+from contextlib import AsyncExitStack, asynccontextmanager
from enum import Enum, IntEnum
from typing import (
Any,
+ AsyncIterator,
Callable,
Coroutine,
Dict,
List,
+ Mapping,
Optional,
Sequence,
Set,
@@ -31,8 +33,10 @@ from fastapi._compat import (
from fastapi.datastructures import Default, DefaultPlaceholder
from fastapi.dependencies.models import Dependant
from fastapi.dependencies.utils import (
+ _should_embed_body_fields,
get_body_field,
get_dependant,
+ get_flat_dependant,
get_parameterless_sub_dependant,
get_typed_return_annotation,
solve_dependencies,
@@ -47,7 +51,7 @@ from fastapi.exceptions import (
from fastapi.types import DecoratedCallable, IncEx
from fastapi.utils import (
create_cloned_field,
- create_response_field,
+ create_model_field,
generate_unique_id,
get_value_or_default,
is_body_allowed_for_status_code,
@@ -67,7 +71,7 @@ from starlette.routing import (
websocket_session,
)
from starlette.routing import Mount as Mount # noqa
-from starlette.types import ASGIApp, Lifespan, Scope
+from starlette.types import AppType, ASGIApp, Lifespan, Scope
from starlette.websockets import WebSocket
from typing_extensions import Annotated, Doc, deprecated
@@ -119,6 +123,23 @@ def _prepare_response_content(
return res
+def _merge_lifespan_context(
+ original_context: Lifespan[Any], nested_context: Lifespan[Any]
+) -> Lifespan[Any]:
+ @asynccontextmanager
+ async def merged_lifespan(
+ app: AppType,
+ ) -> AsyncIterator[Optional[Mapping[str, Any]]]:
+ async with original_context(app) as maybe_original_state:
+ async with nested_context(app) as maybe_nested_state:
+ if maybe_nested_state is None and maybe_original_state is None:
+ yield None # old ASGI compatibility
+ else:
+ yield {**(maybe_nested_state or {}), **(maybe_original_state or {})}
+
+ return merged_lifespan # type: ignore[return-value]
+
+
async def serialize_response(
*,
field: Optional[ModelField] = None,
@@ -209,6 +230,7 @@ def get_request_handler(
response_model_exclude_none: bool = False,
response_model_context: Optional[Dict[str, Any]] = None,
dependency_overrides_provider: Optional[Any] = None,
+ 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 = asyncio.iscoroutinefunction(dependant.call)
@@ -275,27 +297,36 @@ def get_request_handler(
body=body,
dependency_overrides_provider=dependency_overrides_provider,
async_exit_stack=async_exit_stack,
+ embed_body_fields=embed_body_fields,
)
- values, errors, background_tasks, sub_response, _ = solved_result
+ errors = solved_result.errors
if not errors:
raw_response = await run_endpoint_function(
- dependant=dependant, values=values, is_coroutine=is_coroutine
+ dependant=dependant,
+ values=solved_result.values,
+ is_coroutine=is_coroutine,
)
if isinstance(raw_response, Response):
if raw_response.background is None:
- raw_response.background = background_tasks
+ raw_response.background = solved_result.background_tasks
response = raw_response
else:
- response_args: Dict[str, Any] = {"background": background_tasks}
+ response_args: Dict[str, Any] = {
+ "background": solved_result.background_tasks
+ }
# If status_code was set, use it, otherwise use the default from the
# response class, in the case of redirect it's 307
current_status_code = (
- status_code if status_code else sub_response.status_code
+ status_code
+ if status_code
+ else solved_result.response.status_code
)
if current_status_code is not None:
response_args["status_code"] = current_status_code
- if sub_response.status_code:
- response_args["status_code"] = sub_response.status_code
+ if solved_result.response.status_code:
+ response_args["status_code"] = (
+ solved_result.response.status_code
+ )
content = await serialize_response(
field=response_field,
response_content=raw_response,
@@ -311,7 +342,7 @@ def get_request_handler(
response = actual_response_class(content, **response_args)
if not is_body_allowed_for_status_code(response.status_code):
response.body = b""
- response.headers.raw.extend(sub_response.headers.raw)
+ response.headers.raw.extend(solved_result.response.headers.raw)
if errors:
validation_error = RequestValidationError(
_normalize_errors(errors), body=body
@@ -331,7 +362,9 @@ def get_request_handler(
def get_websocket_app(
- dependant: Dependant, dependency_overrides_provider: Optional[Any] = None
+ dependant: Dependant,
+ dependency_overrides_provider: Optional[Any] = None,
+ embed_body_fields: bool = False,
) -> Callable[[WebSocket], Coroutine[Any, Any, Any]]:
async def app(websocket: WebSocket) -> None:
async with AsyncExitStack() as async_exit_stack:
@@ -344,12 +377,14 @@ def get_websocket_app(
dependant=dependant,
dependency_overrides_provider=dependency_overrides_provider,
async_exit_stack=async_exit_stack,
+ embed_body_fields=embed_body_fields,
)
- values, errors, _, _2, _3 = solved_result
- if errors:
- raise WebSocketRequestValidationError(_normalize_errors(errors))
+ if solved_result.errors:
+ raise WebSocketRequestValidationError(
+ _normalize_errors(solved_result.errors)
+ )
assert dependant.call is not None, "dependant.call must be a function"
- await dependant.call(**values)
+ await dependant.call(**solved_result.values)
return app
@@ -375,11 +410,15 @@ class APIWebSocketRoute(routing.WebSocketRoute):
0,
get_parameterless_sub_dependant(depends=depends, path=self.path_format),
)
-
+ self._flat_dependant = get_flat_dependant(self.dependant)
+ self._embed_body_fields = _should_embed_body_fields(
+ self._flat_dependant.body_params
+ )
self.app = websocket_session(
get_websocket_app(
dependant=self.dependant,
dependency_overrides_provider=dependency_overrides_provider,
+ embed_body_fields=self._embed_body_fields,
)
)
@@ -460,9 +499,9 @@ class APIRoute(routing.Route):
methods = ["GET"]
self.methods: Set[str] = {method.upper() for method in methods}
if isinstance(generate_unique_id_function, DefaultPlaceholder):
- current_generate_unique_id: Callable[
- ["APIRoute"], str
- ] = generate_unique_id_function.value
+ current_generate_unique_id: Callable[[APIRoute], str] = (
+ generate_unique_id_function.value
+ )
else:
current_generate_unique_id = generate_unique_id_function
self.unique_id = self.operation_id or current_generate_unique_id(self)
@@ -475,7 +514,7 @@ class APIRoute(routing.Route):
status_code
), f"Status code {status_code} must not have a response body"
response_name = "Response_" + self.unique_id
- self.response_field = create_response_field(
+ self.response_field = create_model_field(
name=response_name,
type_=self.response_model,
mode="serialization",
@@ -488,9 +527,9 @@ class APIRoute(routing.Route):
# By being a new field, no inheritance will be passed as is. A new model
# will always be created.
# TODO: remove when deprecating Pydantic v1
- self.secure_cloned_response_field: Optional[
- ModelField
- ] = create_cloned_field(self.response_field)
+ self.secure_cloned_response_field: Optional[ModelField] = (
+ create_cloned_field(self.response_field)
+ )
else:
self.response_field = None # type: ignore
self.secure_cloned_response_field = None
@@ -508,7 +547,7 @@ class APIRoute(routing.Route):
additional_status_code
), f"Status code {additional_status_code} must not have a response body"
response_name = f"Response_{additional_status_code}_{self.unique_id}"
- response_field = create_response_field(name=response_name, type_=model)
+ response_field = create_model_field(name=response_name, type_=model)
response_fields[additional_status_code] = response_field
if response_fields:
self.response_fields: Dict[Union[int, str], ModelField] = response_fields
@@ -522,7 +561,15 @@ class APIRoute(routing.Route):
0,
get_parameterless_sub_dependant(depends=depends, path=self.path_format),
)
- self.body_field = get_body_field(dependant=self.dependant, name=self.unique_id)
+ self._flat_dependant = get_flat_dependant(self.dependant)
+ self._embed_body_fields = _should_embed_body_fields(
+ self._flat_dependant.body_params
+ )
+ self.body_field = get_body_field(
+ flat_dependant=self._flat_dependant,
+ name=self.unique_id,
+ embed_body_fields=self._embed_body_fields,
+ )
self.app = request_response(self.get_route_handler())
def get_route_handler(self) -> Callable[[Request], Coroutine[Any, Any, Response]]:
@@ -540,6 +587,7 @@ class APIRoute(routing.Route):
response_model_exclude_none=self.response_model_exclude_none,
response_model_context=self.response_model_context,
dependency_overrides_provider=self.dependency_overrides_provider,
+ embed_body_fields=self._embed_body_fields,
)
def matches(self, scope: Scope) -> Tuple[Match, Scope]:
@@ -1320,6 +1368,10 @@ class APIRouter(routing.Router):
self.add_event_handler("startup", handler)
for handler in router.on_shutdown:
self.add_event_handler("shutdown", handler)
+ self.lifespan_context = _merge_lifespan_context(
+ self.lifespan_context,
+ router.lifespan_context,
+ )
def get(
self,
diff --git a/fastapi/utils.py b/fastapi/utils.py
index dfda4e678..4c7350fea 100644
--- a/fastapi/utils.py
+++ b/fastapi/utils.py
@@ -34,9 +34,9 @@ if TYPE_CHECKING: # pragma: nocover
from .routing import APIRoute
# Cache for `create_cloned_field`
-_CLONED_TYPES_CACHE: MutableMapping[
- Type[BaseModel], Type[BaseModel]
-] = WeakKeyDictionary()
+_CLONED_TYPES_CACHE: MutableMapping[Type[BaseModel], Type[BaseModel]] = (
+ WeakKeyDictionary()
+)
def is_body_allowed_for_status_code(status_code: Union[int, str, None]) -> bool:
@@ -60,9 +60,9 @@ def get_path_param_names(path: str) -> Set[str]:
return set(re.findall("{(.*?)}", path))
-def create_response_field(
+def create_model_field(
name: str,
- type_: Type[Any],
+ type_: Any,
class_validators: Optional[Dict[str, Validator]] = None,
default: Optional[Any] = Undefined,
required: Union[bool, UndefinedType] = Undefined,
@@ -71,9 +71,6 @@ def create_response_field(
alias: Optional[str] = None,
mode: Literal["validation", "serialization"] = "validation",
) -> ModelField:
- """
- Create a new response field. Raises if type_ is invalid.
- """
class_validators = class_validators or {}
if PYDANTIC_V2:
field_info = field_info or FieldInfo(
@@ -135,7 +132,7 @@ def create_cloned_field(
use_type.__fields__[f.name] = create_cloned_field(
f, cloned_types=cloned_types
)
- new_field = create_response_field(name=field.name, type_=use_type)
+ new_field = create_model_field(name=field.name, type_=use_type)
new_field.has_alias = field.has_alias # type: ignore[attr-defined]
new_field.alias = field.alias # type: ignore[misc]
new_field.class_validators = field.class_validators # type: ignore[attr-defined]
diff --git a/pyproject.toml b/pyproject.toml
index c34838b83..1be2817a1 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -41,7 +41,7 @@ classifiers = [
"Topic :: Internet :: WWW/HTTP",
]
dependencies = [
- "starlette>=0.37.2,<0.38.0",
+ "starlette>=0.37.2,<0.39.0",
"pydantic>=1.7.4,!=1.8,!=1.8.1,!=2.0.0,!=2.0.1,!=2.1.0,<3.0.0",
"typing-extensions>=4.8.0",
]
@@ -50,6 +50,8 @@ dependencies = [
Homepage = "https://github.com/fastapi/fastapi"
Documentation = "https://fastapi.tiangolo.com/"
Repository = "https://github.com/fastapi/fastapi"
+Issues = "https://github.com/fastapi/fastapi/issues"
+Changelog = "https://fastapi.tiangolo.com/release-notes/"
[project.optional-dependencies]
@@ -62,7 +64,7 @@ standard = [
# For forms and file uploads
"python-multipart >=0.0.7",
# To validate email fields
- "email_validator >=2.0.0",
+ "email-validator >=2.0.0",
# Uvicorn with uvloop
"uvicorn[standard] >=0.12.0",
# TODO: this should be part of some pydantic optional extra dependencies
@@ -89,7 +91,7 @@ all = [
# For ORJSONResponse
"orjson >=3.2.1",
# To validate email fields
- "email_validator >=2.0.0",
+ "email-validator >=2.0.0",
# Uvicorn with uvloop
"uvicorn[standard] >=0.12.0",
# Settings management
@@ -147,27 +149,14 @@ xfail_strict = true
junit_family = "xunit2"
filterwarnings = [
"error",
- # TODO: needed by asyncio in Python 3.9.7 https://bugs.python.org/issue45097, try to remove on 3.9.8
- 'ignore:The loop argument is deprecated since Python 3\.8, and scheduled for removal in Python 3\.10:DeprecationWarning:asyncio',
'ignore:starlette.middleware.wsgi is deprecated and will be removed in a future release\..*:DeprecationWarning:starlette',
- # TODO: remove after upgrading HTTPX to a version newer than 0.23.0
- # Including PR: https://github.com/encode/httpx/pull/2309
- "ignore:'cgi' is deprecated:DeprecationWarning",
# For passlib
"ignore:'crypt' is deprecated and slated for removal in Python 3.13:DeprecationWarning",
# see https://trio.readthedocs.io/en/stable/history.html#trio-0-22-0-2022-09-28
"ignore:You seem to already have a custom.*:RuntimeWarning:trio",
- # TODO remove pytest-cov
- 'ignore::pytest.PytestDeprecationWarning:pytest_cov',
# TODO: remove after upgrading SQLAlchemy to a version that includes the following changes
# https://github.com/sqlalchemy/sqlalchemy/commit/59521abcc0676e936b31a523bd968fc157fef0c2
'ignore:datetime\.datetime\.utcfromtimestamp\(\) is deprecated and scheduled for removal in a future version\..*:DeprecationWarning:sqlalchemy',
- # TODO: remove after upgrading python-jose to a version that explicitly supports Python 3.12
- # also, if it won't receive an update, consider replacing python-jose with some alternative
- # related issues:
- # - https://github.com/mpdavis/python-jose/issues/332
- # - https://github.com/mpdavis/python-jose/issues/334
- 'ignore:datetime\.datetime\.utcnow\(\) is deprecated and scheduled for removal in a future version\..*:DeprecationWarning:jose',
# Trio 24.1.0 raises a warning from attrs
# Ref: https://github.com/python-trio/trio/pull/3054
# Remove once there's a new version of Trio
@@ -176,17 +165,26 @@ filterwarnings = [
[tool.coverage.run]
parallel = true
+data_file = "coverage/.coverage"
source = [
"docs_src",
"tests",
"fastapi"
]
context = '${CONTEXT}'
+dynamic_context = "test_function"
omit = [
"docs_src/response_model/tutorial003_04.py",
"docs_src/response_model/tutorial003_04_py310.py",
]
+[tool.coverage.report]
+show_missing = true
+sort = "-Cover"
+
+[tool.coverage.html]
+show_contexts = true
+
[tool.ruff.lint]
select = [
"E", # pycodestyle errors
@@ -243,3 +241,7 @@ known-third-party = ["fastapi", "pydantic", "starlette"]
[tool.ruff.lint.pyupgrade]
# Preserve types, even if a file imports `from __future__ import annotations`.
keep-runtime-typing = true
+
+[tool.inline-snapshot]
+# default-flags=["fix"]
+# default-flags=["create"]
diff --git a/requirements-docs.txt b/requirements-docs.txt
index a7ef7ef2e..332fd1857 100644
--- a/requirements-docs.txt
+++ b/requirements-docs.txt
@@ -8,11 +8,11 @@ pyyaml >=5.3.1,<7.0.0
# For Material for MkDocs, Chinese search
jieba==0.42.1
# For image processing by Material for MkDocs
-pillow==10.3.0
+pillow==10.4.0
# For image processing by Material for MkDocs
cairosvg==2.7.1
mkdocstrings[python]==0.25.1
-griffe-typingdoc==0.2.5
+griffe-typingdoc==0.2.6
# For griffe, it formats with black
black==24.3.0
mkdocs-macros-plugin==1.0.5
diff --git a/requirements-tests.txt b/requirements-tests.txt
index bfe70f2f5..2f2576dd5 100644
--- a/requirements-tests.txt
+++ b/requirements-tests.txt
@@ -3,18 +3,18 @@
pytest >=7.1.3,<8.0.0
coverage[toml] >= 6.5.0,< 8.0
mypy ==1.8.0
-ruff ==0.2.0
+ruff ==0.6.4
dirty-equals ==0.6.0
# TODO: once removing databases from tutorial, upgrade SQLAlchemy
# probably when including SQLModel
-sqlalchemy >=1.3.18,<1.4.43
+sqlalchemy >=1.3.18,<2.0.33
databases[sqlite] >=0.3.2,<0.7.0
flask >=1.1.2,<3.0.0
anyio[trio] >=3.2.1,<4.0.0
PyJWT==2.8.0
pyyaml >=5.3.1,<7.0.0
passlib[bcrypt] >=1.7.2,<2.0.0
-
+inline-snapshot==0.13.0
# types
types-ujson ==5.7.0.1
types-orjson ==3.6.2
diff --git a/scripts/deploy_docs_status.py b/scripts/deploy_docs_status.py
index ef33fe43d..19dffbcb9 100644
--- a/scripts/deploy_docs_status.py
+++ b/scripts/deploy_docs_status.py
@@ -63,7 +63,8 @@ def main():
lang_links: dict[str, list[str]] = {}
for f in docs_files:
match = re.match(r"docs/([^/]+)/docs/(.*)", f.filename)
- assert match
+ if not match:
+ continue
lang = match.group(1)
path = match.group(2)
if path.endswith("index.md"):
diff --git a/scripts/docs.py b/scripts/docs.py
index fd2dd78f1..f0c51f7a6 100644
--- a/scripts/docs.py
+++ b/scripts/docs.py
@@ -26,6 +26,16 @@ missing_translation_snippet = """
{!../../../docs/missing-translation.md!}
"""
+non_translated_sections = [
+ "reference/",
+ "release-notes.md",
+ "fastapi-people.md",
+ "external-links.md",
+ "newsletter.md",
+ "management-tasks.md",
+ "management.md",
+]
+
docs_path = Path("docs")
en_docs_path = Path("docs/en")
en_config_path: Path = en_docs_path / mkdocs_name
@@ -251,6 +261,7 @@ def live(
lang: str = typer.Argument(
None, callback=lang_callback, autocompletion=complete_existing_lang
),
+ dirty: bool = False,
) -> None:
"""
Serve with livereload a docs site for a specific language.
@@ -265,11 +276,12 @@ def live(
if lang is None:
lang = "en"
lang_path: Path = docs_path / lang
+ # Enable line numbers during local development to make it easier to highlight
+ args = ["mkdocs", "serve", "--dev-addr", "127.0.0.1:8008"]
+ if dirty:
+ args.append("--dirty")
subprocess.run(
- ["mkdocs", "serve", "--dev-addr", "127.0.0.1:8008", "--dirty"],
- env={**os.environ, "LINENUMS": "true"},
- cwd=lang_path,
- check=True,
+ args, env={**os.environ, "LINENUMS": "true"}, cwd=lang_path, check=True
)
@@ -331,10 +343,34 @@ def verify_config() -> None:
typer.echo("Valid mkdocs.yml ✅")
+@app.command()
+def verify_non_translated() -> None:
+ """
+ Verify there are no files in the non translatable pages.
+ """
+ print("Verifying non translated pages")
+ lang_paths = get_lang_paths()
+ error_paths = []
+ for lang in lang_paths:
+ if lang.name == "en":
+ continue
+ for non_translatable in non_translated_sections:
+ non_translatable_path = lang / "docs" / non_translatable
+ if non_translatable_path.exists():
+ error_paths.append(non_translatable_path)
+ if error_paths:
+ print("Non-translated pages found, remove them:")
+ for error_path in error_paths:
+ print(error_path)
+ raise typer.Abort()
+ print("No non-translated pages found ✅")
+
+
@app.command()
def verify_docs():
verify_readme()
verify_config()
+ verify_non_translated()
@app.command()
diff --git a/scripts/format.sh b/scripts/format.sh
index 45742f79a..bf70f42e5 100755
--- a/scripts/format.sh
+++ b/scripts/format.sh
@@ -1,4 +1,4 @@
-#!/bin/sh -e
+#!/usr/bin/env bash
set -x
ruff check fastapi tests docs_src scripts --fix
diff --git a/scripts/mkdocs_hooks.py b/scripts/mkdocs_hooks.py
index 24ffecf46..0bc4929a4 100644
--- a/scripts/mkdocs_hooks.py
+++ b/scripts/mkdocs_hooks.py
@@ -8,9 +8,14 @@ from mkdocs.structure.files import File, Files
from mkdocs.structure.nav import Link, Navigation, Section
from mkdocs.structure.pages import Page
-non_traslated_sections = [
+non_translated_sections = [
"reference/",
"release-notes.md",
+ "fastapi-people.md",
+ "external-links.md",
+ "newsletter.md",
+ "management-tasks.md",
+ "management.md",
]
@@ -128,7 +133,7 @@ def on_page_markdown(
markdown: str, *, page: Page, config: MkDocsConfig, files: Files
) -> str:
if isinstance(page.file, EnFile):
- for excluded_section in non_traslated_sections:
+ for excluded_section in non_translated_sections:
if page.file.src_path.startswith(excluded_section):
return markdown
missing_translation_content = get_missing_translation_content(config.docs_dir)
diff --git a/scripts/playwright/request_form_models/image01.py b/scripts/playwright/request_form_models/image01.py
new file mode 100644
index 000000000..15bd3858c
--- /dev/null
+++ b/scripts/playwright/request_form_models/image01.py
@@ -0,0 +1,36 @@
+import subprocess
+import time
+
+import httpx
+from playwright.sync_api import Playwright, sync_playwright
+
+
+# Run playwright codegen to generate the code below, copy paste the sections in run()
+def run(playwright: Playwright) -> None:
+ browser = playwright.chromium.launch(headless=False)
+ context = browser.new_context()
+ page = context.new_page()
+ page.goto("http://localhost:8000/docs")
+ page.get_by_role("button", name="POST /login/ Login").click()
+ page.get_by_role("button", name="Try it out").click()
+ page.screenshot(path="docs/en/docs/img/tutorial/request-form-models/image01.png")
+
+ # ---------------------
+ context.close()
+ browser.close()
+
+
+process = subprocess.Popen(
+ ["fastapi", "run", "docs_src/request_form_models/tutorial001.py"]
+)
+try:
+ for _ in range(3):
+ try:
+ response = httpx.get("http://localhost:8000/docs")
+ except httpx.ConnectError:
+ time.sleep(1)
+ break
+ with sync_playwright() as playwright:
+ run(playwright)
+finally:
+ process.terminate()
diff --git a/scripts/test-cov-html.sh b/scripts/test-cov-html.sh
index d1bdfced2..517ac6422 100755
--- a/scripts/test-cov-html.sh
+++ b/scripts/test-cov-html.sh
@@ -5,5 +5,5 @@ set -x
bash scripts/test.sh ${@}
coverage combine
-coverage report --show-missing
+coverage report
coverage html
diff --git a/tests/test_allow_inf_nan_in_enforcing.py b/tests/test_allow_inf_nan_in_enforcing.py
new file mode 100644
index 000000000..9e855fdf8
--- /dev/null
+++ b/tests/test_allow_inf_nan_in_enforcing.py
@@ -0,0 +1,83 @@
+import pytest
+from fastapi import Body, FastAPI, Query
+from fastapi.testclient import TestClient
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+@app.post("/")
+async def get(
+ x: Annotated[float, Query(allow_inf_nan=True)] = 0,
+ y: Annotated[float, Query(allow_inf_nan=False)] = 0,
+ z: Annotated[float, Query()] = 0,
+ b: Annotated[float, Body(allow_inf_nan=False)] = 0,
+) -> str:
+ return "OK"
+
+
+client = TestClient(app)
+
+
+@pytest.mark.parametrize(
+ "value,code",
+ [
+ ("-1", 200),
+ ("inf", 200),
+ ("-inf", 200),
+ ("nan", 200),
+ ("0", 200),
+ ("342", 200),
+ ],
+)
+def test_allow_inf_nan_param_true(value: str, code: int):
+ response = client.post(f"/?x={value}")
+ assert response.status_code == code, response.text
+
+
+@pytest.mark.parametrize(
+ "value,code",
+ [
+ ("-1", 200),
+ ("inf", 422),
+ ("-inf", 422),
+ ("nan", 422),
+ ("0", 200),
+ ("342", 200),
+ ],
+)
+def test_allow_inf_nan_param_false(value: str, code: int):
+ response = client.post(f"/?y={value}")
+ assert response.status_code == code, response.text
+
+
+@pytest.mark.parametrize(
+ "value,code",
+ [
+ ("-1", 200),
+ ("inf", 200),
+ ("-inf", 200),
+ ("nan", 200),
+ ("0", 200),
+ ("342", 200),
+ ],
+)
+def test_allow_inf_nan_param_default(value: str, code: int):
+ response = client.post(f"/?z={value}")
+ assert response.status_code == code, response.text
+
+
+@pytest.mark.parametrize(
+ "value,code",
+ [
+ ("-1", 200),
+ ("inf", 422),
+ ("-inf", 422),
+ ("nan", 422),
+ ("0", 200),
+ ("342", 200),
+ ],
+)
+def test_allow_inf_nan_body(value: str, code: int):
+ response = client.post("/", json=value)
+ assert response.status_code == code, response.text
diff --git a/tests/test_compat.py b/tests/test_compat.py
index bf268b860..f4a3093c5 100644
--- a/tests/test_compat.py
+++ b/tests/test_compat.py
@@ -1,11 +1,14 @@
-from typing import List, Union
+from typing import Any, Dict, List, Union
from fastapi import FastAPI, UploadFile
from fastapi._compat import (
ModelField,
Undefined,
_get_model_config,
+ get_cached_model_fields,
+ get_model_fields,
is_bytes_sequence_annotation,
+ is_scalar_field,
is_uploadfile_sequence_annotation,
)
from fastapi.testclient import TestClient
@@ -91,3 +94,27 @@ def test_is_uploadfile_sequence_annotation():
# and other types, but I'm not even sure it's a good idea to support it as a first
# class "feature"
assert is_uploadfile_sequence_annotation(Union[List[str], List[UploadFile]])
+
+
+def test_is_pv1_scalar_field():
+ # For coverage
+ class Model(BaseModel):
+ foo: Union[str, Dict[str, Any]]
+
+ fields = get_model_fields(Model)
+ assert not is_scalar_field(fields[0])
+
+
+def test_get_model_fields_cached():
+ class Model(BaseModel):
+ foo: str
+
+ non_cached_fields = get_model_fields(Model)
+ non_cached_fields2 = get_model_fields(Model)
+ cached_fields = get_cached_model_fields(Model)
+ cached_fields2 = get_cached_model_fields(Model)
+ for f1, f2 in zip(cached_fields, cached_fields2):
+ assert f1 is f2
+
+ assert non_cached_fields is not non_cached_fields2
+ assert cached_fields is cached_fields2
diff --git a/tests/test_dependency_contextmanager.py b/tests/test_dependency_contextmanager.py
index 008dab7bc..039c423b9 100644
--- a/tests/test_dependency_contextmanager.py
+++ b/tests/test_dependency_contextmanager.py
@@ -196,9 +196,9 @@ async def get_sync_context_b_bg(
tasks: BackgroundTasks, state: dict = Depends(context_b)
):
async def bg(state: dict):
- state[
- "sync_bg"
- ] = f"sync_bg set - b: {state['context_b']} - a: {state['context_a']}"
+ state["sync_bg"] = (
+ f"sync_bg set - b: {state['context_b']} - a: {state['context_a']}"
+ )
tasks.add_task(bg, state)
return state
diff --git a/tests/test_forms_single_model.py b/tests/test_forms_single_model.py
new file mode 100644
index 000000000..7ed3ba3a2
--- /dev/null
+++ b/tests/test_forms_single_model.py
@@ -0,0 +1,129 @@
+from typing import List, Optional
+
+from dirty_equals import IsDict
+from fastapi import FastAPI, Form
+from fastapi.testclient import TestClient
+from pydantic import BaseModel
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+class FormModel(BaseModel):
+ username: str
+ lastname: str
+ age: Optional[int] = None
+ tags: List[str] = ["foo", "bar"]
+
+
+@app.post("/form/")
+def post_form(user: Annotated[FormModel, Form()]):
+ return user
+
+
+client = TestClient(app)
+
+
+def test_send_all_data():
+ response = client.post(
+ "/form/",
+ data={
+ "username": "Rick",
+ "lastname": "Sanchez",
+ "age": "70",
+ "tags": ["plumbus", "citadel"],
+ },
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "username": "Rick",
+ "lastname": "Sanchez",
+ "age": 70,
+ "tags": ["plumbus", "citadel"],
+ }
+
+
+def test_defaults():
+ response = client.post("/form/", data={"username": "Rick", "lastname": "Sanchez"})
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "username": "Rick",
+ "lastname": "Sanchez",
+ "age": None,
+ "tags": ["foo", "bar"],
+ }
+
+
+def test_invalid_data():
+ response = client.post(
+ "/form/",
+ data={
+ "username": "Rick",
+ "lastname": "Sanchez",
+ "age": "seventy",
+ "tags": ["plumbus", "citadel"],
+ },
+ )
+ assert response.status_code == 422, response.text
+ assert response.json() == IsDict(
+ {
+ "detail": [
+ {
+ "type": "int_parsing",
+ "loc": ["body", "age"],
+ "msg": "Input should be a valid integer, unable to parse string as an integer",
+ "input": "seventy",
+ }
+ ]
+ }
+ ) | IsDict(
+ # TODO: remove when deprecating Pydantic v1
+ {
+ "detail": [
+ {
+ "loc": ["body", "age"],
+ "msg": "value is not a valid integer",
+ "type": "type_error.integer",
+ }
+ ]
+ }
+ )
+
+
+def test_no_data():
+ response = client.post("/form/")
+ assert response.status_code == 422, response.text
+ assert response.json() == IsDict(
+ {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "username"],
+ "msg": "Field required",
+ "input": {"tags": ["foo", "bar"]},
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "lastname"],
+ "msg": "Field required",
+ "input": {"tags": ["foo", "bar"]},
+ },
+ ]
+ }
+ ) | IsDict(
+ # TODO: remove when deprecating Pydantic v1
+ {
+ "detail": [
+ {
+ "loc": ["body", "username"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ {
+ "loc": ["body", "lastname"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ ]
+ }
+ )
diff --git a/tests/test_forms_single_param.py b/tests/test_forms_single_param.py
new file mode 100644
index 000000000..3bb951441
--- /dev/null
+++ b/tests/test_forms_single_param.py
@@ -0,0 +1,99 @@
+from fastapi import FastAPI, Form
+from fastapi.testclient import TestClient
+from typing_extensions import Annotated
+
+app = FastAPI()
+
+
+@app.post("/form/")
+def post_form(username: Annotated[str, Form()]):
+ return username
+
+
+client = TestClient(app)
+
+
+def test_single_form_field():
+ response = client.post("/form/", data={"username": "Rick"})
+ assert response.status_code == 200, response.text
+ assert response.json() == "Rick"
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/form/": {
+ "post": {
+ "summary": "Post Form",
+ "operationId": "post_form_form__post",
+ "requestBody": {
+ "content": {
+ "application/x-www-form-urlencoded": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_post_form_form__post"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Body_post_form_form__post": {
+ "properties": {"username": {"type": "string", "title": "Username"}},
+ "type": "object",
+ "required": ["username"],
+ "title": "Body_post_form_form__post",
+ },
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ "type": "array",
+ "title": "Detail",
+ }
+ },
+ "type": "object",
+ "title": "HTTPValidationError",
+ },
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ "type": "array",
+ "title": "Location",
+ },
+ "msg": {"type": "string", "title": "Message"},
+ "type": {"type": "string", "title": "Error Type"},
+ },
+ "type": "object",
+ "required": ["loc", "msg", "type"],
+ "title": "ValidationError",
+ },
+ }
+ },
+ }
diff --git a/tests/test_inherited_custom_class.py b/tests/test_inherited_custom_class.py
index 42b249211..fe9350f4e 100644
--- a/tests/test_inherited_custom_class.py
+++ b/tests/test_inherited_custom_class.py
@@ -36,7 +36,7 @@ def test_pydanticv2():
def return_fast_uuid():
asyncpg_uuid = MyUuid("a10ff360-3b1e-4984-a26f-d3ab460bdb51")
assert isinstance(asyncpg_uuid, uuid.UUID)
- assert type(asyncpg_uuid) != uuid.UUID
+ assert type(asyncpg_uuid) is not uuid.UUID
with pytest.raises(TypeError):
vars(asyncpg_uuid)
return {"fast_uuid": asyncpg_uuid}
@@ -79,7 +79,7 @@ def test_pydanticv1():
def return_fast_uuid():
asyncpg_uuid = MyUuid("a10ff360-3b1e-4984-a26f-d3ab460bdb51")
assert isinstance(asyncpg_uuid, uuid.UUID)
- assert type(asyncpg_uuid) != uuid.UUID
+ assert type(asyncpg_uuid) is not uuid.UUID
with pytest.raises(TypeError):
vars(asyncpg_uuid)
return {"fast_uuid": asyncpg_uuid}
diff --git a/tests/test_openapi_examples.py b/tests/test_openapi_examples.py
index 6597e5058..b3f83ae23 100644
--- a/tests/test_openapi_examples.py
+++ b/tests/test_openapi_examples.py
@@ -155,13 +155,26 @@ def test_openapi_schema():
"requestBody": {
"content": {
"application/json": {
- "schema": {
- "allOf": [{"$ref": "#/components/schemas/Item"}],
- "title": "Item",
- "examples": [
- {"data": "Data in Body examples, example1"}
- ],
- },
+ "schema": IsDict(
+ {
+ "$ref": "#/components/schemas/Item",
+ "examples": [
+ {"data": "Data in Body examples, example1"}
+ ],
+ }
+ )
+ | IsDict(
+ {
+ # TODO: remove when deprecating Pydantic v1
+ "allOf": [
+ {"$ref": "#/components/schemas/Item"}
+ ],
+ "title": "Item",
+ "examples": [
+ {"data": "Data in Body examples, example1"}
+ ],
+ }
+ ),
"examples": {
"Example One": {
"summary": "Example One Summary",
diff --git a/tests/test_router_events.py b/tests/test_router_events.py
index 1b9de18ae..dd7ff3314 100644
--- a/tests/test_router_events.py
+++ b/tests/test_router_events.py
@@ -1,8 +1,8 @@
from contextlib import asynccontextmanager
-from typing import AsyncGenerator, Dict
+from typing import AsyncGenerator, Dict, Union
import pytest
-from fastapi import APIRouter, FastAPI
+from fastapi import APIRouter, FastAPI, Request
from fastapi.testclient import TestClient
from pydantic import BaseModel
@@ -109,3 +109,134 @@ def test_app_lifespan_state(state: State) -> None:
assert response.json() == {"message": "Hello World"}
assert state.app_startup is True
assert state.app_shutdown is True
+
+
+def test_router_nested_lifespan_state(state: State) -> None:
+ @asynccontextmanager
+ async def lifespan(app: FastAPI) -> AsyncGenerator[Dict[str, bool], None]:
+ state.app_startup = True
+ yield {"app": True}
+ state.app_shutdown = True
+
+ @asynccontextmanager
+ async def router_lifespan(app: FastAPI) -> AsyncGenerator[Dict[str, bool], None]:
+ state.router_startup = True
+ yield {"router": True}
+ state.router_shutdown = True
+
+ @asynccontextmanager
+ async def subrouter_lifespan(app: FastAPI) -> AsyncGenerator[Dict[str, bool], None]:
+ state.sub_router_startup = True
+ yield {"sub_router": True}
+ state.sub_router_shutdown = True
+
+ sub_router = APIRouter(lifespan=subrouter_lifespan)
+
+ router = APIRouter(lifespan=router_lifespan)
+ router.include_router(sub_router)
+
+ app = FastAPI(lifespan=lifespan)
+ app.include_router(router)
+
+ @app.get("/")
+ def main(request: Request) -> Dict[str, str]:
+ assert request.state.app
+ assert request.state.router
+ assert request.state.sub_router
+ return {"message": "Hello World"}
+
+ assert state.app_startup is False
+ assert state.router_startup is False
+ assert state.sub_router_startup is False
+ assert state.app_shutdown is False
+ assert state.router_shutdown is False
+ assert state.sub_router_shutdown is False
+
+ with TestClient(app) as client:
+ assert state.app_startup is True
+ assert state.router_startup is True
+ assert state.sub_router_startup is True
+ assert state.app_shutdown is False
+ assert state.router_shutdown is False
+ assert state.sub_router_shutdown is False
+ response = client.get("/")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"message": "Hello World"}
+
+ assert state.app_startup is True
+ assert state.router_startup is True
+ assert state.sub_router_startup is True
+ assert state.app_shutdown is True
+ assert state.router_shutdown is True
+ assert state.sub_router_shutdown is True
+
+
+def test_router_nested_lifespan_state_overriding_by_parent() -> None:
+ @asynccontextmanager
+ async def lifespan(
+ app: FastAPI,
+ ) -> AsyncGenerator[Dict[str, Union[str, bool]], None]:
+ yield {
+ "app_specific": True,
+ "overridden": "app",
+ }
+
+ @asynccontextmanager
+ async def router_lifespan(
+ app: FastAPI,
+ ) -> AsyncGenerator[Dict[str, Union[str, bool]], None]:
+ yield {
+ "router_specific": True,
+ "overridden": "router", # should override parent
+ }
+
+ router = APIRouter(lifespan=router_lifespan)
+ app = FastAPI(lifespan=lifespan)
+ app.include_router(router)
+
+ with TestClient(app) as client:
+ assert client.app_state == {
+ "app_specific": True,
+ "router_specific": True,
+ "overridden": "app",
+ }
+
+
+def test_merged_no_return_lifespans_return_none() -> None:
+ @asynccontextmanager
+ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
+ yield
+
+ @asynccontextmanager
+ async def router_lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
+ yield
+
+ router = APIRouter(lifespan=router_lifespan)
+ app = FastAPI(lifespan=lifespan)
+ app.include_router(router)
+
+ with TestClient(app) as client:
+ assert not client.app_state
+
+
+def test_merged_mixed_state_lifespans() -> None:
+ @asynccontextmanager
+ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
+ yield
+
+ @asynccontextmanager
+ async def router_lifespan(app: FastAPI) -> AsyncGenerator[Dict[str, bool], None]:
+ yield {"router": True}
+
+ @asynccontextmanager
+ async def sub_router_lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
+ yield
+
+ sub_router = APIRouter(lifespan=sub_router_lifespan)
+ router = APIRouter(lifespan=router_lifespan)
+ app = FastAPI(lifespan=lifespan)
+ router.include_router(sub_router)
+ app.include_router(router)
+
+ with TestClient(app) as client:
+ assert client.app_state == {"router": True}
diff --git a/tests/test_tutorial/test_request_form_models/__init__.py b/tests/test_tutorial/test_request_form_models/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/test_tutorial/test_request_form_models/test_tutorial001.py b/tests/test_tutorial/test_request_form_models/test_tutorial001.py
new file mode 100644
index 000000000..46c130ee8
--- /dev/null
+++ b/tests/test_tutorial/test_request_form_models/test_tutorial001.py
@@ -0,0 +1,232 @@
+import pytest
+from dirty_equals import IsDict
+from fastapi.testclient import TestClient
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.request_form_models.tutorial001 import app
+
+ client = TestClient(app)
+ return client
+
+
+def test_post_body_form(client: TestClient):
+ response = client.post("/login/", data={"username": "Foo", "password": "secret"})
+ assert response.status_code == 200
+ assert response.json() == {"username": "Foo", "password": "secret"}
+
+
+def test_post_body_form_no_password(client: TestClient):
+ response = client.post("/login/", data={"username": "Foo"})
+ assert response.status_code == 422
+ assert response.json() == IsDict(
+ {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "password"],
+ "msg": "Field required",
+ "input": {"username": "Foo"},
+ }
+ ]
+ }
+ ) | IsDict(
+ # TODO: remove when deprecating Pydantic v1
+ {
+ "detail": [
+ {
+ "loc": ["body", "password"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ }
+ ]
+ }
+ )
+
+
+def test_post_body_form_no_username(client: TestClient):
+ response = client.post("/login/", data={"password": "secret"})
+ assert response.status_code == 422
+ assert response.json() == IsDict(
+ {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "username"],
+ "msg": "Field required",
+ "input": {"password": "secret"},
+ }
+ ]
+ }
+ ) | IsDict(
+ # TODO: remove when deprecating Pydantic v1
+ {
+ "detail": [
+ {
+ "loc": ["body", "username"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ }
+ ]
+ }
+ )
+
+
+def test_post_body_form_no_data(client: TestClient):
+ response = client.post("/login/")
+ assert response.status_code == 422
+ assert response.json() == IsDict(
+ {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "username"],
+ "msg": "Field required",
+ "input": {},
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "password"],
+ "msg": "Field required",
+ "input": {},
+ },
+ ]
+ }
+ ) | IsDict(
+ # TODO: remove when deprecating Pydantic v1
+ {
+ "detail": [
+ {
+ "loc": ["body", "username"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ {
+ "loc": ["body", "password"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ ]
+ }
+ )
+
+
+def test_post_body_json(client: TestClient):
+ response = client.post("/login/", json={"username": "Foo", "password": "secret"})
+ assert response.status_code == 422, response.text
+ assert response.json() == IsDict(
+ {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "username"],
+ "msg": "Field required",
+ "input": {},
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "password"],
+ "msg": "Field required",
+ "input": {},
+ },
+ ]
+ }
+ ) | IsDict(
+ # TODO: remove when deprecating Pydantic v1
+ {
+ "detail": [
+ {
+ "loc": ["body", "username"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ {
+ "loc": ["body", "password"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ ]
+ }
+ )
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/login/": {
+ "post": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Login",
+ "operationId": "login_login__post",
+ "requestBody": {
+ "content": {
+ "application/x-www-form-urlencoded": {
+ "schema": {"$ref": "#/components/schemas/FormData"}
+ }
+ },
+ "required": True,
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "FormData": {
+ "properties": {
+ "username": {"type": "string", "title": "Username"},
+ "password": {"type": "string", "title": "Password"},
+ },
+ "type": "object",
+ "required": ["username", "password"],
+ "title": "FormData",
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_request_form_models/test_tutorial001_an.py b/tests/test_tutorial/test_request_form_models/test_tutorial001_an.py
new file mode 100644
index 000000000..4e14d89c8
--- /dev/null
+++ b/tests/test_tutorial/test_request_form_models/test_tutorial001_an.py
@@ -0,0 +1,232 @@
+import pytest
+from dirty_equals import IsDict
+from fastapi.testclient import TestClient
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.request_form_models.tutorial001_an import app
+
+ client = TestClient(app)
+ return client
+
+
+def test_post_body_form(client: TestClient):
+ response = client.post("/login/", data={"username": "Foo", "password": "secret"})
+ assert response.status_code == 200
+ assert response.json() == {"username": "Foo", "password": "secret"}
+
+
+def test_post_body_form_no_password(client: TestClient):
+ response = client.post("/login/", data={"username": "Foo"})
+ assert response.status_code == 422
+ assert response.json() == IsDict(
+ {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "password"],
+ "msg": "Field required",
+ "input": {"username": "Foo"},
+ }
+ ]
+ }
+ ) | IsDict(
+ # TODO: remove when deprecating Pydantic v1
+ {
+ "detail": [
+ {
+ "loc": ["body", "password"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ }
+ ]
+ }
+ )
+
+
+def test_post_body_form_no_username(client: TestClient):
+ response = client.post("/login/", data={"password": "secret"})
+ assert response.status_code == 422
+ assert response.json() == IsDict(
+ {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "username"],
+ "msg": "Field required",
+ "input": {"password": "secret"},
+ }
+ ]
+ }
+ ) | IsDict(
+ # TODO: remove when deprecating Pydantic v1
+ {
+ "detail": [
+ {
+ "loc": ["body", "username"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ }
+ ]
+ }
+ )
+
+
+def test_post_body_form_no_data(client: TestClient):
+ response = client.post("/login/")
+ assert response.status_code == 422
+ assert response.json() == IsDict(
+ {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "username"],
+ "msg": "Field required",
+ "input": {},
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "password"],
+ "msg": "Field required",
+ "input": {},
+ },
+ ]
+ }
+ ) | IsDict(
+ # TODO: remove when deprecating Pydantic v1
+ {
+ "detail": [
+ {
+ "loc": ["body", "username"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ {
+ "loc": ["body", "password"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ ]
+ }
+ )
+
+
+def test_post_body_json(client: TestClient):
+ response = client.post("/login/", json={"username": "Foo", "password": "secret"})
+ assert response.status_code == 422, response.text
+ assert response.json() == IsDict(
+ {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "username"],
+ "msg": "Field required",
+ "input": {},
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "password"],
+ "msg": "Field required",
+ "input": {},
+ },
+ ]
+ }
+ ) | IsDict(
+ # TODO: remove when deprecating Pydantic v1
+ {
+ "detail": [
+ {
+ "loc": ["body", "username"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ {
+ "loc": ["body", "password"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ ]
+ }
+ )
+
+
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/login/": {
+ "post": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Login",
+ "operationId": "login_login__post",
+ "requestBody": {
+ "content": {
+ "application/x-www-form-urlencoded": {
+ "schema": {"$ref": "#/components/schemas/FormData"}
+ }
+ },
+ "required": True,
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "FormData": {
+ "properties": {
+ "username": {"type": "string", "title": "Username"},
+ "password": {"type": "string", "title": "Password"},
+ },
+ "type": "object",
+ "required": ["username", "password"],
+ "title": "FormData",
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_request_form_models/test_tutorial001_an_py39.py b/tests/test_tutorial/test_request_form_models/test_tutorial001_an_py39.py
new file mode 100644
index 000000000..2e6426aa7
--- /dev/null
+++ b/tests/test_tutorial/test_request_form_models/test_tutorial001_an_py39.py
@@ -0,0 +1,240 @@
+import pytest
+from dirty_equals import IsDict
+from fastapi.testclient import TestClient
+
+from tests.utils import needs_py39
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.request_form_models.tutorial001_an_py39 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py39
+def test_post_body_form(client: TestClient):
+ response = client.post("/login/", data={"username": "Foo", "password": "secret"})
+ assert response.status_code == 200
+ assert response.json() == {"username": "Foo", "password": "secret"}
+
+
+@needs_py39
+def test_post_body_form_no_password(client: TestClient):
+ response = client.post("/login/", data={"username": "Foo"})
+ assert response.status_code == 422
+ assert response.json() == IsDict(
+ {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "password"],
+ "msg": "Field required",
+ "input": {"username": "Foo"},
+ }
+ ]
+ }
+ ) | IsDict(
+ # TODO: remove when deprecating Pydantic v1
+ {
+ "detail": [
+ {
+ "loc": ["body", "password"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ }
+ ]
+ }
+ )
+
+
+@needs_py39
+def test_post_body_form_no_username(client: TestClient):
+ response = client.post("/login/", data={"password": "secret"})
+ assert response.status_code == 422
+ assert response.json() == IsDict(
+ {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "username"],
+ "msg": "Field required",
+ "input": {"password": "secret"},
+ }
+ ]
+ }
+ ) | IsDict(
+ # TODO: remove when deprecating Pydantic v1
+ {
+ "detail": [
+ {
+ "loc": ["body", "username"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ }
+ ]
+ }
+ )
+
+
+@needs_py39
+def test_post_body_form_no_data(client: TestClient):
+ response = client.post("/login/")
+ assert response.status_code == 422
+ assert response.json() == IsDict(
+ {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "username"],
+ "msg": "Field required",
+ "input": {},
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "password"],
+ "msg": "Field required",
+ "input": {},
+ },
+ ]
+ }
+ ) | IsDict(
+ # TODO: remove when deprecating Pydantic v1
+ {
+ "detail": [
+ {
+ "loc": ["body", "username"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ {
+ "loc": ["body", "password"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ ]
+ }
+ )
+
+
+@needs_py39
+def test_post_body_json(client: TestClient):
+ response = client.post("/login/", json={"username": "Foo", "password": "secret"})
+ assert response.status_code == 422, response.text
+ assert response.json() == IsDict(
+ {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "username"],
+ "msg": "Field required",
+ "input": {},
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "password"],
+ "msg": "Field required",
+ "input": {},
+ },
+ ]
+ }
+ ) | IsDict(
+ # TODO: remove when deprecating Pydantic v1
+ {
+ "detail": [
+ {
+ "loc": ["body", "username"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ {
+ "loc": ["body", "password"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ ]
+ }
+ )
+
+
+@needs_py39
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/login/": {
+ "post": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Login",
+ "operationId": "login_login__post",
+ "requestBody": {
+ "content": {
+ "application/x-www-form-urlencoded": {
+ "schema": {"$ref": "#/components/schemas/FormData"}
+ }
+ },
+ "required": True,
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "FormData": {
+ "properties": {
+ "username": {"type": "string", "title": "Username"},
+ "password": {"type": "string", "title": "Password"},
+ },
+ "type": "object",
+ "required": ["username", "password"],
+ "title": "FormData",
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_request_form_models/test_tutorial002.py b/tests/test_tutorial/test_request_form_models/test_tutorial002.py
new file mode 100644
index 000000000..76f480001
--- /dev/null
+++ b/tests/test_tutorial/test_request_form_models/test_tutorial002.py
@@ -0,0 +1,196 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from tests.utils import needs_pydanticv2
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.request_form_models.tutorial002 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_pydanticv2
+def test_post_body_form(client: TestClient):
+ response = client.post("/login/", data={"username": "Foo", "password": "secret"})
+ assert response.status_code == 200
+ assert response.json() == {"username": "Foo", "password": "secret"}
+
+
+@needs_pydanticv2
+def test_post_body_extra_form(client: TestClient):
+ response = client.post(
+ "/login/", data={"username": "Foo", "password": "secret", "extra": "extra"}
+ )
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "extra_forbidden",
+ "loc": ["body", "extra"],
+ "msg": "Extra inputs are not permitted",
+ "input": "extra",
+ }
+ ]
+ }
+
+
+@needs_pydanticv2
+def test_post_body_form_no_password(client: TestClient):
+ response = client.post("/login/", data={"username": "Foo"})
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "password"],
+ "msg": "Field required",
+ "input": {"username": "Foo"},
+ }
+ ]
+ }
+
+
+@needs_pydanticv2
+def test_post_body_form_no_username(client: TestClient):
+ response = client.post("/login/", data={"password": "secret"})
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "username"],
+ "msg": "Field required",
+ "input": {"password": "secret"},
+ }
+ ]
+ }
+
+
+@needs_pydanticv2
+def test_post_body_form_no_data(client: TestClient):
+ response = client.post("/login/")
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "username"],
+ "msg": "Field required",
+ "input": {},
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "password"],
+ "msg": "Field required",
+ "input": {},
+ },
+ ]
+ }
+
+
+@needs_pydanticv2
+def test_post_body_json(client: TestClient):
+ response = client.post("/login/", json={"username": "Foo", "password": "secret"})
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "username"],
+ "msg": "Field required",
+ "input": {},
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "password"],
+ "msg": "Field required",
+ "input": {},
+ },
+ ]
+ }
+
+
+@needs_pydanticv2
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/login/": {
+ "post": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Login",
+ "operationId": "login_login__post",
+ "requestBody": {
+ "content": {
+ "application/x-www-form-urlencoded": {
+ "schema": {"$ref": "#/components/schemas/FormData"}
+ }
+ },
+ "required": True,
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "FormData": {
+ "properties": {
+ "username": {"type": "string", "title": "Username"},
+ "password": {"type": "string", "title": "Password"},
+ },
+ "additionalProperties": False,
+ "type": "object",
+ "required": ["username", "password"],
+ "title": "FormData",
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_request_form_models/test_tutorial002_an.py b/tests/test_tutorial/test_request_form_models/test_tutorial002_an.py
new file mode 100644
index 000000000..179b2977d
--- /dev/null
+++ b/tests/test_tutorial/test_request_form_models/test_tutorial002_an.py
@@ -0,0 +1,196 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from tests.utils import needs_pydanticv2
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.request_form_models.tutorial002_an import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_pydanticv2
+def test_post_body_form(client: TestClient):
+ response = client.post("/login/", data={"username": "Foo", "password": "secret"})
+ assert response.status_code == 200
+ assert response.json() == {"username": "Foo", "password": "secret"}
+
+
+@needs_pydanticv2
+def test_post_body_extra_form(client: TestClient):
+ response = client.post(
+ "/login/", data={"username": "Foo", "password": "secret", "extra": "extra"}
+ )
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "extra_forbidden",
+ "loc": ["body", "extra"],
+ "msg": "Extra inputs are not permitted",
+ "input": "extra",
+ }
+ ]
+ }
+
+
+@needs_pydanticv2
+def test_post_body_form_no_password(client: TestClient):
+ response = client.post("/login/", data={"username": "Foo"})
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "password"],
+ "msg": "Field required",
+ "input": {"username": "Foo"},
+ }
+ ]
+ }
+
+
+@needs_pydanticv2
+def test_post_body_form_no_username(client: TestClient):
+ response = client.post("/login/", data={"password": "secret"})
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "username"],
+ "msg": "Field required",
+ "input": {"password": "secret"},
+ }
+ ]
+ }
+
+
+@needs_pydanticv2
+def test_post_body_form_no_data(client: TestClient):
+ response = client.post("/login/")
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "username"],
+ "msg": "Field required",
+ "input": {},
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "password"],
+ "msg": "Field required",
+ "input": {},
+ },
+ ]
+ }
+
+
+@needs_pydanticv2
+def test_post_body_json(client: TestClient):
+ response = client.post("/login/", json={"username": "Foo", "password": "secret"})
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "username"],
+ "msg": "Field required",
+ "input": {},
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "password"],
+ "msg": "Field required",
+ "input": {},
+ },
+ ]
+ }
+
+
+@needs_pydanticv2
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/login/": {
+ "post": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Login",
+ "operationId": "login_login__post",
+ "requestBody": {
+ "content": {
+ "application/x-www-form-urlencoded": {
+ "schema": {"$ref": "#/components/schemas/FormData"}
+ }
+ },
+ "required": True,
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "FormData": {
+ "properties": {
+ "username": {"type": "string", "title": "Username"},
+ "password": {"type": "string", "title": "Password"},
+ },
+ "additionalProperties": False,
+ "type": "object",
+ "required": ["username", "password"],
+ "title": "FormData",
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_request_form_models/test_tutorial002_an_py39.py b/tests/test_tutorial/test_request_form_models/test_tutorial002_an_py39.py
new file mode 100644
index 000000000..510ad9d7c
--- /dev/null
+++ b/tests/test_tutorial/test_request_form_models/test_tutorial002_an_py39.py
@@ -0,0 +1,203 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from tests.utils import needs_py39, needs_pydanticv2
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.request_form_models.tutorial002_an_py39 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_pydanticv2
+@needs_py39
+def test_post_body_form(client: TestClient):
+ response = client.post("/login/", data={"username": "Foo", "password": "secret"})
+ assert response.status_code == 200
+ assert response.json() == {"username": "Foo", "password": "secret"}
+
+
+@needs_pydanticv2
+@needs_py39
+def test_post_body_extra_form(client: TestClient):
+ response = client.post(
+ "/login/", data={"username": "Foo", "password": "secret", "extra": "extra"}
+ )
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "extra_forbidden",
+ "loc": ["body", "extra"],
+ "msg": "Extra inputs are not permitted",
+ "input": "extra",
+ }
+ ]
+ }
+
+
+@needs_pydanticv2
+@needs_py39
+def test_post_body_form_no_password(client: TestClient):
+ response = client.post("/login/", data={"username": "Foo"})
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "password"],
+ "msg": "Field required",
+ "input": {"username": "Foo"},
+ }
+ ]
+ }
+
+
+@needs_pydanticv2
+@needs_py39
+def test_post_body_form_no_username(client: TestClient):
+ response = client.post("/login/", data={"password": "secret"})
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "username"],
+ "msg": "Field required",
+ "input": {"password": "secret"},
+ }
+ ]
+ }
+
+
+@needs_pydanticv2
+@needs_py39
+def test_post_body_form_no_data(client: TestClient):
+ response = client.post("/login/")
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "username"],
+ "msg": "Field required",
+ "input": {},
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "password"],
+ "msg": "Field required",
+ "input": {},
+ },
+ ]
+ }
+
+
+@needs_pydanticv2
+@needs_py39
+def test_post_body_json(client: TestClient):
+ response = client.post("/login/", json={"username": "Foo", "password": "secret"})
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "missing",
+ "loc": ["body", "username"],
+ "msg": "Field required",
+ "input": {},
+ },
+ {
+ "type": "missing",
+ "loc": ["body", "password"],
+ "msg": "Field required",
+ "input": {},
+ },
+ ]
+ }
+
+
+@needs_pydanticv2
+@needs_py39
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/login/": {
+ "post": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Login",
+ "operationId": "login_login__post",
+ "requestBody": {
+ "content": {
+ "application/x-www-form-urlencoded": {
+ "schema": {"$ref": "#/components/schemas/FormData"}
+ }
+ },
+ "required": True,
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "FormData": {
+ "properties": {
+ "username": {"type": "string", "title": "Username"},
+ "password": {"type": "string", "title": "Password"},
+ },
+ "additionalProperties": False,
+ "type": "object",
+ "required": ["username", "password"],
+ "title": "FormData",
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1.py b/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1.py
new file mode 100644
index 000000000..249b9379d
--- /dev/null
+++ b/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1.py
@@ -0,0 +1,189 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from tests.utils import needs_pydanticv1
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.request_form_models.tutorial002_pv1 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_pydanticv1
+def test_post_body_form(client: TestClient):
+ response = client.post("/login/", data={"username": "Foo", "password": "secret"})
+ assert response.status_code == 200
+ assert response.json() == {"username": "Foo", "password": "secret"}
+
+
+@needs_pydanticv1
+def test_post_body_extra_form(client: TestClient):
+ response = client.post(
+ "/login/", data={"username": "Foo", "password": "secret", "extra": "extra"}
+ )
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "value_error.extra",
+ "loc": ["body", "extra"],
+ "msg": "extra fields not permitted",
+ }
+ ]
+ }
+
+
+@needs_pydanticv1
+def test_post_body_form_no_password(client: TestClient):
+ response = client.post("/login/", data={"username": "Foo"})
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "value_error.missing",
+ "loc": ["body", "password"],
+ "msg": "field required",
+ }
+ ]
+ }
+
+
+@needs_pydanticv1
+def test_post_body_form_no_username(client: TestClient):
+ response = client.post("/login/", data={"password": "secret"})
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "value_error.missing",
+ "loc": ["body", "username"],
+ "msg": "field required",
+ }
+ ]
+ }
+
+
+@needs_pydanticv1
+def test_post_body_form_no_data(client: TestClient):
+ response = client.post("/login/")
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "value_error.missing",
+ "loc": ["body", "username"],
+ "msg": "field required",
+ },
+ {
+ "type": "value_error.missing",
+ "loc": ["body", "password"],
+ "msg": "field required",
+ },
+ ]
+ }
+
+
+@needs_pydanticv1
+def test_post_body_json(client: TestClient):
+ response = client.post("/login/", json={"username": "Foo", "password": "secret"})
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "value_error.missing",
+ "loc": ["body", "username"],
+ "msg": "field required",
+ },
+ {
+ "type": "value_error.missing",
+ "loc": ["body", "password"],
+ "msg": "field required",
+ },
+ ]
+ }
+
+
+@needs_pydanticv1
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/login/": {
+ "post": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Login",
+ "operationId": "login_login__post",
+ "requestBody": {
+ "content": {
+ "application/x-www-form-urlencoded": {
+ "schema": {"$ref": "#/components/schemas/FormData"}
+ }
+ },
+ "required": True,
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "FormData": {
+ "properties": {
+ "username": {"type": "string", "title": "Username"},
+ "password": {"type": "string", "title": "Password"},
+ },
+ "additionalProperties": False,
+ "type": "object",
+ "required": ["username", "password"],
+ "title": "FormData",
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1_an.py b/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1_an.py
new file mode 100644
index 000000000..44cb3c32b
--- /dev/null
+++ b/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1_an.py
@@ -0,0 +1,196 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from tests.utils import needs_pydanticv1
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.request_form_models.tutorial002_pv1_an import app
+
+ client = TestClient(app)
+ return client
+
+
+# TODO: remove when deprecating Pydantic v1
+@needs_pydanticv1
+def test_post_body_form(client: TestClient):
+ response = client.post("/login/", data={"username": "Foo", "password": "secret"})
+ assert response.status_code == 200
+ assert response.json() == {"username": "Foo", "password": "secret"}
+
+
+# TODO: remove when deprecating Pydantic v1
+@needs_pydanticv1
+def test_post_body_extra_form(client: TestClient):
+ response = client.post(
+ "/login/", data={"username": "Foo", "password": "secret", "extra": "extra"}
+ )
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "value_error.extra",
+ "loc": ["body", "extra"],
+ "msg": "extra fields not permitted",
+ }
+ ]
+ }
+
+
+# TODO: remove when deprecating Pydantic v1
+@needs_pydanticv1
+def test_post_body_form_no_password(client: TestClient):
+ response = client.post("/login/", data={"username": "Foo"})
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "value_error.missing",
+ "loc": ["body", "password"],
+ "msg": "field required",
+ }
+ ]
+ }
+
+
+# TODO: remove when deprecating Pydantic v1
+@needs_pydanticv1
+def test_post_body_form_no_username(client: TestClient):
+ response = client.post("/login/", data={"password": "secret"})
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "value_error.missing",
+ "loc": ["body", "username"],
+ "msg": "field required",
+ }
+ ]
+ }
+
+
+# TODO: remove when deprecating Pydantic v1
+@needs_pydanticv1
+def test_post_body_form_no_data(client: TestClient):
+ response = client.post("/login/")
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "value_error.missing",
+ "loc": ["body", "username"],
+ "msg": "field required",
+ },
+ {
+ "type": "value_error.missing",
+ "loc": ["body", "password"],
+ "msg": "field required",
+ },
+ ]
+ }
+
+
+# TODO: remove when deprecating Pydantic v1
+@needs_pydanticv1
+def test_post_body_json(client: TestClient):
+ response = client.post("/login/", json={"username": "Foo", "password": "secret"})
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "value_error.missing",
+ "loc": ["body", "username"],
+ "msg": "field required",
+ },
+ {
+ "type": "value_error.missing",
+ "loc": ["body", "password"],
+ "msg": "field required",
+ },
+ ]
+ }
+
+
+# TODO: remove when deprecating Pydantic v1
+@needs_pydanticv1
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/login/": {
+ "post": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Login",
+ "operationId": "login_login__post",
+ "requestBody": {
+ "content": {
+ "application/x-www-form-urlencoded": {
+ "schema": {"$ref": "#/components/schemas/FormData"}
+ }
+ },
+ "required": True,
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "FormData": {
+ "properties": {
+ "username": {"type": "string", "title": "Username"},
+ "password": {"type": "string", "title": "Password"},
+ },
+ "additionalProperties": False,
+ "type": "object",
+ "required": ["username", "password"],
+ "title": "FormData",
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }
diff --git a/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1_an_p39.py b/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1_an_p39.py
new file mode 100644
index 000000000..899549e40
--- /dev/null
+++ b/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1_an_p39.py
@@ -0,0 +1,203 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from tests.utils import needs_py39, needs_pydanticv1
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.request_form_models.tutorial002_pv1_an_py39 import app
+
+ client = TestClient(app)
+ return client
+
+
+# TODO: remove when deprecating Pydantic v1
+@needs_pydanticv1
+@needs_py39
+def test_post_body_form(client: TestClient):
+ response = client.post("/login/", data={"username": "Foo", "password": "secret"})
+ assert response.status_code == 200
+ assert response.json() == {"username": "Foo", "password": "secret"}
+
+
+# TODO: remove when deprecating Pydantic v1
+@needs_pydanticv1
+@needs_py39
+def test_post_body_extra_form(client: TestClient):
+ response = client.post(
+ "/login/", data={"username": "Foo", "password": "secret", "extra": "extra"}
+ )
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "value_error.extra",
+ "loc": ["body", "extra"],
+ "msg": "extra fields not permitted",
+ }
+ ]
+ }
+
+
+# TODO: remove when deprecating Pydantic v1
+@needs_pydanticv1
+@needs_py39
+def test_post_body_form_no_password(client: TestClient):
+ response = client.post("/login/", data={"username": "Foo"})
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "value_error.missing",
+ "loc": ["body", "password"],
+ "msg": "field required",
+ }
+ ]
+ }
+
+
+# TODO: remove when deprecating Pydantic v1
+@needs_pydanticv1
+@needs_py39
+def test_post_body_form_no_username(client: TestClient):
+ response = client.post("/login/", data={"password": "secret"})
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "value_error.missing",
+ "loc": ["body", "username"],
+ "msg": "field required",
+ }
+ ]
+ }
+
+
+# TODO: remove when deprecating Pydantic v1
+@needs_pydanticv1
+@needs_py39
+def test_post_body_form_no_data(client: TestClient):
+ response = client.post("/login/")
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "value_error.missing",
+ "loc": ["body", "username"],
+ "msg": "field required",
+ },
+ {
+ "type": "value_error.missing",
+ "loc": ["body", "password"],
+ "msg": "field required",
+ },
+ ]
+ }
+
+
+# TODO: remove when deprecating Pydantic v1
+@needs_pydanticv1
+@needs_py39
+def test_post_body_json(client: TestClient):
+ response = client.post("/login/", json={"username": "Foo", "password": "secret"})
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "type": "value_error.missing",
+ "loc": ["body", "username"],
+ "msg": "field required",
+ },
+ {
+ "type": "value_error.missing",
+ "loc": ["body", "password"],
+ "msg": "field required",
+ },
+ ]
+ }
+
+
+# TODO: remove when deprecating Pydantic v1
+@needs_pydanticv1
+@needs_py39
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/login/": {
+ "post": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Login",
+ "operationId": "login_login__post",
+ "requestBody": {
+ "content": {
+ "application/x-www-form-urlencoded": {
+ "schema": {"$ref": "#/components/schemas/FormData"}
+ }
+ },
+ "required": True,
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "FormData": {
+ "properties": {
+ "username": {"type": "string", "title": "Username"},
+ "password": {"type": "string", "title": "Password"},
+ },
+ "additionalProperties": False,
+ "type": "object",
+ "required": ["username", "password"],
+ "title": "FormData",
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "anyOf": [{"type": "string"}, {"type": "integer"}]
+ },
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+ }