diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 38df75928..bf88d59b1 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.12.3
+ uses: pypa/gh-action-pypi-publish@v1.12.4
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md
index 933b7271e..f25648b9a 100644
--- a/docs/en/docs/release-notes.md
+++ b/docs/en/docs/release-notes.md
@@ -7,8 +7,30 @@ hide:
## Latest Changes
+### Translations
+
+* π Update Russian translation for `docs/ru/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md`. PR [#13252](https://github.com/fastapi/fastapi/pull/13252) by [@Rishat-F](https://github.com/Rishat-F).
+* π Add Russian translation for `docs/ru/docs/tutorial/bigger-applications.md`. PR [#13154](https://github.com/fastapi/fastapi/pull/13154) by [@alv2017](https://github.com/alv2017).
+
+### Internal
+
+* β¬ Bump pypa/gh-action-pypi-publish from 1.12.3 to 1.12.4. PR [#13251](https://github.com/fastapi/fastapi/pull/13251) by [@dependabot[bot]](https://github.com/apps/dependabot).
+
+## 0.115.7
+
+### Upgrades
+
+* β¬οΈ Upgrade `python-multipart` to >=0.0.18. PR [#13219](https://github.com/fastapi/fastapi/pull/13219) by [@DanielKusyDev](https://github.com/DanielKusyDev).
+* β¬οΈ Bump Starlette to allow up to 0.45.0: `>=0.40.0,<0.46.0`. PR [#13117](https://github.com/fastapi/fastapi/pull/13117) by [@Kludex](https://github.com/Kludex).
+* β¬οΈ Upgrade `jinja2` to >=3.1.5. PR [#13194](https://github.com/fastapi/fastapi/pull/13194) by [@DanielKusyDev](https://github.com/DanielKusyDev).
+
### Refactors
+* β
Simplify tests for websockets. PR [#13202](https://github.com/fastapi/fastapi/pull/13202) by [@alejsdev](https://github.com/alejsdev).
+* β
Simplify tests for request_form_models . PR [#13183](https://github.com/fastapi/fastapi/pull/13183) by [@alejsdev](https://github.com/alejsdev).
+* β
Simplify tests for separate_openapi_schemas. PR [#13201](https://github.com/fastapi/fastapi/pull/13201) by [@alejsdev](https://github.com/alejsdev).
+* β
Simplify tests for security. PR [#13200](https://github.com/fastapi/fastapi/pull/13200) by [@alejsdev](https://github.com/alejsdev).
+* β
Simplify tests for schema_extra_example. PR [#13197](https://github.com/fastapi/fastapi/pull/13197) by [@alejsdev](https://github.com/alejsdev).
* β
Simplify tests for request_model. PR [#13195](https://github.com/fastapi/fastapi/pull/13195) by [@alejsdev](https://github.com/alejsdev).
* β
Simplify tests for request_forms_and_files. PR [#13185](https://github.com/fastapi/fastapi/pull/13185) by [@alejsdev](https://github.com/alejsdev).
* β
Simplify tests for request_forms. PR [#13184](https://github.com/fastapi/fastapi/pull/13184) by [@alejsdev](https://github.com/alejsdev).
@@ -44,6 +66,7 @@ hide:
### Translations
+* π Update Portuguese Translation for `docs/pt/docs/tutorial/request-forms.md`. PR [#13216](https://github.com/fastapi/fastapi/pull/13216) by [@Joao-Pedro-P-Holanda](https://github.com/Joao-Pedro-P-Holanda).
* π Update Portuguese translation for `docs/pt/docs/advanced/settings.md`. PR [#13209](https://github.com/fastapi/fastapi/pull/13209) by [@ceb10n](https://github.com/ceb10n).
* π Add Portuguese translation for `docs/pt/docs/tutorial/security/oauth2-jwt.md`. PR [#13205](https://github.com/fastapi/fastapi/pull/13205) by [@ceb10n](https://github.com/ceb10n).
* π Add Indonesian translation for `docs/id/docs/index.md`. PR [#13191](https://github.com/fastapi/fastapi/pull/13191) by [@gerry-sabar](https://github.com/gerry-sabar).
@@ -98,6 +121,7 @@ hide:
### Internal
+* π§ Add Pydantic 2 trove classifier. PR [#13199](https://github.com/fastapi/fastapi/pull/13199) by [@johnthagen](https://github.com/johnthagen).
* π₯ Update FastAPI People - Sponsors. PR [#13231](https://github.com/fastapi/fastapi/pull/13231) by [@tiangolo](https://github.com/tiangolo).
* π· Refactor FastAPI People Sponsors to use 2 tokens. PR [#13228](https://github.com/fastapi/fastapi/pull/13228) by [@tiangolo](https://github.com/tiangolo).
* π· Update token for FastAPI People - Sponsors. PR [#13225](https://github.com/fastapi/fastapi/pull/13225) by [@tiangolo](https://github.com/tiangolo).
diff --git a/docs/pt/docs/tutorial/request-forms.md b/docs/pt/docs/tutorial/request-forms.md
index 756ceb581..572ddf003 100644
--- a/docs/pt/docs/tutorial/request-forms.md
+++ b/docs/pt/docs/tutorial/request-forms.md
@@ -6,7 +6,11 @@ Quando vocΓͺ precisar receber campos de formulΓ‘rio ao invΓ©s de JSON, vocΓͺ pod
Para usar formulΓ‘rios, primeiro instale `python-multipart`.
-Ex: `pip install python-multipart`.
+Lembre-se de criar um [ambiente virtual](../virtual-environments.md){.internal-link target=_blank}, ativΓ‘-lo e entΓ£o instalar a dependΓͺncia, por exemplo:
+
+```console
+$ pip install python-multipart
+```
///
diff --git a/docs/ru/docs/tutorial/bigger-applications.md b/docs/ru/docs/tutorial/bigger-applications.md
new file mode 100644
index 000000000..7c3dc288f
--- /dev/null
+++ b/docs/ru/docs/tutorial/bigger-applications.md
@@ -0,0 +1,556 @@
+# ΠΠΎΠ»ΡΡΠΈΠ΅ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ, Π² ΠΊΠΎΡΠΎΡΡΡ
ΠΌΠ½ΠΎΠ³ΠΎ ΡΠ°ΠΉΠ»ΠΎΠ²
+
+ΠΡΠΈ ΠΏΠΎΡΡΡΠΎΠ΅Π½ΠΈΠΈ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ ΠΈΠ»ΠΈ Π²Π΅Π±-API Π½Π°ΠΌ ΡΠ΅Π΄ΠΊΠΎ ΡΠ΄Π°Π΅ΡΡΡ ΠΏΠΎΠΌΠ΅ΡΡΠΈΡΡ Π²ΡΡ Π² ΠΎΠ΄ΠΈΠ½ ΡΠ°ΠΉΠ».
+
+**FastAPI** ΠΏΡΠ΅Π΄ΠΎΡΡΠ°Π²Π»ΡΠ΅Ρ ΡΠ΄ΠΎΠ±Π½ΡΠΉ ΠΈΠ½ΡΡΡΡΠΌΠ΅Π½ΡΠ°ΡΠΈΠΉ, ΠΊΠΎΡΠΎΡΡΠΉ ΠΏΠΎΠ·Π²ΠΎΠ»ΡΠ΅Ρ Π½Π°ΠΌ ΡΡΡΡΠΊΡΡΡΠΈΡΠΎΠ²Π°ΡΡ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅, ΡΠΎΡ
ΡΠ°Π½ΡΡ ΠΏΡΠΈ ΡΡΠΎΠΌ Π²ΡΡ Π½Π΅ΠΎΠ±Ρ
ΠΎΠ΄ΠΈΠΌΡΡ Π³ΠΈΠ±ΠΊΠΎΡΡΡ.
+
+/// info | ΠΡΠΈΠΌΠ΅ΡΠ°Π½ΠΈΠ΅
+
+ΠΡΠ»ΠΈ Π²Ρ ΡΠ°Π½ΡΡΠ΅ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π»ΠΈ Flask, ΡΠΎ ΡΡΠΎ Π°Π½Π°Π»ΠΎΠ³ ΡΠ°Π±Π»ΠΎΠ½ΠΎΠ² Flask (Flask's Blueprints).
+
+///
+
+## ΠΡΠΈΠΌΠ΅Ρ ΡΡΡΡΠΊΡΡΡΡ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ
+
+ΠΠ°Π²Π°ΠΉΡΠ΅ ΠΏΡΠ΅Π΄ΠΏΠΎΠ»ΠΎΠΆΠΈΠΌ, ΡΡΠΎ Π½Π°ΡΠ΅ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ ΠΈΠΌΠ΅Π΅Ρ ΡΠ»Π΅Π΄ΡΡΡΡΡ ΡΡΡΡΠΊΡΡΡΡ:
+
+```
+.
+βββ app
+βΒ Β βββ __init__.py
+βΒ Β βββ main.py
+βΒ Β βββ dependencies.py
+βΒ Β βββ routers
+βΒ Β β βββ __init__.py
+βΒ Β β βββ items.py
+βΒ Β β βββ users.py
+βΒ Β βββ internal
+βΒ Β βββ __init__.py
+βΒ Β βββ admin.py
+```
+
+/// tip | ΠΠΎΠ΄ΡΠΊΠ°Π·ΠΊΠ°
+
+ΠΠ±ΡΠ°ΡΠΈΡΠ΅ Π²Π½ΠΈΠΌΠ°Π½ΠΈΠ΅, ΡΡΠΎ Π² ΠΊΠ°ΠΆΠ΄ΠΎΠΌ ΠΊΠ°ΡΠ°Π»ΠΎΠ³Π΅ ΠΈ ΠΏΠΎΠ΄ΠΊΠ°ΡΠ°Π»ΠΎΠ³Π΅ ΠΈΠΌΠ΅Π΅ΡΡΡ ΡΠ°ΠΉΠ» `__init__.py`
+
+ΠΡΠΎ ΠΊΠ°ΠΊ ΡΠ°Π· ΡΠΎ, ΡΡΠΎ ΠΏΠΎΠ·Π²ΠΎΠ»ΡΠ΅Ρ ΠΈΠΌΠΏΠΎΡΡΠΈΡΠΎΠ²Π°ΡΡ ΠΊΠΎΠ΄ ΠΈΠ· ΠΎΠ΄Π½ΠΎΠ³ΠΎ ΡΠ°ΠΉΠ»Π° Π² Π΄ΡΡΠ³ΠΎΠΉ.
+
+ΠΠ°ΠΏΡΠΈΠΌΠ΅Ρ, Π² ΡΠ°ΠΉΠ»Π΅ `app/main.py` ΠΌΠΎΠΆΠ΅Ρ Π±ΡΡΡ ΡΠ»Π΅Π΄ΡΡΡΠ°Ρ ΡΡΡΠΎΠΊΠ°:
+
+```
+from app.routers import items
+```
+
+///
+
+* ΠΡΡ ΠΏΠΎΠΌΠ΅ΡΠ°Π΅ΡΡΡ Π² ΠΊΠ°ΡΠ°Π»ΠΎΠ³Π΅ `app`. Π Π½ΡΠΌ ΡΠ°ΠΊΠΆΠ΅ Π½Π°Ρ
ΠΎΠ΄ΠΈΡΡΡ ΠΏΡΡΡΠΎΠΉ ΡΠ°ΠΉΠ» `app/__init__.py`. Π’Π°ΠΊΠΈΠΌ ΠΎΠ±ΡΠ°Π·ΠΎΠΌ, `app` ΡΠ²Π»ΡΠ΅ΡΡΡ "Python-ΠΏΠ°ΠΊΠ΅ΡΠΎΠΌ" (ΠΊΠΎΠ»Π»Π΅ΠΊΡΠΈΠ΅ΠΉ ΠΌΠΎΠ΄ΡΠ»Π΅ΠΉ Python).
+* ΠΠ½ ΡΠΎΠ΄Π΅ΡΠΆΠΈΡ ΡΠ°ΠΉΠ» `app/main.py`. ΠΠ°Π½Π½ΡΠΉ ΡΠ°ΠΉΠ» ΡΠ²Π»ΡΠ΅ΡΡΡ ΡΠ°ΡΡΡΡ ΠΏΠ°ΠΊΠ΅ΡΠ° (Ρ.Π΅. Π½Π°Ρ
ΠΎΠ΄ΠΈΡΡΡ Π²Π½ΡΡΡΠΈ ΠΊΠ°ΡΠ°Π»ΠΎΠ³Π°, ΡΠΎΠ΄Π΅ΡΠΆΠ°ΡΠ΅Π³ΠΎ ΡΠ°ΠΉΠ» `__init__.py`), ΠΈ, ΡΠΎΠΎΡΠ²Π΅ΡΡΡΠ²Π΅Π½Π½ΠΎ, ΠΎΠ½ ΡΠ²Π»ΡΠ΅ΡΡΡ ΠΌΠΎΠ΄ΡΠ»Π΅ΠΌ ΠΏΠ°ΠΊΠ΅ΡΠ°: `app.main`.
+* ΠΠ½ ΡΠ°ΠΊΠΆΠ΅ ΡΠΎΠ΄Π΅ΡΠΆΠΈΡ ΡΠ°ΠΉΠ» `app/dependencies.py`, ΠΊΠΎΡΠΎΡΡΠΉ ΡΠ°ΠΊΠΆΠ΅, ΠΊΠ°ΠΊ ΠΈ `app/main.py`, ΡΠ²Π»ΡΠ΅ΡΡΡ ΠΌΠΎΠ΄ΡΠ»Π΅ΠΌ: `app.dependencies`.
+* ΠΠ΄Π΅ΡΡ ΡΠ°ΠΊΠΆΠ΅ Π½Π°Ρ
ΠΎΠ΄ΠΈΡΡΡ ΠΏΠΎΠ΄ΠΊΠ°ΡΠ°Π»ΠΎΠ³ `app/routers/`, ΡΠΎΠ΄Π΅ΡΠΆΠ°ΡΠΈΠΉ `__init__.py`. ΠΠ½ ΡΠ²Π»ΡΠ΅ΡΡΡ ΡΡΠ±-ΠΏΠ°ΠΊΠ΅ΡΠΎΠΌ: `app.routers`.
+* Π€Π°ΠΉΠ» `app/routers/items.py` Π½Π°Ρ
ΠΎΠ΄ΠΈΡΡΡ Π²Π½ΡΡΡΠΈ ΠΏΠ°ΠΊΠ΅ΡΠ° `app/routers/`. Π’Π°ΠΊΠΈΠΌ ΠΎΠ±ΡΠ°Π·ΠΎΠΌ, ΠΎΠ½ ΡΠ²Π»ΡΠ΅ΡΡΡ ΡΡΠ±-ΠΌΠΎΠ΄ΡΠ»Π΅ΠΌ: `app.routers.items`.
+* Π’ΠΎΡΠ½ΠΎ ΡΠ°ΠΊΠΆΠ΅ `app/routers/users.py` ΡΠ²Π»ΡΠ΅ΡΡΡ Π΅ΡΡ ΠΎΠ΄Π½ΠΈΠΌ ΡΡΠ±-ΠΌΠΎΠ΄ΡΠ»Π΅ΠΌ: `app.routers.users`.
+* ΠΠΎΠ΄ΠΊΠ°ΡΠ°Π»ΠΎΠ³ `app/internal/`, ΡΠΎΠ΄Π΅ΡΠΆΠ°ΡΠΈΠΉ ΡΠ°ΠΉΠ» `__init__.py`, ΡΠ²Π»ΡΠ΅ΡΡΡ Π΅ΡΡ ΠΎΠ΄Π½ΠΈΠΌ ΡΡΠ±-ΠΏΠ°ΠΊΠ΅ΡΠΎΠΌ: `app.internal`.
+* Π ΡΠ°ΠΉΠ» `app/internal/admin.py` ΡΠ²Π»ΡΠ΅ΡΡΡ Π΅ΡΡ ΠΎΠ΄Π½ΠΈΠΌ ΡΡΠ±-ΠΌΠΎΠ΄ΡΠ»Π΅ΠΌ: `app.internal.admin`.
+
+
+
+Π’Π° ΠΆΠ΅ ΡΠ°ΠΌΠ°Ρ ΡΠ°ΠΉΠ»ΠΎΠ²Π°Ρ ΡΡΡΡΠΊΡΡΡΠ° ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ, Π½ΠΎ Ρ ΠΊΠΎΠΌΠΌΠ΅Π½ΡΠ°ΡΠΈΡΠΌΠΈ:
+
+```
+.
+βββ app # "app" ΠΏΠ°ΠΊΠ΅Ρ
+βΒ Β βββ __init__.py # ΡΡΠΎΡ ΡΠ°ΠΉΠ» ΠΏΡΠ΅Π²ΡΠ°ΡΠ°Π΅Ρ "app" Π² "Python-ΠΏΠ°ΠΊΠ΅Ρ"
+βΒ Β βββ main.py # ΠΌΠΎΠ΄ΡΠ»Ρ "main", Π½Π°ΠΏΡ.: import app.main
+βΒ Β βββ dependencies.py # ΠΌΠΎΠ΄ΡΠ»Ρ "dependencies", Π½Π°ΠΏΡ.: import app.dependencies
+βΒ Β βββ routers # ΡΡΠ±-ΠΏΠ°ΠΊΠ΅Ρ "routers"
+βΒ Β β βββ __init__.py # ΠΏΡΠ΅Π²ΡΠ°ΡΠ°Π΅Ρ "routers" Π² ΡΡΠ±-ΠΏΠ°ΠΊΠ΅Ρ
+βΒ Β β βββ items.py # ΡΡΠ±-ΠΌΠΎΠ΄ΡΠ»Ρ "items", Π½Π°ΠΏΡ.: import app.routers.items
+βΒ Β β βββ users.py # ΡΡΠ±-ΠΌΠΎΠ΄ΡΠ»Ρ "users", Π½Π°ΠΏΡ.: import app.routers.users
+βΒ Β βββ internal # ΡΡΠ±-ΠΏΠ°ΠΊΠ΅Ρ "internal"
+βΒ Β βββ __init__.py # ΠΏΡΠ΅Π²ΡΠ°ΡΠ°Π΅Ρ "internal" Π² ΡΡΠ±-ΠΏΠ°ΠΊΠ΅Ρ
+βΒ Β βββ admin.py # ΡΡΠ±-ΠΌΠΎΠ΄ΡΠ»Ρ "admin", Π½Π°ΠΏΡ.: import app.internal.admin
+```
+
+## `APIRouter`
+
+ΠΠ°Π²Π°ΠΉΡΠ΅ ΠΏΡΠ΅Π΄ΠΏΠΎΠ»ΠΎΠΆΠΈΠΌ, ΡΡΠΎ Π΄Π»Ρ ΡΠ°Π±ΠΎΡΡ Ρ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»ΡΠΌΠΈ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΡΡΡ ΠΎΡΠ΄Π΅Π»ΡΠ½ΡΠΉ ΡΠ°ΠΉΠ» (ΡΡΠ±-ΠΌΠΎΠ΄ΡΠ»Ρ) `/app/routers/users.py`.
+
+ΠΠ»Ρ Π»ΡΡΡΠ΅ΠΉ ΠΎΡΠ³Π°Π½ΠΈΠ·Π°ΡΠΈΠΈ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ, Π²Ρ Ρ
ΠΎΡΠΈΡΠ΅ ΠΎΡΠ΄Π΅Π»ΠΈΡΡ ΠΎΠΏΠ΅ΡΠ°ΡΠΈΠΈ ΠΏΡΡΠΈ, ΡΠ²ΡΠ·Π°Π½Π½ΡΠ΅ Ρ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»ΡΠΌΠΈ, ΠΎΡ ΠΎΡΡΠ°Π»ΡΠ½ΠΎΠ³ΠΎ ΠΊΠΎΠ΄Π°.
+
+ΠΠΎ ΡΠ°ΠΊ, ΡΡΠΎΠ±Ρ ΡΡΠΈ ΠΎΠΏΠ΅ΡΠ°ΡΠΈΠΈ ΠΏΠΎ-ΠΏΡΠ΅ΠΆΠ½Π΅ΠΌΡ ΠΎΡΡΠ°Π²Π°Π»ΠΈΡΡ ΡΠ°ΡΡΡΡ **FastAPI** ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ/Π²Π΅Π±-API (ΡΠ°ΡΡΡΡ ΠΎΠ΄Π½ΠΎΠ³ΠΎ ΠΏΠ°ΠΊΠ΅ΡΠ°)
+
+Π‘ ΠΏΠΎΠΌΠΎΡΡΡ `APIRouter` Π²Ρ ΠΌΠΎΠΆΠ΅ΡΠ΅ ΡΠΎΠ·Π΄Π°ΡΡ *ΠΎΠΏΠ΅ΡΠ°ΡΠΈΠΈ ΠΏΡΡΠΈ* (*ΡΠ½Π΄ΠΏΠΎΠΈΠ½ΡΡ*) Π΄Π»Ρ Π΄Π°Π½Π½ΠΎΠ³ΠΎ ΠΌΠΎΠ΄ΡΠ»Ρ.
+
+
+### ΠΠΌΠΏΠΎΡΡ `APIRouter`
+
+Π’ΠΎΡΠ½ΠΎ ΡΠ°ΠΊΠΆΠ΅, ΠΊΠ°ΠΊ ΠΈ Π² ΡΠ»ΡΡΠ°Π΅ Ρ ΠΊΠ»Π°ΡΡΠΎΠΌ `FastAPI`, Π²Π°ΠΌ Π½ΡΠΆΠ½ΠΎ ΠΈΠΌΠΏΠΎΡΡΠΈΡΠΎΠ²Π°ΡΡ ΠΈ ΡΠΎΠ·Π΄Π°ΡΡ ΠΎΠ±ΡΠ΅ΠΊΡ ΠΊΠ»Π°ΡΡΠ° `APIRouter`.
+
+```Python hl_lines="1 3" title="app/routers/users.py"
+{!../../docs_src/bigger_applications/app/routers/users.py!}
+```
+
+### Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ *ΡΠ½Π΄ΠΏΠΎΠΈΠ½ΡΠΎΠ²* Ρ ΠΏΠΎΠΌΠΎΡΡΡ `APIRouter`
+
+Π Π΄Π°Π»ΡΠ½Π΅ΠΉΡΠ΅ΠΌ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠΉΡΠ΅ `APIRouter` Π΄Π»Ρ ΠΎΠ±ΡΡΠ²Π»Π΅Π½ΠΈΡ *ΡΠ½Π΄ΠΏΠΎΠΈΠ½ΡΠΎΠ²*, ΡΠΎΡΠ½ΠΎ ΡΠ°ΠΊΠΆΠ΅, ΠΊΠ°ΠΊ Π²Ρ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΡΠ΅ ΠΊΠ»Π°ΡΡ `FastAPI`:
+
+```Python hl_lines="6 11 16" title="app/routers/users.py"
+{!../../docs_src/bigger_applications/app/routers/users.py!}
+```
+
+ΠΡ ΠΌΠΎΠΆΠ΅ΡΠ΅ Π΄ΡΠΌΠ°ΡΡ ΠΎΠ± `APIRouter` ΠΊΠ°ΠΊ ΠΎΠ± "ΡΠΌΠ΅Π½ΡΡΠ΅Π½Π½ΠΎΠΉ Π²Π΅ΡΡΠΈΠΈ" ΠΊΠ»Π°ΡΡΠ° FastAPI`.
+
+`APIRouter` ΠΏΠΎΠ΄Π΄Π΅ΡΠΆΠΈΠ²Π°Π΅Ρ Π²ΡΠ΅ ΡΠ΅ ΠΆΠ΅ ΡΠ°ΠΌΡΠ΅ ΠΎΠΏΡΠΈΠΈ.
+
+`APIRouter` ΠΏΠΎΠ΄Π΄Π΅ΡΠΆΠΈΠ²Π°Π΅Ρ Π²ΡΠ΅ ΡΠ΅ ΠΆΠ΅ ΡΠ°ΠΌΡΠ΅ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡΡ, ΡΠ°ΠΊΠΈΠ΅ ΠΊΠ°ΠΊ `parameters`, `responses`, `dependencies`, `tags`, ΠΈ Ρ. Π΄.
+
+/// tip | ΠΠΎΠ΄ΡΠΊΠ°Π·ΠΊΠ°
+
+Π Π΄Π°Π½Π½ΠΎΠΌ ΠΏΡΠΈΠΌΠ΅ΡΠ΅, Π² ΠΊΠ°ΡΠ΅ΡΡΠ²Π΅ Π½Π°Π·Π²Π°Π½ΠΈΡ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΠΎΠΉ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΡΡΡ `router`, Π½ΠΎ Π²Ρ ΠΌΠΎΠΆΠ΅ΡΠ΅ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ Π»ΡΠ±ΠΎΠ΅ Π΄ΡΡΠ³ΠΎΠ΅ ΠΈΠΌΡ.
+
+///
+
+ΠΡ ΡΠΎΠ±ΠΈΡΠ°Π΅ΠΌΡΡ ΠΏΠΎΠ΄ΠΊΠ»ΡΡΠΈΡΡ Π΄Π°Π½Π½ΡΠΉ `APIRouter` ΠΊ Π½Π°ΡΠ΅ΠΌΡ ΠΎΡΠ½ΠΎΠ²Π½ΠΎΠΌΡ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ Π½Π° `FastAPI`, Π½ΠΎ ΡΠ½Π°ΡΠ°Π»Π° Π΄Π°Π²Π°ΠΉΡΠ΅ ΠΏΡΠΎΠ²Π΅ΡΠΈΠΌ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠΈ ΠΈ ΡΠΎΠ·Π΄Π°Π΄ΠΈΠΌ Π΅ΡΡ ΠΎΠ΄ΠΈΠ½ ΠΌΠΎΠ΄ΡΠ»Ρ Ρ `APIRouter`.
+
+## ΠΠ°Π²ΠΈΡΠΈΠΌΠΎΡΡΠΈ
+
+ΠΠ°ΠΌ ΠΏΠΎΠ½Π°Π΄ΠΎΠ±ΡΡΡΡ Π½Π΅ΠΊΠΎΡΠΎΡΡΠ΅ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠΈ, ΠΊΠΎΡΠΎΡΡΠ΅ ΠΌΡ Π±ΡΠ΄Π΅ΠΌ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ Π² ΡΠ°Π·Π½ΡΡ
ΠΌΠ΅ΡΡΠ°Ρ
Π½Π°ΡΠ΅Π³ΠΎ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ.
+
+ΠΡ ΠΏΠΎΠΌΠ΅ΡΡΠΈΠΌ ΠΈΡ
Π² ΠΎΡΠ΄Π΅Π»ΡΠ½ΡΠΉ ΠΌΠΎΠ΄ΡΠ»Ρ `dependencies` (`app/dependencies.py`).
+
+Π’Π΅ΠΏΠ΅ΡΡ ΠΌΡ Π²ΠΎΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌΡΡ ΠΏΡΠΎΡΡΠΎΠΉ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΡΡ, ΡΡΠΎΠ±Ρ ΠΏΡΠΎΡΠΈΡΠ°ΡΡ ΠΊΠ°ΡΡΠΎΠΌΠΈΠ·ΠΈΡΠΎΠ²Π°Π½Π½ΡΠΉ `X-Token` ΠΈΠ· Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠ°:
+
+//// tab | Python 3.9+
+
+```Python hl_lines="3 6-8" title="app/dependencies.py"
+{!> ../../docs_src/bigger_applications/app_an_py39/dependencies.py!}
+```
+
+////
+
+//// tab | Python 3.8+
+
+```Python hl_lines="1 5-7" title="app/dependencies.py"
+{!> ../../docs_src/bigger_applications/app_an/dependencies.py!}
+```
+
+////
+
+//// tab | Python 3.8+ non-Annotated
+
+/// tip | ΠΠΎΠ΄ΡΠΊΠ°Π·ΠΊΠ°
+
+ΠΡ ΡΠ΅ΠΊΠΎΠΌΠ΅Π½Π΄ΡΠ΅ΠΌ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ Π²Π΅ΡΡΠΈΡ `Annotated`, ΠΊΠΎΠ³Π΄Π° ΡΡΠΎ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎ.
+
+///
+
+```Python hl_lines="1 4-6" title="app/dependencies.py"
+{!> ../../docs_src/bigger_applications/app/dependencies.py!}
+```
+
+////
+
+/// tip | ΠΠΎΠ΄ΡΠΊΠ°Π·ΠΊΠ°
+
+ΠΠ»Ρ ΠΏΡΠΎΡΡΠΎΡΡ ΠΌΡ Π²ΠΎΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π»ΠΈΡΡ Π½Π΅ΠΊΠΈΠΌ Π²ΠΎΠΎΠ±ΡΠ°ΠΆΠ°Π΅ΠΌΡΠΌ Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊΠΎΠΌ.
+
+Π ΡΠ΅Π°Π»ΡΠ½ΡΡ
ΡΠ»ΡΡΠ°ΡΡ
Π΄Π»Ρ ΠΏΠΎΠ»ΡΡΠ΅Π½ΠΈΡ Π½Π°ΠΈΠ»ΡΡΡΠΈΡ
ΡΠ΅Π·ΡΠ»ΡΡΠ°ΡΠΎΠ² ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠΉΡΠ΅ ΠΈΠ½ΡΠ΅Π³ΡΠΈΡΠΎΠ²Π°Π½Π½ΡΠ΅ ΡΡΠΈΠ»ΠΈΡΡ ΠΎΠ±Π΅ΡΠΏΠ΅ΡΠ΅Π½ΠΈΡ Π±Π΅Π·ΠΎΠΏΠ°ΡΠ½ΠΎΡΡΠΈ [Security utilities](security/index.md){.internal-link target=_blank}.
+
+///
+
+## ΠΡΡ ΠΎΠ΄ΠΈΠ½ ΠΌΠΎΠ΄ΡΠ»Ρ Ρ `APIRouter`
+
+ΠΠ°Π²Π°ΠΉΡΠ΅ ΡΠ°ΠΊΠΆΠ΅ ΠΏΡΠ΅Π΄ΠΏΠΎΠ»ΠΎΠΆΠΈΠΌ, ΡΡΠΎ Ρ Π²Π°Ρ Π΅ΡΡΡ *ΡΠ½Π΄ΠΏΠΎΠΈΠ½ΡΡ*, ΠΎΡΠ²Π΅ΡΠ°ΡΡΠΈΠ΅ Π·Π° ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΡ "items", ΠΈ ΠΎΠ½ΠΈ Π½Π°Ρ
ΠΎΠ΄ΡΡΡΡ Π² ΠΌΠΎΠ΄ΡΠ»Π΅ `app/routers/items.py`.
+
+Π£ Π²Π°Ρ ΠΎΠΏΡΠ΅Π΄Π΅Π»Π΅Π½Ρ ΡΠ»Π΅Π΄ΡΡΡΠΈΠ΅ *ΠΎΠΏΠ΅ΡΠ°ΡΠΈΠΈ ΠΏΡΡΠΈ* (*ΡΠ½Π΄ΠΏΠΎΠΈΠ½ΡΡ*):
+
+* `/items/`
+* `/items/{item_id}`
+
+Π’ΡΡ Π²ΡΡ ΡΠΎΡΠ½ΠΎ ΡΠ°ΠΊΠΆΠ΅, ΠΊΠ°ΠΊ ΠΈ Π² ΡΠΈΡΡΠ°ΡΠΈΠΈ Ρ `app/routers/users.py`.
+
+ΠΠΎ ΡΠ΅ΠΏΠ΅ΡΡ ΠΌΡ Ρ
ΠΎΡΠΈΠΌ ΠΏΠΎΡΡΡΠΏΠΈΡΡ Π½Π΅ΠΌΠ½ΠΎΠ³ΠΎ ΡΠΌΠ½Π΅Π΅ ΠΈ ΡΠ»Π΅Π³ΠΊΠ° ΡΠΏΡΠΎΡΡΠΈΡΡ ΠΊΠΎΠ΄.
+
+ΠΡ Π·Π½Π°Π΅ΠΌ, ΡΡΠΎ Π²ΡΠ΅ *ΡΠ½Π΄ΠΏΠΎΠΈΠ½ΡΡ* Π΄Π°Π½Π½ΠΎΠ³ΠΎ ΠΌΠΎΠ΄ΡΠ»Ρ ΠΈΠΌΠ΅ΡΡ Π½Π΅ΠΊΠΎΡΠΎΡΡΠ΅ ΠΎΠ±ΡΠΈΠ΅ ΡΠ²ΠΎΠΉΡΡΠ²Π°:
+
+* ΠΡΠ΅ΡΠΈΠΊΡ ΠΏΡΡΠΈ: `/items`.
+* Π’Π΅Π³ΠΈ: (ΠΎΠ΄ΠΈΠ½ Π΅Π΄ΠΈΠ½ΡΡΠ²Π΅Π½Π½ΡΠΉ ΡΠ΅Π³: `items`).
+* ΠΠΎΠΏΠΎΠ»Π½ΠΈΡΠ΅Π»ΡΠ½ΡΠ΅ ΠΎΡΠ²Π΅ΡΡ (responses)
+* ΠΠ°Π²ΠΈΡΠΈΠΌΠΎΡΡΠΈ: ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ ΡΠΎΠ·Π΄Π°Π½Π½ΠΎΠΉ Π½Π°ΠΌΠΈ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠΈ `X-token`
+
+Π’Π°ΠΊΠΈΠΌ ΠΎΠ±ΡΠ°Π·ΠΎΠΌ, Π²ΠΌΠ΅ΡΡΠΎ ΡΠΎΠ³ΠΎ ΡΡΠΎΠ±Ρ Π΄ΠΎΠ±Π°Π²Π»ΡΡΡ Π²ΡΠ΅ ΡΡΠΈ ΡΠ²ΠΎΠΉΡΡΠ²Π° Π² ΡΡΠ½ΠΊΡΠΈΡ ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ ΠΎΡΠ΄Π΅Π»ΡΠ½ΠΎΠ³ΠΎ *ΡΠ½Π΄ΠΏΠΎΠΈΠ½ΡΠ°*,
+ΠΌΡ Π΄ΠΎΠ±Π°Π²ΠΈΠΌ ΠΈΡ
Π² `APIRouter`.
+
+```Python hl_lines="5-10 16 21" title="app/routers/items.py"
+{!../../docs_src/bigger_applications/app/routers/items.py!}
+```
+
+Π’Π°ΠΊ ΠΊΠ°ΠΊ ΠΊΠ°ΠΆΠ΄ΡΠΉ *ΡΠ½Π΄ΠΏΠΎΠΈΠ½Ρ* Π½Π°ΡΠΈΠ½Π°Π΅ΡΡΡ Ρ ΡΠΈΠΌΠ²ΠΎΠ»Π° `/`:
+
+```Python hl_lines="1"
+@router.get("/{item_id}")
+async def read_item(item_id: str):
+ ...
+```
+
+...ΡΠΎ ΠΏΡΠ΅ΡΠΈΠΊΡ Π½Π΅ Π΄ΠΎΠ»ΠΆΠ΅Π½ Π·Π°ΠΊΠ°Π½ΡΠΈΠ²Π°ΡΡΡΡ ΡΠΈΠΌΠ²ΠΎΠ»ΠΎΠΌ `/`.
+
+Π Π½Π°ΡΠ΅ΠΌ ΡΠ»ΡΡΠ°Π΅ ΠΏΡΠ΅ΡΠΈΠΊΡΠΎΠΌ ΡΠ²Π»ΡΠ΅ΡΡΡ `/items`.
+
+ΠΡ ΡΠ°ΠΊΠΆΠ΅ ΠΌΠΎΠΆΠ΅ΠΌ Π΄ΠΎΠ±Π°Π²ΠΈΡΡ Π² Π½Π°Ρ ΠΌΠ°ΡΡΡΡΡΠΈΠ·Π°ΡΠΎΡ (router) ΡΠΏΠΈΡΠΎΠΊ `ΡΠ΅Π³ΠΎΠ²` (`tags`) ΠΈ Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡΠ΅Π»ΡΠ½ΡΡ
`ΠΎΡΠ²Π΅ΡΠΎΠ²` (`responses`), ΠΊΠΎΡΠΎΡΡΠ΅ ΡΠ²Π»ΡΡΡΡΡ ΠΎΠ±ΡΠΈΠΌΠΈ Π΄Π»Ρ ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ *ΡΠ½Π΄ΠΏΠΎΠΈΠ½ΡΠ°*.
+
+Π Π΅ΡΡ ΠΌΡ ΠΌΠΎΠΆΠ΅ΠΌ Π΄ΠΎΠ±Π°Π²ΠΈΡΡ Π² Π½Π°Ρ ΠΌΠ°ΡΡΡΡΡΠΈΠ·Π°ΡΠΎΡ ΡΠΏΠΈΡΠΎΠΊ `Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠ΅ΠΉ`, ΠΊΠΎΡΠΎΡΡΠ΅ Π΄ΠΎΠ»ΠΆΠ½Ρ Π²ΡΠ·ΡΠ²Π°ΡΡΡΡ ΠΏΡΠΈ ΠΊΠ°ΠΆΠ΄ΠΎΠΌ ΠΎΠ±ΡΠ°ΡΠ΅Π½ΠΈΠΈ ΠΊ *ΡΠ½Π΄ΠΏΠΎΠΈΠ½ΡΠ°ΠΌ*.
+
+/// tip | ΠΠΎΠ΄ΡΠΊΠ°Π·ΠΊΠ°
+
+ΠΠ±ΡΠ°ΡΠΈΡΠ΅ Π²Π½ΠΈΠΌΠ°Π½ΠΈΠ΅, ΡΡΠΎ ΡΠ°ΠΊΠΆΠ΅, ΠΊΠ°ΠΊ ΠΈ Π² ΡΠ»ΡΡΠ°Π΅ Ρ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΡΠΌΠΈ Π² Π΄Π΅ΠΊΠΎΡΠ°ΡΠΎΡΠ°Ρ
*ΡΠ½Π΄ΠΏΠΎΠΈΠ½ΡΠΎΠ²* ([dependencies in *path operation decorators*](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), Π½ΠΈΠΊΠ°ΠΊΠΎΠ³ΠΎ Π·Π½Π°ΡΠ΅Π½ΠΈΡ Π² *ΡΡΠ½ΠΊΡΠΈΡ ΡΠ½Π΄ΠΏΠΎΠΈΠ½ΡΠ°* ΠΏΠ΅ΡΠ΅Π΄Π°Π½ΠΎ Π½Π΅ Π±ΡΠ΄Π΅Ρ.
+
+///
+
+Π ΡΠ΅Π·ΡΠ»ΡΡΠ°ΡΠ΅ ΠΌΡ ΠΏΠΎΠ»ΡΡΠΈΠΌ ΡΠ»Π΅Π΄ΡΡΡΠΈΠ΅ ΡΠ½Π΄ΠΏΠΎΠΈΠ½ΡΡ:
+
+* `/items/`
+* `/items/{item_id}`
+
+...ΠΊΠ°ΠΊ ΠΌΡ ΠΈ ΠΏΠ»Π°Π½ΠΈΡΠΎΠ²Π°Π»ΠΈ.
+
+* ΠΠ½ΠΈ Π±ΡΠ΄ΡΡ ΠΏΠΎΠΌΠ΅ΡΠ΅Π½Ρ ΡΠ΅Π³Π°ΠΌΠΈ ΠΈΠ· Π·Π°Π΄Π°Π½Π½ΠΎΠ³ΠΎ ΡΠΏΠΈΡΠΊΠ°, Π² Π½Π°ΡΠ΅ΠΌ ΡΠ»ΡΡΠ°Π΅ ΡΡΠΎ `"items"`.
+ * ΠΡΠΈ ΡΠ΅Π³ΠΈ ΠΎΡΠΎΠ±Π΅Π½Π½ΠΎ ΠΏΠΎΠ»Π΅Π·Π½Ρ Π΄Π»Ρ ΡΠΈΡΡΠ΅ΠΌΡ Π°Π²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠΎΠΉ ΠΈΠ½ΡΠ΅ΡΠ°ΠΊΡΠΈΠ²Π½ΠΎΠΉ Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ°ΡΠΈΠΈ (Ρ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ΠΌ OpenAPI).
+* ΠΠ°ΠΆΠ΄ΡΠΉ ΠΈΠ· Π½ΠΈΡ
Π±ΡΠ΄Π΅Ρ Π²ΠΊΠ»ΡΡΠ°ΡΡ ΠΏΡΠ΅Π΄ΠΎΠΏΡΠ΅Π΄Π΅Π»Π΅Π½Π½ΡΠ΅ ΠΎΡΠ²Π΅ΡΡ `responses`.
+* ΠΠ°ΠΆΠ΄ΡΠΉ *ΡΠ½Π΄ΠΏΠΎΠΈΠ½Ρ* Π±ΡΠ΄Π΅Ρ ΠΈΠΌΠ΅ΡΡ ΡΠΏΠΈΡΠΎΠΊ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠ΅ΠΉ (`dependencies`), ΠΈΡΠΏΠΎΠ»Π½ΡΠ΅ΠΌΡΡ
ΠΏΠ΅ΡΠ΅Π΄ Π²ΡΠ·ΠΎΠ²ΠΎΠΌ *ΡΠ½Π΄ΠΏΠΎΠΈΠ½ΡΠ°*.
+ * ΠΡΠ»ΠΈ Π²Ρ ΠΎΠΏΡΠ΅Π΄Π΅Π»ΠΈΠ»ΠΈ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠΈ Π² ΡΠ°ΠΌΠΎΠΉ ΠΎΠΏΠ΅ΡΠ°ΡΠΈΠΈ ΠΏΡΡΠΈ, **ΡΠΎ ΠΎΠ½Π° ΡΠ°ΠΊΠΆΠ΅ Π±ΡΠ΄Π΅Ρ Π²ΡΠΏΠΎΠ»Π½Π΅Π½Π°**.
+ * Π‘Π½Π°ΡΠ°Π»Π° Π²ΡΠΏΠΎΠ»Π½ΡΡΡΡΡ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠΈ ΠΌΠ°ΡΡΡΡΡΠΈΠ·Π°ΡΠΎΡΠ°, Π·Π°ΡΠ΅ΠΌ Π²ΡΠ·ΡΠ²Π°ΡΡΡΡ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠΈ, ΠΎΠΏΡΠ΅Π΄Π΅Π»Π΅Π½Π½ΡΠ΅ Π² Π΄Π΅ΠΊΠΎΡΠ°ΡΠΎΡΠ΅ *ΡΠ½Π΄ΠΏΠΎΠΈΠ½ΡΠ°* ([`dependencies` in the decorator](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), ΠΈ, Π½Π°ΠΊΠΎΠ½Π΅Ρ, ΠΎΠ±ΡΡΠ½ΡΠ΅ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡΠΈΡΠ΅ΡΠΊΠΈΠ΅ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠΈ.
+ * ΠΡ ΡΠ°ΠΊΠΆΠ΅ ΠΌΠΎΠΆΠ΅ΡΠ΅ Π΄ΠΎΠ±Π°Π²ΠΈΡΡ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠΈ Π±Π΅Π·ΠΎΠΏΠ°ΡΠ½ΠΎΡΡΠΈ Ρ ΠΎΠ±Π»Π°ΡΡΡΠΌΠΈ Π²ΠΈΠ΄ΠΈΠΌΠΎΡΡΠΈ (`scopes`) [`Security` dependencies with `scopes`](../advanced/security/oauth2-scopes.md){.internal-link target=_blank}.
+
+/// tip | ΠΠΎΠ΄ΡΠΊΠ°Π·ΠΊΠ°
+
+ΠΠ°ΠΏΡΠΈΠΌΠ΅Ρ, Ρ ΠΏΠΎΠΌΠΎΡΡΡ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠ΅ΠΉ Π² `APIRouter` ΠΌΡ ΠΌΠΎΠΆΠ΅ΠΌ ΠΏΠΎΡΡΠ΅Π±ΠΎΠ²Π°ΡΡ Π°ΡΡΠ΅Π½ΡΠΈΡΠΈΠΊΠ°ΡΠΈΠΈ Π΄Π»Ρ Π΄ΠΎΡΡΡΠΏΠ° ΠΊΠΎ Π²ΡΠ΅ΠΉ Π³ΡΡΠΏΠΏΠ΅ *ΡΠ½Π΄ΠΏΠΎΠΈΠ½ΡΠΎΠ²*, Π½Π΅ ΡΠΊΠ°Π·ΡΠ²Π°Ρ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠΈ Π΄Π»Ρ ΠΊΠ°ΠΆΠ΄ΠΎΠΉ ΠΎΡΠ΄Π΅Π»ΡΠ½ΠΎΠΉ ΡΡΠ½ΠΊΡΠΈΠΈ *ΡΠ½Π΄ΠΏΠΎΠΈΠ½ΡΠ°*.
+
+///
+
+/// check | ΠΠ°ΠΌΠ΅ΡΠΊΠ°
+
+ΠΠ°ΡΠ°ΠΌΠ΅ΡΡΡ `prefix`, `tags`, `responses` ΠΈ `dependencies` ΠΎΡΠ½ΠΎΡΡΡΡΡ ΠΊ ΡΡΠ½ΠΊΡΠΈΠΎΠ½Π°Π»Ρ **FastAPI**, ΠΏΠΎΠΌΠΎΠ³Π°ΡΡΠ΅ΠΌΡ ΠΈΠ·Π±Π΅ΠΆΠ°ΡΡ Π΄ΡΠ±Π»ΠΈΡΠΎΠ²Π°Π½ΠΈΡ ΠΊΠΎΠ΄Π°.
+
+///
+
+### ΠΠΌΠΏΠΎΡΡ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠ΅ΠΉ
+
+ΠΠ°Ρ ΠΊΠΎΠ΄ Π½Π°Ρ
ΠΎΠ΄ΠΈΡΡΡ Π² ΠΌΠΎΠ΄ΡΠ»Π΅ `app.routers.items` (ΡΠ°ΠΉΠ» `app/routers/items.py`).
+
+Π Π½Π°ΠΌ Π½ΡΠΆΠ½ΠΎ Π²ΡΠ·Π²Π°ΡΡ ΡΡΠ½ΠΊΡΠΈΡ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠΈ ΠΈΠ· ΠΌΠΎΠ΄ΡΠ»Ρ `app.dependencies` (ΡΠ°ΠΉΠ» `app/dependencies.py`).
+
+ΠΡ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌ ΠΎΠΏΠ΅ΡΠ°ΡΠΈΡ ΠΎΡΠ½ΠΎΡΠΈΡΠ΅Π»ΡΠ½ΠΎΠ³ΠΎ ΠΈΠΌΠΏΠΎΡΡΠ° `..` Π΄Π»Ρ ΠΈΠΌΠΏΠΎΡΡΠ° Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠΈ:
+
+```Python hl_lines="3" title="app/routers/items.py"
+{!../../docs_src/bigger_applications/app/routers/items.py!}
+```
+
+#### ΠΠ°ΠΊ ΡΠ°Π±ΠΎΡΠ°Π΅Ρ ΠΎΡΠ½ΠΎΡΠΈΡΠ΅Π»ΡΠ½ΡΠΉ ΠΈΠΌΠΏΠΎΡΡ?
+
+/// tip | ΠΠΎΠ΄ΡΠΊΠ°Π·ΠΊΠ°
+
+ΠΡΠ»ΠΈ Π²Ρ ΠΏΡΠ΅ΠΊΡΠ°ΡΠ½ΠΎ Π·Π½Π°Π΅ΡΠ΅, ΠΊΠ°ΠΊ ΡΠ°Π±ΠΎΡΠ°Π΅Ρ ΠΈΠΌΠΏΠΎΡΡ Π² Python, ΡΠΎ ΠΏΠ΅ΡΠ΅Ρ
ΠΎΠ΄ΠΈΡΠ΅ ΠΊ ΡΠ»Π΅Π΄ΡΡΡΠ΅ΠΌΡ ΡΠ°Π·Π΄Π΅Π»Ρ.
+
+///
+
+ΠΠ΄Π½Π° ΡΠΎΡΠΊΠ° `.`, ΠΊΠ°ΠΊ Π² Π΄Π°Π½Π½ΠΎΠΌ ΠΏΡΠΈΠΌΠ΅ΡΠ΅:
+
+```Python
+from .dependencies import get_token_header
+```
+ΠΎΠ·Π½Π°ΡΠ°Π΅Ρ:
+
+* ΠΠ°ΡΠ½ΠΈΡΠ΅ Ρ ΠΏΠ°ΠΊΠ΅ΡΠ°, Π² ΠΊΠΎΡΠΎΡΠΎΠΌ Π½Π°Ρ
ΠΎΠ΄ΠΈΡΡΡ Π΄Π°Π½Π½ΡΠΉ ΠΌΠΎΠ΄ΡΠ»Ρ (ΡΠ°ΠΉΠ» `app/routers/items.py` ΡΠ°ΡΠΏΠΎΠ»ΠΎΠΆΠ΅Π½ Π² ΠΊΠ°ΡΠ°Π»ΠΎΠ³Π΅ `app/routers/`)...
+* ... Π½Π°ΠΉΠ΄ΠΈΡΠ΅ ΠΌΠΎΠ΄ΡΠ»Ρ `dependencies` (ΡΠ°ΠΉΠ» `app/routers/dependencies.py`)...
+* ... ΠΈ ΠΈΠΌΠΏΠΎΡΡΠΈΡΡΠΉΡΠ΅ ΠΈΠ· Π½Π΅Π³ΠΎ ΡΡΠ½ΠΊΡΠΈΡ `get_token_header`.
+
+Π ΡΠΎΠΆΠ°Π»Π΅Π½ΠΈΡ, ΡΠ°ΠΊΠΎΠ³ΠΎ ΡΠ°ΠΉΠ»Π° Π½Π΅ ΡΡΡΠ΅ΡΡΠ²ΡΠ΅Ρ, ΠΈ Π½Π°ΡΠΈ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠΈ Π½Π°Ρ
ΠΎΠ΄ΡΡΡΡ Π² ΡΠ°ΠΉΠ»Π΅ `app/dependencies.py`.
+
+ΠΡΠΏΠΎΠΌΠ½ΠΈΡΠ΅, ΠΊΠ°ΠΊ Π²ΡΠ³Π»ΡΠ΄ΠΈΡ ΡΠ°ΠΉΠ»ΠΎΠ²Π°Ρ ΡΡΡΡΠΊΡΡΡΠ° Π½Π°ΡΠ΅Π³ΠΎ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ:
+
+
+
+---
+
+ΠΠ²Π΅ ΡΠΎΡΠΊΠΈ `..`, ΠΊΠ°ΠΊ Π² Π΄Π°Π½Π½ΠΎΠΌ ΠΏΡΠΈΠΌΠ΅ΡΠ΅:
+
+```Python
+from ..dependencies import get_token_header
+```
+
+ΠΎΠ·Π½Π°ΡΠ°ΡΡ:
+
+* ΠΠ°ΡΠ½ΠΈΡΠ΅ Ρ ΠΏΠ°ΠΊΠ΅ΡΠ°, Π² ΠΊΠΎΡΠΎΡΠΎΠΌ Π½Π°Ρ
ΠΎΠ΄ΠΈΡΡΡ Π΄Π°Π½Π½ΡΠΉ ΠΌΠΎΠ΄ΡΠ»Ρ (ΡΠ°ΠΉΠ» `app/routers/items.py` Π½Π°Ρ
ΠΎΠ΄ΠΈΡΡΡ Π² ΠΊΠ°ΡΠ°Π»ΠΎΠ³Π΅ `app/routers/`)...
+* ... ΠΏΠ΅ΡΠ΅ΠΉΠ΄ΠΈΡΠ΅ Π² ΡΠΎΠ΄ΠΈΡΠ΅Π»ΡΡΠΊΠΈΠΉ ΠΏΠ°ΠΊΠ΅Ρ (ΠΊΠ°ΡΠ°Π»ΠΎΠ³ `app/`)...
+* ... Π½Π°ΠΉΠ΄ΠΈΡΠ΅ Π² Π½ΡΠΌ ΠΌΠΎΠ΄ΡΠ»Ρ `dependencies` (ΡΠ°ΠΉΠ» `app/dependencies.py`)...
+* ... ΠΈ ΠΈΠΌΠΏΠΎΡΡΠΈΡΡΠΉΡΠ΅ ΠΈΠ· Π½Π΅Π³ΠΎ ΡΡΠ½ΠΊΡΠΈΡ `get_token_header`.
+
+ΠΡΠΎ ΡΠ°Π±ΠΎΡΠ°Π΅Ρ Π²Π΅ΡΠ½ΠΎ! π
+
+---
+
+ΠΠ½Π°Π»ΠΎΠ³ΠΈΡΠ½ΠΎ, Π΅ΡΠ»ΠΈ Π±Ρ ΠΌΡ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π»ΠΈ ΡΡΠΈ ΡΠΎΡΠΊΠΈ `...`, ΠΊΠ°ΠΊ Π·Π΄Π΅ΡΡ:
+
+```Python
+from ...dependencies import get_token_header
+```
+
+ΡΠΎ ΡΡΠΎ Π±Ρ ΠΎΠ·Π½Π°ΡΠ°Π»ΠΎ:
+
+* ΠΠ°ΡΠ½ΠΈΡΠ΅ Ρ ΠΏΠ°ΠΊΠ΅ΡΠ°, Π² ΠΊΠΎΡΠΎΡΠΎΠΌ Π½Π°Ρ
ΠΎΠ΄ΠΈΡΡΡ Π΄Π°Π½Π½ΡΠΉ ΠΌΠΎΠ΄ΡΠ»Ρ (ΡΠ°ΠΉΠ» `app/routers/items.py` Π½Π°Ρ
ΠΎΠ΄ΠΈΡΡΡ Π² ΠΊΠ°ΡΠ°Π»ΠΎΠ³Π΅ `app/routers/`)...
+* ... ΠΏΠ΅ΡΠ΅ΠΉΠ΄ΠΈΡΠ΅ Π² ΡΠΎΠ΄ΠΈΡΠ΅Π»ΡΡΠΊΠΈΠΉ ΠΏΠ°ΠΊΠ΅Ρ (ΠΊΠ°ΡΠ°Π»ΠΎΠ³ `app/`)...
+* ... Π·Π°ΡΠ΅ΠΌ ΠΏΠ΅ΡΠ΅ΠΉΠ΄ΠΈΡΠ΅ Π² ΡΠΎΠ΄ΠΈΡΠ΅Π»ΡΡΠΊΠΈΠΉ ΠΏΠ°ΠΊΠ΅Ρ ΡΠ΅ΠΊΡΡΠ΅Π³ΠΎ ΠΏΠ°ΠΊΠ΅ΡΠ° (ΡΠ°ΠΊΠΎΠ³ΠΎ ΠΏΠ°ΠΊΠ΅ΡΠ° Π½Π΅ ΡΡΡΠ΅ΡΡΠ²ΡΠ΅Ρ, `app` Π½Π°Ρ
ΠΎΠ΄ΠΈΡΡΡ Π½Π° ΡΠ°ΠΌΠΎΠΌ Π²Π΅ΡΡ
Π½Π΅ΠΌ ΡΡΠΎΠ²Π½Π΅ π±)...
+* ... Π½Π°ΠΉΠ΄ΠΈΡΠ΅ Π² Π½ΡΠΌ ΠΌΠΎΠ΄ΡΠ»Ρ `dependencies` (ΡΠ°ΠΉΠ» `app/dependencies.py`)...
+* ... ΠΈ ΠΈΠΌΠΏΠΎΡΡΠΈΡΡΠΉΡΠ΅ ΠΈΠ· Π½Π΅Π³ΠΎ ΡΡΠ½ΠΊΡΠΈΡ `get_token_header`.
+
+ΠΡΠΎ Π±ΡΠ΄Π΅Ρ ΠΎΡΠ½ΠΎΡΠΈΡΡΡΡ ΠΊ Π½Π΅ΠΊΠΎΡΠΎΡΠΎΠΌΡ ΠΏΠ°ΠΊΠ΅ΡΡ, Π½Π°Ρ
ΠΎΠ΄ΡΡΠ΅ΠΌΡΡΡ Π½Π° ΠΎΠ΄ΠΈΠ½ ΡΡΠΎΠ²Π΅Π½Ρ Π²ΡΡΠ΅ ΡΠ΅ΠΌ `app/` ΠΈ ΡΠΎΠ΄Π΅ΡΠΆΠ°ΡΠ΅ΠΌΡ ΡΠ²ΠΎΠΉ ΡΠΎΠ±ΡΡΠ²Π΅Π½Π½ΡΠΉ ΡΠ°ΠΉΠ» `__init__.py`. ΠΠΎ Π½ΠΈΡΠ΅Π³ΠΎ ΡΠ°ΠΊΠΎΠ³ΠΎ Ρ Π½Π°Ρ Π½Π΅Ρ. ΠΠΎΡΡΠΎΠΌΡ ΡΡΠΎ ΠΏΡΠΈΠ²Π΅Π΄Π΅Ρ ΠΊ ΠΎΡΠΈΠ±ΠΊΠ΅ Π² Π½Π°ΡΠ΅ΠΌ ΠΏΡΠΈΠΌΠ΅ΡΠ΅. π¨
+
+Π’Π΅ΠΏΠ΅ΡΡ Π²Ρ Π·Π½Π°Π΅ΡΠ΅, ΠΊΠ°ΠΊ ΡΠ°Π±ΠΎΡΠ°Π΅Ρ ΠΈΠΌΠΏΠΎΡΡ Π² Python, ΠΈ ΡΠΌΠΎΠΆΠ΅ΡΠ΅ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ ΠΎΡΠ½ΠΎΡΠΈΡΠ΅Π»ΡΠ½ΠΎΠ΅ ΠΈΠΌΠΏΠΎΡΡΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ Π² ΡΠ²ΠΎΠΈΡ
ΡΠΎΠ±ΡΡΠ²Π΅Π½Π½ΡΡ
ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡΡ
Π»ΡΠ±ΠΎΠ³ΠΎ ΡΡΠΎΠ²Π½Ρ ΡΠ»ΠΎΠΆΠ½ΠΎΡΡΠΈ. π€
+
+### ΠΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠ΅ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»ΡΡΠΊΠΈΡ
ΡΠ΅Π³ΠΎΠ² (`tags`), ΠΎΡΠ²Π΅ΡΠΎΠ² (`responses`) ΠΈ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠ΅ΠΉ (`dependencies`)
+
+ΠΡ Π½Π΅ Π±ΡΠ΄Π΅ΠΌ Π΄ΠΎΠ±Π°Π²Π»ΡΡΡ ΠΏΡΠ΅ΡΠΈΠΊΡ `/items` ΠΈ ΡΠΏΠΈΡΠΎΠΊ ΡΠ΅Π³ΠΎΠ² `tags=["items"]` Π΄Π»Ρ ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ *ΡΠ½Π΄ΠΏΠΎΠΈΠ½ΡΠ°*, Ρ.ΠΊ. ΠΌΡ ΡΠΆΠ΅ ΠΈΡ
Π΄ΠΎΠ±Π°Π²ΠΈΠ»ΠΈ Ρ ΠΏΠΎΠΌΠΎΡΡΡ `APIRouter`.
+
+ΠΠΎ ΠΏΠΎΠΌΠΈΠΌΠΎ ΡΡΠΎΠ³ΠΎ ΠΌΡ ΠΌΠΎΠΆΠ΅ΠΌ Π΄ΠΎΠ±Π°Π²ΠΈΡΡ Π½ΠΎΠ²ΡΠ΅ ΡΠ΅Π³ΠΈ Π΄Π»Ρ ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ ΠΎΡΠ΄Π΅Π»ΡΠ½ΠΎΠ³ΠΎ *ΡΠ½Π΄ΠΏΠΎΠΈΠ½ΡΠ°*, Π° ΡΠ°ΠΊΠΆΠ΅ Π½Π΅ΠΊΠΎΡΠΎΡΡΠ΅ Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡΠ΅Π»ΡΠ½ΡΠ΅ ΠΎΡΠ²Π΅ΡΡ (`responses`), Ρ
Π°ΡΠ°ΠΊΡΠ΅ΡΠ½ΡΠ΅ Π΄Π»Ρ Π΄Π°Π½Π½ΠΎΠ³ΠΎ *ΡΠ½Π΄ΠΏΠΎΠΈΠ½ΡΠ°*:
+
+```Python hl_lines="30-31" title="app/routers/items.py"
+{!../../docs_src/bigger_applications/app/routers/items.py!}
+```
+
+/// tip | ΠΠΎΠ΄ΡΠΊΠ°Π·ΠΊΠ°
+
+ΠΠΎΡΠ»Π΅Π΄Π½ΠΈΠΉ *ΡΠ½Π΄ΠΏΠΎΠΈΠ½Ρ* Π±ΡΠ΄Π΅Ρ ΠΈΠΌΠ΅ΡΡ ΡΠ»Π΅Π΄ΡΡΡΡΡ ΠΊΠΎΠΌΠ±ΠΈΠ½Π°ΡΠΈΡ ΡΠ΅Π³ΠΎΠ²: `["items", "custom"]`.
+
+Π ΡΠ°ΠΊΠΆΠ΅ Π² Π΅Π³ΠΎ Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ°ΡΠΈΠΈ Π±ΡΠ΄ΡΡ ΡΠΎΠ΄Π΅ΡΠΆΠ°ΡΡΡΡ ΠΎΠ±Π° ΠΎΡΠ²Π΅ΡΠ°: ΠΎΠ΄ΠΈΠ½ Π΄Π»Ρ `404` ΠΈ Π΄ΡΡΠ³ΠΎΠΉ Π΄Π»Ρ `403`.
+
+///
+
+## ΠΠΎΠ΄ΡΠ»Ρ main Π² `FastAPI`
+
+Π’Π΅ΠΏΠ΅ΡΡ Π΄Π°Π²Π°ΠΉΡΠ΅ ΠΏΠΎΡΠΌΠΎΡΡΠΈΠΌ Π½Π° ΠΌΠΎΠ΄ΡΠ»Ρ `app/main.py`.
+
+ΠΠΌΠ΅Π½Π½ΠΎ ΡΡΠ΄Π° Π²Ρ ΠΈΠΌΠΏΠΎΡΡΠΈΡΡΠ΅ΡΠ΅ ΠΈ ΠΈΠΌΠ΅Π½Π½ΠΎ Π·Π΄Π΅ΡΡ Π²Ρ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΡΠ΅ ΠΊΠ»Π°ΡΡ `FastAPI`.
+
+ΠΡΠΎ ΠΎΡΠ½ΠΎΠ²Π½ΠΎΠΉ ΡΠ°ΠΉΠ» Π²Π°ΡΠ΅Π³ΠΎ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ, ΠΊΠΎΡΠΎΡΡΠΉ ΠΎΠ±ΡΠ΅Π΄ΠΈΠ½ΡΠ΅Ρ Π²ΡΡ Π² ΠΎΠ΄Π½ΠΎ ΡΠ΅Π»ΠΎΠ΅.
+
+Π ΡΠ΅ΠΏΠ΅ΡΡ, ΠΊΠΎΠ³Π΄Π° Π±ΠΎΠ»ΡΡΠ°Ρ ΡΠ°ΡΡΡ Π»ΠΎΠ³ΠΈΠΊΠΈ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ ΡΠ°Π·Π΄Π΅Π»Π΅Π½Π° Π½Π° ΠΎΡΠ΄Π΅Π»ΡΠ½ΡΠ΅ ΠΌΠΎΠ΄ΡΠ»ΠΈ, ΠΎΡΠ½ΠΎΠ²Π½ΠΎΠΉ ΡΠ°ΠΉΠ» `app/main.py` Π±ΡΠ΄Π΅Ρ Π΄ΠΎΡΡΠ°ΡΠΎΡΠ½ΠΎ ΠΏΡΠΎΡΡΡΠΌ.
+
+### ΠΠΌΠΏΠΎΡΡ `FastAPI`
+
+ΠΡ ΠΈΠΌΠΏΠΎΡΡΠΈΡΡΠ΅ΡΠ΅ ΠΈ ΡΠΎΠ·Π΄Π°Π΅ΡΠ΅ ΠΊΠ»Π°ΡΡ `FastAPI` ΠΊΠ°ΠΊ ΠΎΠ±ΡΡΠ½ΠΎ.
+
+ΠΡ Π΄Π°ΠΆΠ΅ ΠΌΠΎΠΆΠ΅ΠΌ ΠΎΠ±ΡΡΠ²ΠΈΡΡ Π³Π»ΠΎΠ±Π°Π»ΡΠ½ΡΠ΅ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠΈ [global dependencies](dependencies/global-dependencies.md){.internal-link target=_blank}, ΠΊΠΎΡΠΎΡΡΠ΅ Π±ΡΠ΄ΡΡ ΠΎΠ±ΡΠ΅Π΄ΠΈΠ½Π΅Π½Ρ Ρ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΡΠΌΠΈ Π΄Π»Ρ ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ ΠΎΡΠ΄Π΅Π»ΡΠ½ΠΎΠ³ΠΎ ΠΌΠ°ΡΡΡΡΡΠΈΠ·Π°ΡΠΎΡΠ°:
+
+```Python hl_lines="1 3 7" title="app/main.py"
+{!../../docs_src/bigger_applications/app/main.py!}
+```
+
+### ΠΠΌΠΏΠΎΡΡ `APIRouter`
+
+Π’Π΅ΠΏΠ΅ΡΡ ΠΌΡ ΠΈΠΌΠΏΠΎΡΡΠΈΡΡΠ΅ΠΌ Π΄ΡΡΠ³ΠΈΠ΅ ΡΡΠ±-ΠΌΠΎΠ΄ΡΠ»ΠΈ, ΡΠΎΠ΄Π΅ΡΠΆΠ°ΡΠΈΠ΅ `APIRouter`:
+
+```Python hl_lines="4-5" title="app/main.py"
+{!../../docs_src/bigger_applications/app/main.py!}
+```
+
+Π’Π°ΠΊ ΠΊΠ°ΠΊ ΡΠ°ΠΉΠ»Ρ `app/routers/users.py` ΠΈ `app/routers/items.py` ΡΠ²Π»ΡΡΡΡΡ ΡΡΠ±-ΠΌΠΎΠ΄ΡΠ»ΡΠΌΠΈ ΠΎΠ΄Π½ΠΎΠ³ΠΎ ΠΈ ΡΠΎΠ³ΠΎ ΠΆΠ΅ Python-ΠΏΠ°ΠΊΠ΅ΡΠ° `app`, ΡΠΎ ΠΌΡ ΡΠΌΠΎΠΆΠ΅ΠΌ ΠΈΡ
ΠΈΠΌΠΏΠΎΡΡΠΈΡΠΎΠ²Π°ΡΡ, Π²ΠΎΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π²ΡΠΈΡΡ ΠΎΠΏΠ΅ΡΠ°ΡΠΈΠ΅ΠΉ ΠΎΡΠ½ΠΎΡΠΈΡΠ΅Π»ΡΠ½ΠΎΠ³ΠΎ ΠΈΠΌΠΏΠΎΡΡΠ° `.`.
+
+### ΠΠ°ΠΊ ΡΠ°Π±ΠΎΡΠ°Π΅Ρ ΠΈΠΌΠΏΠΎΡΡ?
+
+ΠΠ°Π½Π½Π°Ρ ΡΡΡΠΎΠΊΠ° ΠΊΠΎΠ΄Π°:
+
+```Python
+from .routers import items, users
+```
+
+ΠΎΠ·Π½Π°ΡΠ°Π΅Ρ:
+
+* ΠΠ°ΡΠ½ΠΈΡΠ΅ Ρ ΠΏΠ°ΠΊΠ΅ΡΠ°, Π² ΠΊΠΎΡΠΎΡΠΎΠΌ ΡΠΎΠ΄Π΅ΡΠΆΠΈΡΡΡ Π΄Π°Π½Π½ΡΠΉ ΠΌΠΎΠ΄ΡΠ»Ρ (ΡΠ°ΠΉΠ» `app/main.py` ΡΠΎΠ΄Π΅ΡΠΆΠΈΡΡΡ Π² ΠΊΠ°ΡΠ°Π»ΠΎΠ³Π΅ `app/`)...
+* ... Π½Π°ΠΉΠ΄ΠΈΡΠ΅ ΡΡΠ±-ΠΏΠ°ΠΊΠ΅Ρ `routers` (ΠΊΠ°ΡΠ°Π»ΠΎΠ³ `app/routers/`)...
+* ... ΠΈ ΠΈΠ· Π½Π΅Π³ΠΎ ΠΈΠΌΠΏΠΎΡΡΠΈΡΡΠΉΡΠ΅ ΡΡΠ±-ΠΌΠΎΠ΄ΡΠ»ΠΈ `items` (ΡΠ°ΠΉΠ» `app/routers/items.py`) ΠΈ `users` (ΡΠ°ΠΉΠ» `app/routers/users.py`)...
+
+Π ΠΌΠΎΠ΄ΡΠ»Π΅ `items` ΡΠΎΠ΄Π΅ΡΠΆΠΈΡΡΡ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½Π°Ρ `router` (`items.router`), ΡΠ° ΡΠ°ΠΌΠ°Ρ, ΠΊΠΎΡΠΎΡΡΡ ΠΌΡ ΡΠΎΠ·Π΄Π°Π»ΠΈ Π² ΡΠ°ΠΉΠ»Π΅ `app/routers/items.py`, ΠΎΠ½Π° ΡΠ²Π»ΡΠ΅ΡΡΡ ΠΎΠ±ΡΠ΅ΠΊΡΠΎΠΌ ΠΊΠ»Π°ΡΡΠ° `APIRouter`.
+
+Π Π·Π°ΡΠ΅ΠΌ ΠΌΡ ΡΠ΄Π΅Π»Π°Π΅ΠΌ ΡΠΎ ΠΆΠ΅ ΡΠ°ΠΌΠΎΠ΅ Π΄Π»Ρ ΠΌΠΎΠ΄ΡΠ»Ρ `users`.
+
+ΠΡ ΡΠ°ΠΊΠΆΠ΅ ΠΌΠΎΠ³Π»ΠΈ Π±Ρ ΠΈΠΌΠΏΠΎΡΡΠΈΡΠΎΠ²Π°ΡΡ ΠΈ Π΄ΡΡΠ³ΠΈΠΌ ΠΌΠ΅ΡΠΎΠ΄ΠΎΠΌ:
+
+```Python
+from app.routers import items, users
+```
+
+/// info | ΠΡΠΈΠΌΠ΅ΡΠ°Π½ΠΈΠ΅
+
+ΠΠ΅ΡΠ²Π°Ρ Π²Π΅ΡΡΠΈΡ ΡΠ²Π»ΡΠ΅ΡΡΡ ΠΏΡΠΈΠΌΠ΅ΡΠΎΠΌ ΠΎΡΠ½ΠΎΡΠΈΡΠ΅Π»ΡΠ½ΠΎΠ³ΠΎ ΠΈΠΌΠΏΠΎΡΡΠ°:
+
+```Python
+from .routers import items, users
+```
+
+ΠΡΠΎΡΠ°Ρ Π²Π΅ΡΡΠΈΡ ΡΠ²Π»ΡΠ΅ΡΡΡ ΠΏΡΠΈΠΌΠ΅ΡΠΎΠΌ Π°Π±ΡΠΎΠ»ΡΡΠ½ΠΎΠ³ΠΎ ΠΈΠΌΠΏΠΎΡΡΠ°:
+
+```Python
+from app.routers import items, users
+```
+
+Π£Π·Π½Π°ΡΡ Π±ΠΎΠ»ΡΡΠ΅ ΠΎ ΠΏΠ°ΠΊΠ΅ΡΠ°Ρ
ΠΈ ΠΌΠΎΠ΄ΡΠ»ΡΡ
Π² Python Π²Ρ ΠΌΠΎΠΆΠ΅ΡΠ΅ ΠΈΠ· ΠΎΡΠΈΡΠΈΠ°Π»ΡΠ½ΠΎΠΉ Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ°ΡΠΈΠΈ Python ΠΎ ΠΌΠΎΠ΄ΡΠ»ΡΡ
+
+///
+
+### ΠΠ·Π±Π΅Π³Π°ΠΉΡΠ΅ ΠΊΠΎΠ½ΡΠ»ΠΈΠΊΡΠΎΠ² ΠΈΠΌΠ΅Π½
+
+ΠΠΌΠ΅ΡΡΠΎ ΡΠΎΠ³ΠΎ ΡΡΠΎΠ±Ρ ΠΈΠΌΠΏΠΎΡΡΠΈΡΠΎΠ²Π°ΡΡ ΡΠΎΠ»ΡΠΊΠΎ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΡΡ `router`, ΠΌΡ ΠΈΠΌΠΏΠΎΡΡΠΈΡΡΠ΅ΠΌ Π½Π΅ΠΏΠΎΡΡΠ΅Π΄ΡΡΠ²Π΅Π½Π½ΠΎ ΡΡΠ±-ΠΌΠΎΠ΄ΡΠ»Ρ `items`.
+
+ΠΡ Π΄Π΅Π»Π°Π΅ΠΌ ΡΡΠΎ ΠΏΠΎΡΠΎΠΌΡ, ΡΡΠΎ Ρ Π½Π°Ρ Π΅ΡΡΡ Π΅ΡΡ ΠΎΠ΄Π½Π° ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½Π°Ρ `router` Π² ΡΡΠ±-ΠΌΠΎΠ΄ΡΠ»Π΅ `users`.
+
+ΠΡΠ»ΠΈ Π±Ρ ΠΌΡ ΠΈΠΌΠΏΠΎΡΡΠΈΡΠΎΠ²Π°Π»ΠΈ ΠΈΡ
ΠΎΠ΄Π½Ρ Π·Π° Π΄ΡΡΠ³ΠΎΠΉ, ΠΊΠ°ΠΊ ΠΏΠΎΠΊΠ°Π·Π°Π½ΠΎ Π² ΠΏΡΠΈΠΌΠ΅ΡΠ΅:
+
+```Python
+from .routers.items import router
+from .routers.users import router
+```
+
+ΡΠΎ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½Π°Ρ `router` ΠΈΠ· `users` ΠΏΠ΅ΡΠ΅ΠΏΠΈΡΠ°Π» Π±Ρ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΡΡ `router` ΠΈΠ· `items`, ΠΈ Ρ Π½Π°Ρ Π½Π΅ Π±ΡΠ»ΠΎ Π±Ρ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡΠΈ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ ΠΈΡ
ΠΎΠ΄Π½ΠΎΠ²ΡΠ΅ΠΌΠ΅Π½Π½ΠΎ.
+
+ΠΠΎΡΡΠΎΠΌΡ, Π΄Π»Ρ ΡΠΎΠ³ΠΎ ΡΡΠΎΠ±Ρ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ ΠΎΠ±Π΅ ΡΡΠΈ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΡΠ΅ Π² ΠΎΠ΄Π½ΠΎΠΌ ΡΠ°ΠΉΠ»Π΅, ΠΌΡ ΠΈΠΌΠΏΠΎΡΡΠΈΡΠΎΠ²Π°Π»ΠΈ ΡΠΎΠΎΡΠ²Π΅ΡΡΡΠ²ΡΡΡΠΈΠ΅ ΡΡΠ±-ΠΌΠΎΠ΄ΡΠ»ΠΈ:
+
+```Python hl_lines="5" title="app/main.py"
+{!../../docs_src/bigger_applications/app/main.py!}
+```
+
+### ΠΠΎΠ΄ΠΊΠ»ΡΡΠ΅Π½ΠΈΠ΅ ΠΌΠ°ΡΡΡΡΡΠΈΠ·Π°ΡΠΎΡΠΎΠ² (`APIRouter`) Π΄Π»Ρ `users` ΠΈ Π΄Π»Ρ `items`
+
+ΠΠ°Π²Π°ΠΉΡΠ΅ ΠΏΠΎΠ΄ΠΊΠ»ΡΡΠΈΠΌ ΠΌΠ°ΡΡΡΡΡΠΈΠ·Π°ΡΠΎΡΡ (`router`) ΠΈΠ· ΡΡΠ±-ΠΌΠΎΠ΄ΡΠ»Π΅ΠΉ `users` ΠΈ `items`:
+
+```Python hl_lines="10-11" title="app/main.py"
+{!../../docs_src/bigger_applications/app/main.py!}
+```
+
+/// info | ΠΡΠΈΠΌΠ΅ΡΠ°Π½ΠΈΠ΅
+
+`users.router` ΡΠΎΠ΄Π΅ΡΠΆΠΈΡ `APIRouter` ΠΈΠ· ΡΠ°ΠΉΠ»Π° `app/routers/users.py`.
+
+Π `items.router` ΡΠΎΠ΄Π΅ΡΠΆΠΈΡ `APIRouter` ΠΈΠ· ΡΠ°ΠΉΠ»Π° `app/routers/items.py`.
+
+///
+
+Π‘ ΠΏΠΎΠΌΠΎΡΡΡ `app.include_router()` ΠΌΡ ΠΌΠΎΠΆΠ΅ΠΌ Π΄ΠΎΠ±Π°Π²ΠΈΡΡ ΠΊΠ°ΠΆΠ΄ΡΠΉ ΠΈΠ· ΠΌΠ°ΡΡΡΡΡΠΈΠ·Π°ΡΠΎΡΠΎΠ² (`APIRouter`) Π² ΠΎΡΠ½ΠΎΠ²Π½ΠΎΠ΅ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ `FastAPI`.
+
+ΠΠ½ ΠΏΠΎΠ΄ΠΊΠ»ΡΡΠΈΡ Π²ΡΠ΅ ΠΌΠ°ΡΡΡΡΡΡ Π·Π°Π΄Π°Π½Π½ΠΎΠ³ΠΎ ΠΌΠ°ΡΡΡΡΡΠΈΠ·Π°ΡΠΎΡΠ° ΠΊ Π½Π°ΡΠ΅ΠΌΡ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ.
+
+/// note | Π’Π΅Ρ
Π½ΠΈΡΠ΅ΡΠΊΠΈΠ΅ Π΄Π΅ΡΠ°Π»ΠΈ
+
+Π€Π°ΠΊΡΠΈΡΠ΅ΡΠΊΠΈ, Π²Π½ΡΡΡΠΈ ΠΎΠ½ ΡΠΎΠ·Π΄Π°ΡΡ Π²ΡΠ΅ *ΠΎΠΏΠ΅ΡΠ°ΡΠΈΠΈ ΠΏΡΡΠΈ* Π΄Π»Ρ ΠΊΠ°ΠΆΠ΄ΠΎΠΉ ΠΎΠΏΠ΅ΡΠ°ΡΠΈΠΈ ΠΏΡΡΠΈ ΠΎΠ±ΡΡΠ²Π»Π΅Π½Π½ΠΎΠΉ Π² `APIRouter`.
+
+Π ΠΏΠΎΠ΄ ΠΊΠ°ΠΏΠΎΡΠΎΠΌ Π²ΡΡ Π±ΡΠ΄Π΅Ρ ΡΠ°Π±ΠΎΡΠ°ΡΡ ΡΠ°ΠΊ, ΠΊΠ°ΠΊ Π±ΡΠ΄ΡΠΎ Π±Ρ ΠΌΡ ΠΈΠΌΠ΅Π΅ΠΌ Π΄Π΅Π»ΠΎ Ρ ΠΎΠ΄Π½ΠΈΠΌ ΡΠ°ΠΉΠ»ΠΎΠΌ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ.
+
+///
+
+/// check | ΠΠ°ΠΌΠ΅ΡΠΊΠ°
+
+ΠΡΠΈ ΠΏΠΎΠ΄ΠΊΠ»ΡΡΠ΅Π½ΠΈΠΈ ΠΌΠ°ΡΡΡΡΡΠΈΠ·Π°ΡΠΎΡΠΎΠ² Π½Π΅ ΡΡΠΎΠΈΡ Π±Π΅ΡΠΏΠΎΠΊΠΎΠΈΡΡΡΡ ΠΎ ΠΏΡΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡΠ΅Π»ΡΠ½ΠΎΡΡΠΈ.
+
+ΠΠΏΠ΅ΡΠ°ΡΠΈΡ ΠΏΠΎΠ΄ΠΊΠ»ΡΡΠ΅Π½ΠΈΡ Π·Π°ΠΉΠΌΡΡ ΠΌΠΈΠΊΡΠΎΡΠ΅ΠΊΡΠ½Π΄Ρ ΠΈ ΠΏΠΎΠ½Π°Π΄ΠΎΠ±ΠΈΡΡΡ ΡΠΎΠ»ΡΠΊΠΎ ΠΏΡΠΈ Π·Π°ΠΏΡΡΠΊΠ΅ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ.
+
+Π’Π°ΠΊΠΈΠΌ ΠΎΠ±ΡΠ°Π·ΠΎΠΌ, ΡΡΠΎ Π½Π΅ ΠΏΠΎΠ²Π»ΠΈΡΠ΅Ρ Π½Π° ΠΏΡΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡΠ΅Π»ΡΠ½ΠΎΡΡΡ. β‘
+
+///
+
+### ΠΠΎΠ΄ΠΊΠ»ΡΡΠ΅Π½ΠΈΠ΅ `APIRouter` Ρ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»ΡΡΠΊΠΈΠΌΠΈ ΠΏΡΠ΅ΡΠΈΠΊΡΠΎΠΌ (`prefix`), ΡΠ΅Π³Π°ΠΌΠΈ (`tags`), ΠΎΡΠ²Π΅ΡΠ°ΠΌΠΈ (`responses`), ΠΈ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΡΠΌΠΈ (`dependencies`)
+
+Π’Π΅ΠΏΠ΅ΡΡ Π΄Π°Π²Π°ΠΉΡΠ΅ ΠΏΡΠ΅Π΄ΡΡΠ°Π²ΠΈΠΌ, ΡΡΠΎ Π²Π°ΡΠ° ΠΎΡΠ³Π°Π½ΠΈΠ·Π°ΡΠΈΡ ΠΏΠ΅ΡΠ΅Π΄Π°Π»Π° Π²Π°ΠΌ ΡΠ°ΠΉΠ» `app/internal/admin.py`.
+
+ΠΠ½ ΡΠΎΠ΄Π΅ΡΠΆΠΈΡ `APIRouter` Ρ Π½Π΅ΠΊΠΎΡΠΎΡΡΠΌΠΈ *ΡΠ½Π΄ΠΏΠΎΠΈΡΠ°ΠΌΠΈ* Π°Π΄ΠΌΠΈΠ½ΠΈΡΡΡΠΈΡΠΎΠ²Π°Π½ΠΈΡ, ΠΊΠΎΡΠΎΡΡΠ΅ Π²Π°ΡΠ° ΠΎΡΠ³Π°Π½ΠΈΠ·Π°ΡΠΈΡ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅Ρ Π΄Π»Ρ Π½Π΅ΡΠΊΠΎΠ»ΡΠΊΠΈΡ
ΠΏΡΠΎΠ΅ΠΊΡΠΎΠ².
+
+Π Π΄Π°Π½Π½ΠΎΠΌ ΠΏΡΠΈΠΌΠ΅ΡΠ΅ ΡΡΠΎ ΡΠ΄Π΅Π»Π°ΡΡ ΠΎΡΠ΅Π½Ρ ΠΏΡΠΎΡΡΠΎ. ΠΠΎ Π΄Π°Π²Π°ΠΉΡΠ΅ ΠΏΡΠ΅Π΄ΠΏΠΎΠ»ΠΎΠΆΠΈΠΌ, ΡΡΠΎ ΠΏΠΎΡΠΊΠΎΠ»ΡΠΊΡ ΡΠ°ΠΉΠ» ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΡΡΡ Π΄Π»Ρ Π½Π΅ΡΠΊΠΎΠ»ΡΠΊΠΈΡ
ΠΏΡΠΎΠ΅ΠΊΡΠΎΠ²,
+ΡΠΎ ΠΌΡ Π½Π΅ ΠΌΠΎΠΆΠ΅ΠΌ ΠΌΠΎΠ΄ΠΈΡΠΈΡΠΈΡΠΎΠ²Π°ΡΡ Π΅Π³ΠΎ, Π΄ΠΎΠ±Π°Π²Π»ΡΡ ΠΏΡΠ΅ΡΠΈΠΊΡΡ (`prefix`), Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠΈ (`dependencies`), ΡΠ΅Π³ΠΈ (`tags`), ΠΈ Ρ.Π΄. Π½Π΅ΠΏΠΎΡΡΠ΅Π΄ΡΡΠ²Π΅Π½Π½ΠΎ Π² `APIRouter`:
+
+```Python hl_lines="3" title="app/internal/admin.py"
+{!../../docs_src/bigger_applications/app/internal/admin.py!}
+```
+
+ΠΠΎ, Π½Π΅ΡΠΌΠΎΡΡΡ Π½Π° ΡΡΠΎ, ΠΌΡ Ρ
ΠΎΡΠΈΠΌ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ ΠΊΠ°ΡΡΠΎΠΌΠ½ΡΠΉ ΠΏΡΠ΅ΡΠΈΠΊΡ (`prefix`) Π΄Π»Ρ ΠΏΠΎΠ΄ΠΊΠ»ΡΡΠ΅Π½Π½ΠΎΠ³ΠΎ ΠΌΠ°ΡΡΡΡΡΠΈΠ·Π°ΡΠΎΡΠ° (`APIRouter`), Π² ΡΠ΅Π·ΡΠ»ΡΡΠ°ΡΠ΅ ΡΠ΅Π³ΠΎ, ΠΊΠ°ΠΆΠ΄Π°Ρ *ΠΎΠΏΠ΅ΡΠ°ΡΠΈΡ ΠΏΡΡΠΈ* Π±ΡΠ΄Π΅Ρ Π½Π°ΡΠΈΠ½Π°ΡΡΡΡ Ρ `/admin`. Π’Π°ΠΊΠΆΠ΅ ΠΌΡ Ρ
ΠΎΡΠΈΠΌ Π·Π°ΡΠΈΡΠΈΡΡ Π½Π°Ρ ΠΌΠ°ΡΡΡΡΡΠΈΠ·Π°ΡΠΎΡ Ρ ΠΏΠΎΠΌΠΎΡΡΡ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠ΅ΠΉ, ΡΠΎΠ·Π΄Π°Π½Π½ΡΡ
Π΄Π»Ρ Π½Π°ΡΠ΅Π³ΠΎ ΠΏΡΠΎΠ΅ΠΊΡΠ°. Π Π΅ΡΡ ΠΌΡ Ρ
ΠΎΡΠΈΠΌ Π²ΠΊΠ»ΡΡΠΈΡΡ ΡΠ΅Π³ΠΈ (`tags`) ΠΈ ΠΎΡΠ²Π΅ΡΡ (`responses`).
+
+ΠΡ ΠΌΠΎΠΆΠ΅ΠΌ ΠΏΡΠΈΠΌΠ΅Π½ΠΈΡΡ Π²ΡΠ΅ Π²ΡΡΠ΅ΠΏΠ΅ΡΠ΅ΡΠΈΡΠ»Π΅Π½Π½ΡΠ΅ Π½Π°ΡΡΡΠΎΠΉΠΊΠΈ, Π½Π΅ ΠΈΠ·ΠΌΠ΅Π½ΡΡ Π½Π°ΡΠ°Π»ΡΠ½ΡΠΉ `APIRouter`. ΠΠ°ΠΌ Π²ΡΠ΅Π³ΠΎ Π»ΠΈΡΡ Π½ΡΠΆΠ½ΠΎ ΠΏΠ΅ΡΠ΅Π΄Π°ΡΡ Π½ΡΠΆΠ½ΡΠ΅ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡΡ Π² `app.include_router()`.
+
+```Python hl_lines="14-17" title="app/main.py"
+{!../../docs_src/bigger_applications/app/main.py!}
+```
+
+Π’Π°ΠΊΠΈΠΌ ΠΎΠ±ΡΠ°Π·ΠΎΠΌ, ΠΎΡΠΈΠ³ΠΈΠ½Π°Π»ΡΠ½ΡΠΉ `APIRouter` Π½Π΅ Π±ΡΠ΄Π΅Ρ ΠΌΠΎΠ΄ΠΈΡΠΈΡΠΈΡΠΎΠ²Π°Π½, ΠΈ ΠΌΡ ΡΠΌΠΎΠΆΠ΅ΠΌ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ ΡΠ°ΠΉΠ» `app/internal/admin.py` ΡΡΠ°Π·Ρ Π² Π½Π΅ΡΠΊΠΎΠ»ΡΠΊΠΈΡ
ΠΏΡΠΎΠ΅ΠΊΡΠ°Ρ
ΠΎΡΠ³Π°Π½ΠΈΠ·Π°ΡΠΈΠΈ.
+
+Π ΡΠ΅Π·ΡΠ»ΡΡΠ°ΡΠ΅, Π² Π½Π°ΡΠ΅ΠΌ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΈ ΠΊΠ°ΠΆΠ΄ΡΠΉ *ΡΠ½Π΄ΠΏΠΎΠΈΠ½Ρ* ΠΌΠΎΠ΄ΡΠ»Ρ `admin` Π±ΡΠ΄Π΅Ρ ΠΈΠΌΠ΅ΡΡ:
+
+* ΠΡΠ΅ΡΠΈΠΊΡ `/admin`.
+* Π’Π΅Π³ `admin`.
+* ΠΠ°Π²ΠΈΡΠΈΠΌΠΎΡΡΡ `get_token_header`.
+* ΠΡΠ²Π΅Ρ `418`. π΅
+
+ΠΡΠΎ Π±ΡΠ΄Π΅Ρ ΠΈΠΌΠ΅ΡΡ ΠΌΠ΅ΡΡΠΎ ΠΈΡΠΊΠ»ΡΡΠΈΡΠ΅Π»ΡΠ½ΠΎ Π΄Π»Ρ `APIRouter` Π² Π½Π°ΡΠ΅ΠΌ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΈ, ΠΈ Π½Π΅ Π·Π°ΡΡΠΎΠ½Π΅Ρ Π»ΡΠ±ΠΎΠΉ Π΄ΡΡΠ³ΠΎΠΉ ΠΊΠΎΠ΄, ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΡΡΠΈΠΉ Π΅Π³ΠΎ.
+
+ΠΠ°ΠΏΡΠΈΠΌΠ΅Ρ, Π΄ΡΡΠ³ΠΈΠ΅ ΠΏΡΠΎΠ΅ΠΊΡΡ, ΠΌΠΎΠ³ΡΡ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ ΡΠΎΡ ΠΆΠ΅ ΡΠ°ΠΌΡΠΉ `APIRouter` Ρ Π΄ΡΡΠ³ΠΈΠΌΠΈ ΠΌΠ΅ΡΠΎΠ΄Π°ΠΌΠΈ Π°ΡΡΠ΅Π½ΡΠΈΡΠΈΠΊΠ°ΡΠΈΠΈ.
+
+### ΠΠΎΠ΄ΠΊΠ»ΡΡΠ΅Π½ΠΈΠ΅ ΠΎΡΠ΄Π΅Π»ΡΠ½ΠΎΠ³ΠΎ *ΡΠ½Π΄ΠΏΠΎΠΈΠ½ΡΠ°*
+
+ΠΡ ΡΠ°ΠΊΠΆΠ΅ ΠΌΠΎΠΆΠ΅ΠΌ Π΄ΠΎΠ±Π°Π²ΠΈΡΡ *ΡΠ½Π΄ΠΏΠΎΠΈΠ½Ρ* Π½Π΅ΠΏΠΎΡΡΠ΅Π΄ΡΡΠ²Π΅Π½Π½ΠΎ Π² ΠΎΡΠ½ΠΎΠ²Π½ΠΎΠ΅ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ `FastAPI`.
+
+ΠΠ΄Π΅ΡΡ ΠΌΡ ΡΡΠΎ Π΄Π΅Π»Π°Π΅ΠΌ ... ΠΏΡΠΎΡΡΠΎ, ΡΡΠΎΠ±Ρ ΠΏΠΎΠΊΠ°Π·Π°ΡΡ, ΡΡΠΎ ΡΡΠΎ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎ π€·:
+
+```Python hl_lines="21-23" title="app/main.py"
+{!../../docs_src/bigger_applications/app/main.py!}
+```
+
+ΠΈ ΡΡΠΎ Π±ΡΠ΄Π΅Ρ ΡΠ°Π±ΠΎΡΠ°ΡΡ ΠΊΠΎΡΡΠ΅ΠΊΡΠ½ΠΎ Π²ΠΌΠ΅ΡΡΠ΅ Ρ Π΄ΡΡΠ³ΠΈΠΌΠΈ *ΡΠ½Π΄ΠΏΠΎΠΈΠ½ΡΠ°ΠΌΠΈ*, Π΄ΠΎΠ±Π°Π²Π»Π΅Π½Π½ΡΠΌΠΈ Ρ ΠΏΠΎΠΌΠΎΡΡΡ `app.include_router()`.
+
+/// info | Π‘Π»ΠΎΠΆΠ½ΡΠ΅ ΡΠ΅Ρ
Π½ΠΈΡΠ΅ΡΠΊΠΈΠ΅ Π΄Π΅ΡΠ°Π»ΠΈ
+
+**ΠΡΠΈΠΌΠ΅ΡΠ°Π½ΠΈΠ΅**: ΡΡΠΎ ΡΠ»ΠΎΠΆΠ½Π°Ρ ΡΠ΅Ρ
Π½ΠΈΡΠ΅ΡΠΊΠ°Ρ Π΄Π΅ΡΠ°Π»Ρ, ΠΊΠΎΡΠΎΡΡΡ, ΡΠΊΠΎΡΠ΅Π΅ Π²ΡΠ΅Π³ΠΎ, **Π²Ρ ΠΌΠΎΠΆΠ΅ΡΠ΅ ΠΏΡΠΎΠΏΡΡΡΠΈΡΡ**.
+
+---
+
+ΠΠ°ΡΡΡΡΡΠΈΠ·Π°ΡΠΎΡΡ (`APIRouter`) Π½Π΅ "ΠΌΠΎΠ½ΡΠΈΡΡΡΡΡΡ" ΠΏΠΎ-ΠΎΡΠ΄Π΅Π»ΡΠ½ΠΎΡΡΠΈ ΠΈ Π½Π΅ ΠΈΠ·ΠΎΠ»ΠΈΡΡΡΡΡΡ ΠΎΡ ΠΎΡΡΠ°Π»ΡΠ½ΠΎΠ³ΠΎ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ.
+
+ΠΡΠΎ ΠΏΡΠΎΠΈΡΡ
ΠΎΠ΄ΠΈΡ ΠΏΠΎΡΠΎΠΌΡ, ΡΡΠΎ Π½ΡΠΆΠ½ΠΎ Π²ΠΊΠ»ΡΡΠΈΡΡ ΠΈΡ
*ΡΠ½Π΄ΠΏΠΎΠΈΠ½ΡΡ* Π² OpenAPI ΡΡ
Π΅ΠΌΡ ΠΈ Π² ΠΈΠ½ΡΠ΅ΡΡΠ΅ΠΉΡ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Ρ.
+
+Π ΡΠΈΠ»Ρ ΡΠΎΠ³ΠΎ, ΡΡΠΎ ΠΌΡ Π½Π΅ ΠΌΠΎΠΆΠ΅ΠΌ ΠΈΡ
ΠΈΠ·ΠΎΠ»ΠΈΡΠΎΠ²Π°ΡΡ ΠΈ "ΠΏΡΠΈΠΌΠΎΠ½ΡΠΈΡΠΎΠ²Π°ΡΡ" Π½Π΅Π·Π°Π²ΠΈΡΠΈΠΌΠΎ ΠΎΡ ΠΎΡΡΠ°Π»ΡΠ½ΡΡ
, *ΡΠ½Π΄ΠΏΠΎΠΈΠ½ΡΡ* ΠΊΠ»ΠΎΠ½ΠΈΡΡΡΡΡΡ (ΠΏΠ΅ΡΠ΅ΡΠΎΠ·Π΄Π°ΡΡΡΡ) ΠΈ Π½Π΅ ΠΏΠΎΠ΄ΠΊΠ»ΡΡΠ°ΡΡΡΡ Π½Π°ΠΏΡΡΠΌΡΡ.
+
+///
+
+## ΠΡΠΎΠ²Π΅ΡΠΊΠ° Π°Π²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠΎΠΉ Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ°ΡΠΈΠΈ API
+
+Π’Π΅ΠΏΠ΅ΡΡ Π·Π°ΠΏΡΡΡΠΈΡΠ΅ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅:
+
+
+
+```console
+$ fastapi dev app/main.py
+
+INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
+```
+
+
+
+ΠΡΠΊΡΠΎΠΉΡΠ΅ Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ°ΡΠΈΡ ΠΏΠΎ Π°Π΄ΡΠ΅ΡΡ http://127.0.0.1:8000/docs.
+
+ΠΡ ΡΠ²ΠΈΠ΄ΠΈΡΠ΅ Π°Π²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΡΡ API Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ°ΡΠΈΡ. ΠΠ½Π° Π²ΠΊΠ»ΡΡΠ°Π΅Ρ Π² ΡΠ΅Π±Ρ ΠΌΠ°ΡΡΡΡΡΡ ΠΈΠ· ΡΡΠ±-ΠΌΠΎΠ΄ΡΠ»Π΅ΠΉ, ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΡ Π²Π΅ΡΠ½ΡΠ΅ ΠΌΠ°ΡΡΡΡΡΡ, ΠΏΡΠ΅ΡΠΈΠΊΡΡ ΠΈ ΡΠ΅Π³ΠΈ:
+
+
+
+## ΠΠΎΠ΄ΠΊΠ»ΡΡΠ΅Π½ΠΈΠ΅ ΡΡΡΠ΅ΡΡΠ²ΡΡΡΠ΅Π³ΠΎ ΠΌΠ°ΡΡΡΡΡΠ° ΡΠ΅ΡΠ΅Π· Π½ΠΎΠ²ΡΠΉ ΠΏΡΠ΅ΡΠΈΠΊΡ (`prefix`)
+
+ΠΡ ΠΌΠΎΠΆΠ΅ΡΠ΅ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ `.include_router()` Π½Π΅ΡΠΊΠΎΠ»ΡΠΊΠΎ ΡΠ°Π· Ρ ΠΎΠ΄Π½ΠΈΠΌ ΠΈ ΡΠ΅ΠΌ ΠΆΠ΅ ΠΌΠ°ΡΡΡΡΡΠΎΠΌ, ΠΏΡΠΈΠΌΠ΅Π½ΠΈΠ² ΡΠ°Π·Π»ΠΈΡΠ½ΡΠ΅ ΠΏΡΠ΅ΡΠΈΠΊΡΡ.
+
+ΠΡΠΎ ΠΌΠΎΠΆΠ΅Ρ Π±ΡΡΡ ΠΏΠΎΠ»Π΅Π·Π½ΡΠΌ, Π΅ΡΠ»ΠΈ Π½ΡΠΆΠ½ΠΎ ΠΏΡΠ΅Π΄ΠΎΡΡΠ°Π²ΠΈΡΡ Π΄ΠΎΡΡΡΠΏ ΠΊ ΠΎΠ΄Π½ΠΎΠΌΡ ΠΈ ΡΠΎΠΌΡ ΠΆΠ΅ API ΡΠ΅ΡΠ΅Π· ΡΠ°Π·Π»ΠΈΡΠ½ΡΠ΅ ΠΏΡΠ΅ΡΠΈΠΊΡΡ, Π½Π°ΠΏΡΠΈΠΌΠ΅Ρ, `/api/v1` ΠΈ `/api/latest`.
+
+ΠΡΠΎ ΠΏΡΠΎΠ΄Π²ΠΈΠ½ΡΡΡΠΉ ΡΠΏΠΎΡΠΎΠ±, ΠΊΠΎΡΠΎΡΡΠΉ Π²Π°ΠΌ ΠΌΠΎΠΆΠ΅Ρ ΠΈ Π½Π΅ ΠΏΡΠΈΠ³ΠΎΠ΄ΠΈΡΡΡ. ΠΡ ΠΏΡΠΈΠ²ΠΎΠ΄ΠΈΠΌ Π΅Π³ΠΎ Π½Π° ΡΠ»ΡΡΠ°ΠΉ, Π΅ΡΠ»ΠΈ Π²Π΄ΡΡΠ³ Π²Π°ΠΌ ΡΡΠΎ ΠΏΠΎΠ½Π°Π΄ΠΎΠ±ΠΈΡΡΡ.
+
+## ΠΠΊΠ»ΡΡΠ΅Π½ΠΈΠ΅ ΠΎΠ΄Π½ΠΎΠ³ΠΎ ΠΌΠ°ΡΡΡΡΡΠΈΠ·Π°ΡΠΎΡΠ° (`APIRouter`) Π² Π΄ΡΡΠ³ΠΎΠΉ
+
+Π’ΠΎΡΠ½ΠΎ ΡΠ°ΠΊ ΠΆΠ΅, ΠΊΠ°ΠΊ Π²Ρ Π²ΠΊΠ»ΡΡΠ°Π΅ΡΠ΅ `APIRouter` Π² ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ `FastAPI`, Π²Ρ ΠΌΠΎΠΆΠ΅ΡΠ΅ Π²ΠΊΠ»ΡΡΠΈΡΡ `APIRouter` Π² Π΄ΡΡΠ³ΠΎΠΉ `APIRouter`:
+
+```Python
+router.include_router(other_router)
+```
+
+Π£Π΄ΠΎΡΡΠΎΠ²Π΅ΡΡΡΠ΅ΡΡ, ΡΡΠΎ Π²Ρ ΡΠ΄Π΅Π»Π°Π»ΠΈ ΡΡΠΎ Π΄ΠΎ ΡΠΎΠ³ΠΎ, ΠΊΠ°ΠΊ ΠΏΠΎΠ΄ΠΊΠ»ΡΡΠΈΡΡ ΠΌΠ°ΡΡΡΡΡΠΈΠ·Π°ΡΠΎΡ (`router`) ΠΊ Π²Π°ΡΠ΅ΠΌΡ `FastAPI` ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ, ΠΈ *ΡΠ½Π΄ΠΏΠΎΠΈΠ½ΡΡ* ΠΌΠ°ΡΡΡΡΡΠΈΠ·Π°ΡΠΎΡΠ° `other_router` Π±ΡΠ»ΠΈ ΡΠ°ΠΊΠΆΠ΅ ΠΏΠΎΠ΄ΠΊΠ»ΡΡΠ΅Π½Ρ.
diff --git a/docs/ru/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md b/docs/ru/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md
index f9b9dec25..0e4eb95be 100644
--- a/docs/ru/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md
+++ b/docs/ru/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md
@@ -18,7 +18,7 @@
ΠΠ°Π²ΠΈΡΠΈΠΌΠΎΡΡΠΈ ΠΈΠ· dependencies Π²ΡΠΏΠΎΠ»Π½ΡΡΡΡ ΡΠ°ΠΊ ΠΆΠ΅, ΠΊΠ°ΠΊ ΠΈ ΠΎΠ±ΡΡΠ½ΡΠ΅ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠΈ. ΠΠΎ ΠΈΡ
Π·Π½Π°ΡΠ΅Π½ΠΈΡ (Π΅ΡΠ»ΠΈ ΠΎΠ½ΠΈ Π±ΡΠ»ΠΈ) Π½Π΅ Π±ΡΠ΄ΡΡ ΠΏΠ΅ΡΠ΅Π΄Π°Π½Ρ Π² *ΡΡΠ½ΠΊΡΠΈΡ ΠΎΠΏΠ΅ΡΠ°ΡΠΈΠΈ ΠΏΡΡΠΈ*.
-/// ΠΏΠΎΠ΄ΡΠΊΠ°Π·ΠΊΠ°
+/// tip | ΠΠΎΠ΄ΡΠΊΠ°Π·ΠΊΠ°
ΠΠ΅ΠΊΠΎΡΠΎΡΡΠ΅ ΡΠ΅Π΄Π°ΠΊΡΠΎΡΡ ΠΊΠΎΠ΄Π° ΠΎΠΏΡΠ΅Π΄Π΅Π»ΡΡΡ Π½Π΅ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌΡΠ΅ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡΡ ΡΡΠ½ΠΊΡΠΈΠΉ ΠΈ ΠΏΠΎΠ΄ΡΠ²Π΅ΡΠΈΠ²Π°ΡΡ ΠΈΡ
ΠΊΠ°ΠΊ ΠΎΡΠΈΠ±ΠΊΡ.
@@ -28,7 +28,7 @@
///
-/// Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡΠ΅Π»ΡΠ½Π°Ρ | ΠΈΠ½ΡΠΎΡΠΌΠ°ΡΠΈΡ
+/// info | ΠΡΠΈΠΌΠ΅ΡΠ°Π½ΠΈΠ΅
Π ΡΡΠΎΠΌ ΠΏΡΠΈΠΌΠ΅ΡΠ΅ ΠΌΡ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌ Π²ΡΠ΄ΡΠΌΠ°Π½Π½ΡΠ΅ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»ΡΡΠΊΠΈΠ΅ Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠΈ `X-Key` ΠΈ `X-Token`.
diff --git a/fastapi/__init__.py b/fastapi/__init__.py
index 823957822..c92279cfd 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.115.6"
+__version__ = "0.115.7"
from starlette import status as status
diff --git a/pyproject.toml b/pyproject.toml
index edfa81522..381eb50bf 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -29,6 +29,7 @@ classifiers = [
"Framework :: FastAPI",
"Framework :: Pydantic",
"Framework :: Pydantic :: 1",
+ "Framework :: Pydantic :: 2",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3 :: Only",
@@ -41,7 +42,7 @@ classifiers = [
"Topic :: Internet :: WWW/HTTP",
]
dependencies = [
- "starlette>=0.40.0,<0.42.0",
+ "starlette>=0.40.0,<0.46.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",
]
@@ -60,9 +61,9 @@ standard = [
# For the test client
"httpx >=0.23.0",
# For templates
- "jinja2 >=2.11.2",
+ "jinja2 >=3.1.5",
# For forms and file uploads
- "python-multipart >=0.0.7",
+ "python-multipart >=0.0.18",
# To validate email fields
"email-validator >=2.0.0",
# Uvicorn with uvloop
@@ -79,9 +80,9 @@ all = [
# # For the test client
"httpx >=0.23.0",
# For templates
- "jinja2 >=2.11.2",
+ "jinja2 >=3.1.5",
# For forms and file uploads
- "python-multipart >=0.0.7",
+ "python-multipart >=0.0.18",
# For Starlette's SessionMiddleware, not commonly used with FastAPI
"itsdangerous >=1.1.0",
# For Starlette's schema generation, would not be used with FastAPI
diff --git a/tests/test_tutorial/test_request_form_models/test_tutorial001.py b/tests/test_tutorial/test_request_form_models/test_tutorial001.py
index 46c130ee8..1ca3c96d3 100644
--- a/tests/test_tutorial/test_request_form_models/test_tutorial001.py
+++ b/tests/test_tutorial/test_request_form_models/test_tutorial001.py
@@ -1,13 +1,24 @@
+import importlib
+
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
+from ...utils import needs_py39
+
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.request_form_models.tutorial001 import app
+@pytest.fixture(
+ name="client",
+ params=[
+ "tutorial001",
+ "tutorial001_an",
+ pytest.param("tutorial001_an_py39", marks=needs_py39),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.request_form_models.{request.param}")
- client = TestClient(app)
+ client = TestClient(mod.app)
return client
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
deleted file mode 100644
index 4e14d89c8..000000000
--- a/tests/test_tutorial/test_request_form_models/test_tutorial001_an.py
+++ /dev/null
@@ -1,232 +0,0 @@
-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
deleted file mode 100644
index 2e6426aa7..000000000
--- a/tests/test_tutorial/test_request_form_models/test_tutorial001_an_py39.py
+++ /dev/null
@@ -1,240 +0,0 @@
-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
index 76f480001..b3f6be63a 100644
--- a/tests/test_tutorial/test_request_form_models/test_tutorial002.py
+++ b/tests/test_tutorial/test_request_form_models/test_tutorial002.py
@@ -1,14 +1,23 @@
+import importlib
+
import pytest
from fastapi.testclient import TestClient
-from tests.utils import needs_pydanticv2
+from ...utils import needs_py39, needs_pydanticv2
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.request_form_models.tutorial002 import app
+@pytest.fixture(
+ name="client",
+ params=[
+ "tutorial002",
+ "tutorial002_an",
+ pytest.param("tutorial002_an_py39", marks=needs_py39),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.request_form_models.{request.param}")
- client = TestClient(app)
+ client = TestClient(mod.app)
return client
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
deleted file mode 100644
index 179b2977d..000000000
--- a/tests/test_tutorial/test_request_form_models/test_tutorial002_an.py
+++ /dev/null
@@ -1,196 +0,0 @@
-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
deleted file mode 100644
index 510ad9d7c..000000000
--- a/tests/test_tutorial/test_request_form_models/test_tutorial002_an_py39.py
+++ /dev/null
@@ -1,203 +0,0 @@
-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
index 249b9379d..b503f23a5 100644
--- a/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1.py
+++ b/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1.py
@@ -1,17 +1,27 @@
+import importlib
+
import pytest
from fastapi.testclient import TestClient
-from tests.utils import needs_pydanticv1
+from ...utils import needs_py39, needs_pydanticv1
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.request_form_models.tutorial002_pv1 import app
+@pytest.fixture(
+ name="client",
+ params=[
+ "tutorial002_pv1",
+ "tutorial002_pv1_an",
+ pytest.param("tutorial002_pv1_an_py39", marks=needs_py39),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.request_form_models.{request.param}")
- client = TestClient(app)
+ client = TestClient(mod.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"})
@@ -19,6 +29,7 @@ def test_post_body_form(client: TestClient):
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(
@@ -36,6 +47,7 @@ def test_post_body_extra_form(client: TestClient):
}
+# TODO: remove when deprecating Pydantic v1
@needs_pydanticv1
def test_post_body_form_no_password(client: TestClient):
response = client.post("/login/", data={"username": "Foo"})
@@ -51,6 +63,7 @@ def test_post_body_form_no_password(client: TestClient):
}
+# TODO: remove when deprecating Pydantic v1
@needs_pydanticv1
def test_post_body_form_no_username(client: TestClient):
response = client.post("/login/", data={"password": "secret"})
@@ -66,6 +79,7 @@ def test_post_body_form_no_username(client: TestClient):
}
+# TODO: remove when deprecating Pydantic v1
@needs_pydanticv1
def test_post_body_form_no_data(client: TestClient):
response = client.post("/login/")
@@ -86,6 +100,7 @@ def test_post_body_form_no_data(client: TestClient):
}
+# TODO: remove when deprecating Pydantic v1
@needs_pydanticv1
def test_post_body_json(client: TestClient):
response = client.post("/login/", json={"username": "Foo", "password": "secret"})
@@ -106,6 +121,7 @@ def test_post_body_json(client: TestClient):
}
+# TODO: remove when deprecating Pydantic v1
@needs_pydanticv1
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
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
deleted file mode 100644
index 44cb3c32b..000000000
--- a/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1_an.py
+++ /dev/null
@@ -1,196 +0,0 @@
-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
deleted file mode 100644
index 899549e40..000000000
--- a/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1_an_p39.py
+++ /dev/null
@@ -1,203 +0,0 @@
-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"},
- }
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial001.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial001.py
index 98b187355..c21cbb4bc 100644
--- a/tests/test_tutorial/test_schema_extra_example/test_tutorial001.py
+++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial001.py
@@ -1,14 +1,22 @@
+import importlib
+
import pytest
from fastapi.testclient import TestClient
-from ...utils import needs_pydanticv2
+from ...utils import needs_py310, needs_pydanticv2
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.schema_extra_example.tutorial001 import app
+@pytest.fixture(
+ name="client",
+ params=[
+ "tutorial001",
+ pytest.param("tutorial001_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.schema_extra_example.{request.param}")
- client = TestClient(app)
+ client = TestClient(mod.app)
return client
diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py
index 3520ef61d..b79f42e64 100644
--- a/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py
+++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py
@@ -1,14 +1,22 @@
+import importlib
+
import pytest
from fastapi.testclient import TestClient
-from ...utils import needs_pydanticv1
+from ...utils import needs_py310, needs_pydanticv1
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.schema_extra_example.tutorial001_pv1 import app
+@pytest.fixture(
+ name="client",
+ params=[
+ "tutorial001_pv1",
+ pytest.param("tutorial001_pv1_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.schema_extra_example.{request.param}")
- client = TestClient(app)
+ client = TestClient(mod.app)
return client
diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1_py310.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1_py310.py
deleted file mode 100644
index b2a4d15b1..000000000
--- a/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1_py310.py
+++ /dev/null
@@ -1,129 +0,0 @@
-import pytest
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py310, needs_pydanticv1
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.schema_extra_example.tutorial001_pv1_py310 import app
-
- client = TestClient(app)
- return client
-
-
-@needs_py310
-@needs_pydanticv1
-def test_post_body_example(client: TestClient):
- response = client.put(
- "/items/5",
- json={
- "name": "Foo",
- "description": "A very nice Item",
- "price": 35.4,
- "tax": 3.2,
- },
- )
- assert response.status_code == 200
-
-
-@needs_py310
-@needs_pydanticv1
-def test_openapi_schema(client: TestClient):
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- # insert_assert(response.json())
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/{item_id}": {
- "put": {
- "summary": "Update Item",
- "operationId": "update_item_items__item_id__put",
- "parameters": [
- {
- "required": True,
- "schema": {"type": "integer", "title": "Item Id"},
- "name": "item_id",
- "in": "path",
- }
- ],
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- "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": {
- "HTTPValidationError": {
- "properties": {
- "detail": {
- "items": {"$ref": "#/components/schemas/ValidationError"},
- "type": "array",
- "title": "Detail",
- }
- },
- "type": "object",
- "title": "HTTPValidationError",
- },
- "Item": {
- "properties": {
- "name": {"type": "string", "title": "Name"},
- "description": {"type": "string", "title": "Description"},
- "price": {"type": "number", "title": "Price"},
- "tax": {"type": "number", "title": "Tax"},
- },
- "type": "object",
- "required": ["name", "price"],
- "title": "Item",
- "examples": [
- {
- "name": "Foo",
- "description": "A very nice Item",
- "price": 35.4,
- "tax": 3.2,
- }
- ],
- },
- "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_tutorial/test_schema_extra_example/test_tutorial001_py310.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial001_py310.py
deleted file mode 100644
index e63e33cda..000000000
--- a/tests/test_tutorial/test_schema_extra_example/test_tutorial001_py310.py
+++ /dev/null
@@ -1,135 +0,0 @@
-import pytest
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py310, needs_pydanticv2
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.schema_extra_example.tutorial001_py310 import app
-
- client = TestClient(app)
- return client
-
-
-@needs_py310
-@needs_pydanticv2
-def test_post_body_example(client: TestClient):
- response = client.put(
- "/items/5",
- json={
- "name": "Foo",
- "description": "A very nice Item",
- "price": 35.4,
- "tax": 3.2,
- },
- )
- assert response.status_code == 200
-
-
-@needs_py310
-@needs_pydanticv2
-def test_openapi_schema(client: TestClient):
- response = client.get("/openapi.json")
- assert response.status_code == 200, response.text
- # insert_assert(response.json())
- assert response.json() == {
- "openapi": "3.1.0",
- "info": {"title": "FastAPI", "version": "0.1.0"},
- "paths": {
- "/items/{item_id}": {
- "put": {
- "summary": "Update Item",
- "operationId": "update_item_items__item_id__put",
- "parameters": [
- {
- "name": "item_id",
- "in": "path",
- "required": True,
- "schema": {"type": "integer", "title": "Item Id"},
- }
- ],
- "requestBody": {
- "required": True,
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- },
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- }
- }
- },
- "components": {
- "schemas": {
- "HTTPValidationError": {
- "properties": {
- "detail": {
- "items": {"$ref": "#/components/schemas/ValidationError"},
- "type": "array",
- "title": "Detail",
- }
- },
- "type": "object",
- "title": "HTTPValidationError",
- },
- "Item": {
- "properties": {
- "name": {"type": "string", "title": "Name"},
- "description": {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Description",
- },
- "price": {"type": "number", "title": "Price"},
- "tax": {
- "anyOf": [{"type": "number"}, {"type": "null"}],
- "title": "Tax",
- },
- },
- "type": "object",
- "required": ["name", "price"],
- "title": "Item",
- "examples": [
- {
- "description": "A very nice Item",
- "name": "Foo",
- "price": 35.4,
- "tax": 3.2,
- }
- ],
- },
- "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_tutorial/test_schema_extra_example/test_tutorial004.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial004.py
index eac0d1e29..61aefd12a 100644
--- a/tests/test_tutorial/test_schema_extra_example/test_tutorial004.py
+++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial004.py
@@ -1,13 +1,30 @@
+import importlib
+
+import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
-from docs_src.schema_extra_example.tutorial004 import app
+from ...utils import needs_py39, needs_py310
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ "tutorial004",
+ pytest.param("tutorial004_py310", marks=needs_py310),
+ "tutorial004_an",
+ pytest.param("tutorial004_an_py39", marks=needs_py39),
+ pytest.param("tutorial004_an_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.schema_extra_example.{request.param}")
-client = TestClient(app)
+ client = TestClient(mod.app)
+ return client
-# Test required and embedded body parameters with no bodies sent
-def test_post_body_example():
+def test_post_body_example(client: TestClient):
response = client.put(
"/items/5",
json={
@@ -20,7 +37,7 @@ def test_post_body_example():
assert response.status_code == 200
-def test_openapi_schema():
+def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial004_an.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial004_an.py
deleted file mode 100644
index a9cecd098..000000000
--- a/tests/test_tutorial/test_schema_extra_example/test_tutorial004_an.py
+++ /dev/null
@@ -1,168 +0,0 @@
-from dirty_equals import IsDict
-from fastapi.testclient import TestClient
-
-from docs_src.schema_extra_example.tutorial004_an import app
-
-client = TestClient(app)
-
-
-# Test required and embedded body parameters with no bodies sent
-def test_post_body_example():
- response = client.put(
- "/items/5",
- json={
- "name": "Foo",
- "description": "A very nice Item",
- "price": 35.4,
- "tax": 3.2,
- },
- )
- assert response.status_code == 200
-
-
-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": {
- "/items/{item_id}": {
- "put": {
- "summary": "Update Item",
- "operationId": "update_item_items__item_id__put",
- "parameters": [
- {
- "required": True,
- "schema": {"title": "Item Id", "type": "integer"},
- "name": "item_id",
- "in": "path",
- }
- ],
- "requestBody": {
- "content": {
- "application/json": {
- "schema": IsDict(
- {
- "$ref": "#/components/schemas/Item",
- "examples": [
- {
- "name": "Foo",
- "description": "A very nice Item",
- "price": 35.4,
- "tax": 3.2,
- },
- {"name": "Bar", "price": "35.4"},
- {
- "name": "Baz",
- "price": "thirty five point four",
- },
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "allOf": [
- {"$ref": "#/components/schemas/Item"}
- ],
- "title": "Item",
- "examples": [
- {
- "name": "Foo",
- "description": "A very nice Item",
- "price": 35.4,
- "tax": 3.2,
- },
- {"name": "Bar", "price": "35.4"},
- {
- "name": "Baz",
- "price": "thirty five point four",
- },
- ],
- }
- )
- }
- },
- "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": {
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- "Item": {
- "title": "Item",
- "required": ["name", "price"],
- "type": "object",
- "properties": {
- "name": {"title": "Name", "type": "string"},
- "description": IsDict(
- {
- "title": "Description",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Description", "type": "string"}
- ),
- "price": {"title": "Price", "type": "number"},
- "tax": IsDict(
- {
- "title": "Tax",
- "anyOf": [{"type": "number"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Tax", "type": "number"}
- ),
- },
- },
- "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"},
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial004_an_py310.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial004_an_py310.py
deleted file mode 100644
index b6a735599..000000000
--- a/tests/test_tutorial/test_schema_extra_example/test_tutorial004_an_py310.py
+++ /dev/null
@@ -1,177 +0,0 @@
-import pytest
-from dirty_equals import IsDict
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py310
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.schema_extra_example.tutorial004_an_py310 import app
-
- client = TestClient(app)
- return client
-
-
-# Test required and embedded body parameters with no bodies sent
-@needs_py310
-def test_post_body_example(client: TestClient):
- response = client.put(
- "/items/5",
- json={
- "name": "Foo",
- "description": "A very nice Item",
- "price": 35.4,
- "tax": 3.2,
- },
- )
- assert response.status_code == 200
-
-
-@needs_py310
-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": {
- "/items/{item_id}": {
- "put": {
- "summary": "Update Item",
- "operationId": "update_item_items__item_id__put",
- "parameters": [
- {
- "required": True,
- "schema": {"title": "Item Id", "type": "integer"},
- "name": "item_id",
- "in": "path",
- }
- ],
- "requestBody": {
- "content": {
- "application/json": {
- "schema": IsDict(
- {
- "$ref": "#/components/schemas/Item",
- "examples": [
- {
- "name": "Foo",
- "description": "A very nice Item",
- "price": 35.4,
- "tax": 3.2,
- },
- {"name": "Bar", "price": "35.4"},
- {
- "name": "Baz",
- "price": "thirty five point four",
- },
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "allOf": [
- {"$ref": "#/components/schemas/Item"}
- ],
- "title": "Item",
- "examples": [
- {
- "name": "Foo",
- "description": "A very nice Item",
- "price": 35.4,
- "tax": 3.2,
- },
- {"name": "Bar", "price": "35.4"},
- {
- "name": "Baz",
- "price": "thirty five point four",
- },
- ],
- }
- )
- }
- },
- "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": {
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- "Item": {
- "title": "Item",
- "required": ["name", "price"],
- "type": "object",
- "properties": {
- "name": {"title": "Name", "type": "string"},
- "description": IsDict(
- {
- "title": "Description",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Description", "type": "string"}
- ),
- "price": {"title": "Price", "type": "number"},
- "tax": IsDict(
- {
- "title": "Tax",
- "anyOf": [{"type": "number"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Tax", "type": "number"}
- ),
- },
- },
- "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"},
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial004_an_py39.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial004_an_py39.py
deleted file mode 100644
index 2493194a0..000000000
--- a/tests/test_tutorial/test_schema_extra_example/test_tutorial004_an_py39.py
+++ /dev/null
@@ -1,177 +0,0 @@
-import pytest
-from dirty_equals import IsDict
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py39
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.schema_extra_example.tutorial004_an_py39 import app
-
- client = TestClient(app)
- return client
-
-
-# Test required and embedded body parameters with no bodies sent
-@needs_py39
-def test_post_body_example(client: TestClient):
- response = client.put(
- "/items/5",
- json={
- "name": "Foo",
- "description": "A very nice Item",
- "price": 35.4,
- "tax": 3.2,
- },
- )
- assert response.status_code == 200
-
-
-@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": {
- "/items/{item_id}": {
- "put": {
- "summary": "Update Item",
- "operationId": "update_item_items__item_id__put",
- "parameters": [
- {
- "required": True,
- "schema": {"title": "Item Id", "type": "integer"},
- "name": "item_id",
- "in": "path",
- }
- ],
- "requestBody": {
- "content": {
- "application/json": {
- "schema": IsDict(
- {
- "$ref": "#/components/schemas/Item",
- "examples": [
- {
- "name": "Foo",
- "description": "A very nice Item",
- "price": 35.4,
- "tax": 3.2,
- },
- {"name": "Bar", "price": "35.4"},
- {
- "name": "Baz",
- "price": "thirty five point four",
- },
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "allOf": [
- {"$ref": "#/components/schemas/Item"}
- ],
- "title": "Item",
- "examples": [
- {
- "name": "Foo",
- "description": "A very nice Item",
- "price": 35.4,
- "tax": 3.2,
- },
- {"name": "Bar", "price": "35.4"},
- {
- "name": "Baz",
- "price": "thirty five point four",
- },
- ],
- }
- )
- }
- },
- "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": {
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- "Item": {
- "title": "Item",
- "required": ["name", "price"],
- "type": "object",
- "properties": {
- "name": {"title": "Name", "type": "string"},
- "description": IsDict(
- {
- "title": "Description",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Description", "type": "string"}
- ),
- "price": {"title": "Price", "type": "number"},
- "tax": IsDict(
- {
- "title": "Tax",
- "anyOf": [{"type": "number"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Tax", "type": "number"}
- ),
- },
- },
- "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"},
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial004_py310.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial004_py310.py
deleted file mode 100644
index 15f54bd5a..000000000
--- a/tests/test_tutorial/test_schema_extra_example/test_tutorial004_py310.py
+++ /dev/null
@@ -1,177 +0,0 @@
-import pytest
-from dirty_equals import IsDict
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py310
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.schema_extra_example.tutorial004_py310 import app
-
- client = TestClient(app)
- return client
-
-
-# Test required and embedded body parameters with no bodies sent
-@needs_py310
-def test_post_body_example(client: TestClient):
- response = client.put(
- "/items/5",
- json={
- "name": "Foo",
- "description": "A very nice Item",
- "price": 35.4,
- "tax": 3.2,
- },
- )
- assert response.status_code == 200
-
-
-@needs_py310
-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": {
- "/items/{item_id}": {
- "put": {
- "summary": "Update Item",
- "operationId": "update_item_items__item_id__put",
- "parameters": [
- {
- "required": True,
- "schema": {"title": "Item Id", "type": "integer"},
- "name": "item_id",
- "in": "path",
- }
- ],
- "requestBody": {
- "content": {
- "application/json": {
- "schema": IsDict(
- {
- "$ref": "#/components/schemas/Item",
- "examples": [
- {
- "name": "Foo",
- "description": "A very nice Item",
- "price": 35.4,
- "tax": 3.2,
- },
- {"name": "Bar", "price": "35.4"},
- {
- "name": "Baz",
- "price": "thirty five point four",
- },
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "allOf": [
- {"$ref": "#/components/schemas/Item"}
- ],
- "title": "Item",
- "examples": [
- {
- "name": "Foo",
- "description": "A very nice Item",
- "price": 35.4,
- "tax": 3.2,
- },
- {"name": "Bar", "price": "35.4"},
- {
- "name": "Baz",
- "price": "thirty five point four",
- },
- ],
- }
- )
- }
- },
- "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": {
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- "Item": {
- "title": "Item",
- "required": ["name", "price"],
- "type": "object",
- "properties": {
- "name": {"title": "Name", "type": "string"},
- "description": IsDict(
- {
- "title": "Description",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Description", "type": "string"}
- ),
- "price": {"title": "Price", "type": "number"},
- "tax": IsDict(
- {
- "title": "Tax",
- "anyOf": [{"type": "number"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Tax", "type": "number"}
- ),
- },
- },
- "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"},
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial005.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial005.py
index 94a40ed5a..12859227b 100644
--- a/tests/test_tutorial/test_schema_extra_example/test_tutorial005.py
+++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial005.py
@@ -1,13 +1,26 @@
+import importlib
+
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
+from ...utils import needs_py39, needs_py310
+
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.schema_extra_example.tutorial005 import app
+@pytest.fixture(
+ name="client",
+ params=[
+ "tutorial005",
+ pytest.param("tutorial005_py310", marks=needs_py310),
+ "tutorial005_an",
+ pytest.param("tutorial005_an_py39", marks=needs_py39),
+ pytest.param("tutorial005_an_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.schema_extra_example.{request.param}")
- client = TestClient(app)
+ client = TestClient(mod.app)
return client
diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial005_an.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial005_an.py
deleted file mode 100644
index da92f98f6..000000000
--- a/tests/test_tutorial/test_schema_extra_example/test_tutorial005_an.py
+++ /dev/null
@@ -1,166 +0,0 @@
-import pytest
-from dirty_equals import IsDict
-from fastapi.testclient import TestClient
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.schema_extra_example.tutorial005_an import app
-
- client = TestClient(app)
- return client
-
-
-def test_post_body_example(client: TestClient):
- response = client.put(
- "/items/5",
- json={
- "name": "Foo",
- "description": "A very nice Item",
- "price": 35.4,
- "tax": 3.2,
- },
- )
- assert response.status_code == 200
-
-
-def test_openapi_schema(client: TestClient) -> None:
- 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": {
- "/items/{item_id}": {
- "put": {
- "summary": "Update Item",
- "operationId": "update_item_items__item_id__put",
- "parameters": [
- {
- "required": True,
- "schema": {"title": "Item Id", "type": "integer"},
- "name": "item_id",
- "in": "path",
- }
- ],
- "requestBody": {
- "content": {
- "application/json": {
- "schema": IsDict({"$ref": "#/components/schemas/Item"})
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "allOf": [
- {"$ref": "#/components/schemas/Item"}
- ],
- "title": "Item",
- }
- ),
- "examples": {
- "normal": {
- "summary": "A normal example",
- "description": "A **normal** item works correctly.",
- "value": {
- "name": "Foo",
- "description": "A very nice Item",
- "price": 35.4,
- "tax": 3.2,
- },
- },
- "converted": {
- "summary": "An example with converted data",
- "description": "FastAPI can convert price `strings` to actual `numbers` automatically",
- "value": {"name": "Bar", "price": "35.4"},
- },
- "invalid": {
- "summary": "Invalid data is rejected with an error",
- "value": {
- "name": "Baz",
- "price": "thirty five point four",
- },
- },
- },
- }
- },
- "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": {
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- "Item": {
- "title": "Item",
- "required": ["name", "price"],
- "type": "object",
- "properties": {
- "name": {"title": "Name", "type": "string"},
- "description": IsDict(
- {
- "title": "Description",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Description", "type": "string"}
- ),
- "price": {"title": "Price", "type": "number"},
- "tax": IsDict(
- {
- "title": "Tax",
- "anyOf": [{"type": "number"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Tax", "type": "number"}
- ),
- },
- },
- "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"},
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial005_an_py310.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial005_an_py310.py
deleted file mode 100644
index 9109cb14e..000000000
--- a/tests/test_tutorial/test_schema_extra_example/test_tutorial005_an_py310.py
+++ /dev/null
@@ -1,170 +0,0 @@
-import pytest
-from dirty_equals import IsDict
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py310
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.schema_extra_example.tutorial005_an_py310 import app
-
- client = TestClient(app)
- return client
-
-
-@needs_py310
-def test_post_body_example(client: TestClient):
- response = client.put(
- "/items/5",
- json={
- "name": "Foo",
- "description": "A very nice Item",
- "price": 35.4,
- "tax": 3.2,
- },
- )
- assert response.status_code == 200
-
-
-@needs_py310
-def test_openapi_schema(client: TestClient) -> None:
- 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": {
- "/items/{item_id}": {
- "put": {
- "summary": "Update Item",
- "operationId": "update_item_items__item_id__put",
- "parameters": [
- {
- "required": True,
- "schema": {"title": "Item Id", "type": "integer"},
- "name": "item_id",
- "in": "path",
- }
- ],
- "requestBody": {
- "content": {
- "application/json": {
- "schema": IsDict({"$ref": "#/components/schemas/Item"})
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "allOf": [
- {"$ref": "#/components/schemas/Item"}
- ],
- "title": "Item",
- }
- ),
- "examples": {
- "normal": {
- "summary": "A normal example",
- "description": "A **normal** item works correctly.",
- "value": {
- "name": "Foo",
- "description": "A very nice Item",
- "price": 35.4,
- "tax": 3.2,
- },
- },
- "converted": {
- "summary": "An example with converted data",
- "description": "FastAPI can convert price `strings` to actual `numbers` automatically",
- "value": {"name": "Bar", "price": "35.4"},
- },
- "invalid": {
- "summary": "Invalid data is rejected with an error",
- "value": {
- "name": "Baz",
- "price": "thirty five point four",
- },
- },
- },
- }
- },
- "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": {
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- "Item": {
- "title": "Item",
- "required": ["name", "price"],
- "type": "object",
- "properties": {
- "name": {"title": "Name", "type": "string"},
- "description": IsDict(
- {
- "title": "Description",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Description", "type": "string"}
- ),
- "price": {"title": "Price", "type": "number"},
- "tax": IsDict(
- {
- "title": "Tax",
- "anyOf": [{"type": "number"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Tax", "type": "number"}
- ),
- },
- },
- "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"},
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial005_an_py39.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial005_an_py39.py
deleted file mode 100644
index fd4ec0575..000000000
--- a/tests/test_tutorial/test_schema_extra_example/test_tutorial005_an_py39.py
+++ /dev/null
@@ -1,170 +0,0 @@
-import pytest
-from dirty_equals import IsDict
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py39
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.schema_extra_example.tutorial005_an_py39 import app
-
- client = TestClient(app)
- return client
-
-
-@needs_py39
-def test_post_body_example(client: TestClient):
- response = client.put(
- "/items/5",
- json={
- "name": "Foo",
- "description": "A very nice Item",
- "price": 35.4,
- "tax": 3.2,
- },
- )
- assert response.status_code == 200
-
-
-@needs_py39
-def test_openapi_schema(client: TestClient) -> None:
- 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": {
- "/items/{item_id}": {
- "put": {
- "summary": "Update Item",
- "operationId": "update_item_items__item_id__put",
- "parameters": [
- {
- "required": True,
- "schema": {"title": "Item Id", "type": "integer"},
- "name": "item_id",
- "in": "path",
- }
- ],
- "requestBody": {
- "content": {
- "application/json": {
- "schema": IsDict({"$ref": "#/components/schemas/Item"})
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "allOf": [
- {"$ref": "#/components/schemas/Item"}
- ],
- "title": "Item",
- }
- ),
- "examples": {
- "normal": {
- "summary": "A normal example",
- "description": "A **normal** item works correctly.",
- "value": {
- "name": "Foo",
- "description": "A very nice Item",
- "price": 35.4,
- "tax": 3.2,
- },
- },
- "converted": {
- "summary": "An example with converted data",
- "description": "FastAPI can convert price `strings` to actual `numbers` automatically",
- "value": {"name": "Bar", "price": "35.4"},
- },
- "invalid": {
- "summary": "Invalid data is rejected with an error",
- "value": {
- "name": "Baz",
- "price": "thirty five point four",
- },
- },
- },
- }
- },
- "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": {
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- "Item": {
- "title": "Item",
- "required": ["name", "price"],
- "type": "object",
- "properties": {
- "name": {"title": "Name", "type": "string"},
- "description": IsDict(
- {
- "title": "Description",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Description", "type": "string"}
- ),
- "price": {"title": "Price", "type": "number"},
- "tax": IsDict(
- {
- "title": "Tax",
- "anyOf": [{"type": "number"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Tax", "type": "number"}
- ),
- },
- },
- "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"},
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial005_py310.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial005_py310.py
deleted file mode 100644
index 05df53422..000000000
--- a/tests/test_tutorial/test_schema_extra_example/test_tutorial005_py310.py
+++ /dev/null
@@ -1,170 +0,0 @@
-import pytest
-from dirty_equals import IsDict
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py310
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.schema_extra_example.tutorial005_py310 import app
-
- client = TestClient(app)
- return client
-
-
-@needs_py310
-def test_post_body_example(client: TestClient):
- response = client.put(
- "/items/5",
- json={
- "name": "Foo",
- "description": "A very nice Item",
- "price": 35.4,
- "tax": 3.2,
- },
- )
- assert response.status_code == 200
-
-
-@needs_py310
-def test_openapi_schema(client: TestClient) -> None:
- 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": {
- "/items/{item_id}": {
- "put": {
- "summary": "Update Item",
- "operationId": "update_item_items__item_id__put",
- "parameters": [
- {
- "required": True,
- "schema": {"title": "Item Id", "type": "integer"},
- "name": "item_id",
- "in": "path",
- }
- ],
- "requestBody": {
- "content": {
- "application/json": {
- "schema": IsDict({"$ref": "#/components/schemas/Item"})
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "allOf": [
- {"$ref": "#/components/schemas/Item"}
- ],
- "title": "Item",
- }
- ),
- "examples": {
- "normal": {
- "summary": "A normal example",
- "description": "A **normal** item works correctly.",
- "value": {
- "name": "Foo",
- "description": "A very nice Item",
- "price": 35.4,
- "tax": 3.2,
- },
- },
- "converted": {
- "summary": "An example with converted data",
- "description": "FastAPI can convert price `strings` to actual `numbers` automatically",
- "value": {"name": "Bar", "price": "35.4"},
- },
- "invalid": {
- "summary": "Invalid data is rejected with an error",
- "value": {
- "name": "Baz",
- "price": "thirty five point four",
- },
- },
- },
- }
- },
- "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": {
- "HTTPValidationError": {
- "title": "HTTPValidationError",
- "type": "object",
- "properties": {
- "detail": {
- "title": "Detail",
- "type": "array",
- "items": {"$ref": "#/components/schemas/ValidationError"},
- }
- },
- },
- "Item": {
- "title": "Item",
- "required": ["name", "price"],
- "type": "object",
- "properties": {
- "name": {"title": "Name", "type": "string"},
- "description": IsDict(
- {
- "title": "Description",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Description", "type": "string"}
- ),
- "price": {"title": "Price", "type": "number"},
- "tax": IsDict(
- {
- "title": "Tax",
- "anyOf": [{"type": "number"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Tax", "type": "number"}
- ),
- },
- },
- "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"},
- },
- },
- }
- },
- }
diff --git a/tests/test_tutorial/test_security/test_tutorial001.py b/tests/test_tutorial/test_security/test_tutorial001.py
index 417bed8f7..f572d6e3e 100644
--- a/tests/test_tutorial/test_security/test_tutorial001.py
+++ b/tests/test_tutorial/test_security/test_tutorial001.py
@@ -1,31 +1,47 @@
+import importlib
+
+import pytest
from fastapi.testclient import TestClient
-from docs_src.security.tutorial001 import app
+from ...utils import needs_py39
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ "tutorial001",
+ "tutorial001_an",
+ pytest.param("tutorial001_an_py39", marks=needs_py39),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.security.{request.param}")
-client = TestClient(app)
+ client = TestClient(mod.app)
+ return client
-def test_no_token():
+def test_no_token(client: TestClient):
response = client.get("/items")
assert response.status_code == 401, response.text
assert response.json() == {"detail": "Not authenticated"}
assert response.headers["WWW-Authenticate"] == "Bearer"
-def test_token():
+def test_token(client: TestClient):
response = client.get("/items", headers={"Authorization": "Bearer testtoken"})
assert response.status_code == 200, response.text
assert response.json() == {"token": "testtoken"}
-def test_incorrect_token():
+def test_incorrect_token(client: TestClient):
response = client.get("/items", headers={"Authorization": "Notexistent testtoken"})
assert response.status_code == 401, response.text
assert response.json() == {"detail": "Not authenticated"}
assert response.headers["WWW-Authenticate"] == "Bearer"
-def test_openapi_schema():
+def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
diff --git a/tests/test_tutorial/test_security/test_tutorial001_an.py b/tests/test_tutorial/test_security/test_tutorial001_an.py
deleted file mode 100644
index 59460da7f..000000000
--- a/tests/test_tutorial/test_security/test_tutorial001_an.py
+++ /dev/null
@@ -1,57 +0,0 @@
-from fastapi.testclient import TestClient
-
-from docs_src.security.tutorial001_an import app
-
-client = TestClient(app)
-
-
-def test_no_token():
- response = client.get("/items")
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Not authenticated"}
- assert response.headers["WWW-Authenticate"] == "Bearer"
-
-
-def test_token():
- response = client.get("/items", headers={"Authorization": "Bearer testtoken"})
- assert response.status_code == 200, response.text
- assert response.json() == {"token": "testtoken"}
-
-
-def test_incorrect_token():
- response = client.get("/items", headers={"Authorization": "Notexistent testtoken"})
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Not authenticated"}
- assert response.headers["WWW-Authenticate"] == "Bearer"
-
-
-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": {
- "/items/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- }
- },
- "summary": "Read Items",
- "operationId": "read_items_items__get",
- "security": [{"OAuth2PasswordBearer": []}],
- }
- }
- },
- "components": {
- "securitySchemes": {
- "OAuth2PasswordBearer": {
- "type": "oauth2",
- "flows": {"password": {"scopes": {}, "tokenUrl": "token"}},
- }
- }
- },
- }
diff --git a/tests/test_tutorial/test_security/test_tutorial001_an_py39.py b/tests/test_tutorial/test_security/test_tutorial001_an_py39.py
deleted file mode 100644
index d8e712773..000000000
--- a/tests/test_tutorial/test_security/test_tutorial001_an_py39.py
+++ /dev/null
@@ -1,68 +0,0 @@
-import pytest
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py39
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.security.tutorial001_an_py39 import app
-
- client = TestClient(app)
- return client
-
-
-@needs_py39
-def test_no_token(client: TestClient):
- response = client.get("/items")
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Not authenticated"}
- assert response.headers["WWW-Authenticate"] == "Bearer"
-
-
-@needs_py39
-def test_token(client: TestClient):
- response = client.get("/items", headers={"Authorization": "Bearer testtoken"})
- assert response.status_code == 200, response.text
- assert response.json() == {"token": "testtoken"}
-
-
-@needs_py39
-def test_incorrect_token(client: TestClient):
- response = client.get("/items", headers={"Authorization": "Notexistent testtoken"})
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Not authenticated"}
- assert response.headers["WWW-Authenticate"] == "Bearer"
-
-
-@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": {
- "/items/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- }
- },
- "summary": "Read Items",
- "operationId": "read_items_items__get",
- "security": [{"OAuth2PasswordBearer": []}],
- }
- }
- },
- "components": {
- "securitySchemes": {
- "OAuth2PasswordBearer": {
- "type": "oauth2",
- "flows": {"password": {"scopes": {}, "tokenUrl": "token"}},
- }
- }
- },
- }
diff --git a/tests/test_tutorial/test_security/test_tutorial003.py b/tests/test_tutorial/test_security/test_tutorial003.py
index 18d4680f6..7a4c99401 100644
--- a/tests/test_tutorial/test_security/test_tutorial003.py
+++ b/tests/test_tutorial/test_security/test_tutorial003.py
@@ -1,18 +1,36 @@
+import importlib
+
+import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
-from docs_src.security.tutorial003 import app
+from ...utils import needs_py39, needs_py310
+
+
+@pytest.fixture(
+ name="client",
+ params=[
+ "tutorial003",
+ pytest.param("tutorial003_py310", marks=needs_py310),
+ "tutorial003_an",
+ pytest.param("tutorial003_an_py39", marks=needs_py39),
+ pytest.param("tutorial003_an_py310", marks=needs_py310),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.security.{request.param}")
-client = TestClient(app)
+ client = TestClient(mod.app)
+ return client
-def test_login():
+def test_login(client: TestClient):
response = client.post("/token", data={"username": "johndoe", "password": "secret"})
assert response.status_code == 200, response.text
assert response.json() == {"access_token": "johndoe", "token_type": "bearer"}
-def test_login_incorrect_password():
+def test_login_incorrect_password(client: TestClient):
response = client.post(
"/token", data={"username": "johndoe", "password": "incorrect"}
)
@@ -20,20 +38,20 @@ def test_login_incorrect_password():
assert response.json() == {"detail": "Incorrect username or password"}
-def test_login_incorrect_username():
+def test_login_incorrect_username(client: TestClient):
response = client.post("/token", data={"username": "foo", "password": "secret"})
assert response.status_code == 400, response.text
assert response.json() == {"detail": "Incorrect username or password"}
-def test_no_token():
+def test_no_token(client: TestClient):
response = client.get("/users/me")
assert response.status_code == 401, response.text
assert response.json() == {"detail": "Not authenticated"}
assert response.headers["WWW-Authenticate"] == "Bearer"
-def test_token():
+def test_token(client: TestClient):
response = client.get("/users/me", headers={"Authorization": "Bearer johndoe"})
assert response.status_code == 200, response.text
assert response.json() == {
@@ -45,14 +63,14 @@ def test_token():
}
-def test_incorrect_token():
+def test_incorrect_token(client: TestClient):
response = client.get("/users/me", headers={"Authorization": "Bearer nonexistent"})
assert response.status_code == 401, response.text
assert response.json() == {"detail": "Invalid authentication credentials"}
assert response.headers["WWW-Authenticate"] == "Bearer"
-def test_incorrect_token_type():
+def test_incorrect_token_type(client: TestClient):
response = client.get(
"/users/me", headers={"Authorization": "Notexistent testtoken"}
)
@@ -61,13 +79,13 @@ def test_incorrect_token_type():
assert response.headers["WWW-Authenticate"] == "Bearer"
-def test_inactive_user():
+def test_inactive_user(client: TestClient):
response = client.get("/users/me", headers={"Authorization": "Bearer alice"})
assert response.status_code == 400, response.text
assert response.json() == {"detail": "Inactive user"}
-def test_openapi_schema():
+def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
diff --git a/tests/test_tutorial/test_security/test_tutorial003_an.py b/tests/test_tutorial/test_security/test_tutorial003_an.py
deleted file mode 100644
index a8f64d0c6..000000000
--- a/tests/test_tutorial/test_security/test_tutorial003_an.py
+++ /dev/null
@@ -1,207 +0,0 @@
-from dirty_equals import IsDict
-from fastapi.testclient import TestClient
-
-from docs_src.security.tutorial003_an import app
-
-client = TestClient(app)
-
-
-def test_login():
- response = client.post("/token", data={"username": "johndoe", "password": "secret"})
- assert response.status_code == 200, response.text
- assert response.json() == {"access_token": "johndoe", "token_type": "bearer"}
-
-
-def test_login_incorrect_password():
- response = client.post(
- "/token", data={"username": "johndoe", "password": "incorrect"}
- )
- assert response.status_code == 400, response.text
- assert response.json() == {"detail": "Incorrect username or password"}
-
-
-def test_login_incorrect_username():
- response = client.post("/token", data={"username": "foo", "password": "secret"})
- assert response.status_code == 400, response.text
- assert response.json() == {"detail": "Incorrect username or password"}
-
-
-def test_no_token():
- response = client.get("/users/me")
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Not authenticated"}
- assert response.headers["WWW-Authenticate"] == "Bearer"
-
-
-def test_token():
- response = client.get("/users/me", headers={"Authorization": "Bearer johndoe"})
- assert response.status_code == 200, response.text
- assert response.json() == {
- "username": "johndoe",
- "full_name": "John Doe",
- "email": "johndoe@example.com",
- "hashed_password": "fakehashedsecret",
- "disabled": False,
- }
-
-
-def test_incorrect_token():
- response = client.get("/users/me", headers={"Authorization": "Bearer nonexistent"})
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Invalid authentication credentials"}
- assert response.headers["WWW-Authenticate"] == "Bearer"
-
-
-def test_incorrect_token_type():
- response = client.get(
- "/users/me", headers={"Authorization": "Notexistent testtoken"}
- )
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Not authenticated"}
- assert response.headers["WWW-Authenticate"] == "Bearer"
-
-
-def test_inactive_user():
- response = client.get("/users/me", headers={"Authorization": "Bearer alice"})
- assert response.status_code == 400, response.text
- assert response.json() == {"detail": "Inactive user"}
-
-
-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": {
- "/token": {
- "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_token_post",
- "requestBody": {
- "content": {
- "application/x-www-form-urlencoded": {
- "schema": {
- "$ref": "#/components/schemas/Body_login_token_post"
- }
- }
- },
- "required": True,
- },
- }
- },
- "/users/me": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- }
- },
- "summary": "Read Users Me",
- "operationId": "read_users_me_users_me_get",
- "security": [{"OAuth2PasswordBearer": []}],
- }
- },
- },
- "components": {
- "schemas": {
- "Body_login_token_post": {
- "title": "Body_login_token_post",
- "required": ["username", "password"],
- "type": "object",
- "properties": {
- "grant_type": IsDict(
- {
- "title": "Grant Type",
- "anyOf": [
- {"pattern": "password", "type": "string"},
- {"type": "null"},
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Grant Type",
- "pattern": "password",
- "type": "string",
- }
- ),
- "username": {"title": "Username", "type": "string"},
- "password": {"title": "Password", "type": "string"},
- "scope": {"title": "Scope", "type": "string", "default": ""},
- "client_id": IsDict(
- {
- "title": "Client Id",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Client Id", "type": "string"}
- ),
- "client_secret": IsDict(
- {
- "title": "Client Secret",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Client Secret", "type": "string"}
- ),
- },
- },
- "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"},
- }
- },
- },
- },
- "securitySchemes": {
- "OAuth2PasswordBearer": {
- "type": "oauth2",
- "flows": {"password": {"scopes": {}, "tokenUrl": "token"}},
- }
- },
- },
- }
diff --git a/tests/test_tutorial/test_security/test_tutorial003_an_py310.py b/tests/test_tutorial/test_security/test_tutorial003_an_py310.py
deleted file mode 100644
index 7cbbcee2f..000000000
--- a/tests/test_tutorial/test_security/test_tutorial003_an_py310.py
+++ /dev/null
@@ -1,223 +0,0 @@
-import pytest
-from dirty_equals import IsDict
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py310
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.security.tutorial003_an_py310 import app
-
- client = TestClient(app)
- return client
-
-
-@needs_py310
-def test_login(client: TestClient):
- response = client.post("/token", data={"username": "johndoe", "password": "secret"})
- assert response.status_code == 200, response.text
- assert response.json() == {"access_token": "johndoe", "token_type": "bearer"}
-
-
-@needs_py310
-def test_login_incorrect_password(client: TestClient):
- response = client.post(
- "/token", data={"username": "johndoe", "password": "incorrect"}
- )
- assert response.status_code == 400, response.text
- assert response.json() == {"detail": "Incorrect username or password"}
-
-
-@needs_py310
-def test_login_incorrect_username(client: TestClient):
- response = client.post("/token", data={"username": "foo", "password": "secret"})
- assert response.status_code == 400, response.text
- assert response.json() == {"detail": "Incorrect username or password"}
-
-
-@needs_py310
-def test_no_token(client: TestClient):
- response = client.get("/users/me")
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Not authenticated"}
- assert response.headers["WWW-Authenticate"] == "Bearer"
-
-
-@needs_py310
-def test_token(client: TestClient):
- response = client.get("/users/me", headers={"Authorization": "Bearer johndoe"})
- assert response.status_code == 200, response.text
- assert response.json() == {
- "username": "johndoe",
- "full_name": "John Doe",
- "email": "johndoe@example.com",
- "hashed_password": "fakehashedsecret",
- "disabled": False,
- }
-
-
-@needs_py310
-def test_incorrect_token(client: TestClient):
- response = client.get("/users/me", headers={"Authorization": "Bearer nonexistent"})
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Invalid authentication credentials"}
- assert response.headers["WWW-Authenticate"] == "Bearer"
-
-
-@needs_py310
-def test_incorrect_token_type(client: TestClient):
- response = client.get(
- "/users/me", headers={"Authorization": "Notexistent testtoken"}
- )
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Not authenticated"}
- assert response.headers["WWW-Authenticate"] == "Bearer"
-
-
-@needs_py310
-def test_inactive_user(client: TestClient):
- response = client.get("/users/me", headers={"Authorization": "Bearer alice"})
- assert response.status_code == 400, response.text
- assert response.json() == {"detail": "Inactive user"}
-
-
-@needs_py310
-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": {
- "/token": {
- "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_token_post",
- "requestBody": {
- "content": {
- "application/x-www-form-urlencoded": {
- "schema": {
- "$ref": "#/components/schemas/Body_login_token_post"
- }
- }
- },
- "required": True,
- },
- }
- },
- "/users/me": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- }
- },
- "summary": "Read Users Me",
- "operationId": "read_users_me_users_me_get",
- "security": [{"OAuth2PasswordBearer": []}],
- }
- },
- },
- "components": {
- "schemas": {
- "Body_login_token_post": {
- "title": "Body_login_token_post",
- "required": ["username", "password"],
- "type": "object",
- "properties": {
- "grant_type": IsDict(
- {
- "title": "Grant Type",
- "anyOf": [
- {"pattern": "password", "type": "string"},
- {"type": "null"},
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Grant Type",
- "pattern": "password",
- "type": "string",
- }
- ),
- "username": {"title": "Username", "type": "string"},
- "password": {"title": "Password", "type": "string"},
- "scope": {"title": "Scope", "type": "string", "default": ""},
- "client_id": IsDict(
- {
- "title": "Client Id",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Client Id", "type": "string"}
- ),
- "client_secret": IsDict(
- {
- "title": "Client Secret",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Client Secret", "type": "string"}
- ),
- },
- },
- "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"},
- }
- },
- },
- },
- "securitySchemes": {
- "OAuth2PasswordBearer": {
- "type": "oauth2",
- "flows": {"password": {"scopes": {}, "tokenUrl": "token"}},
- }
- },
- },
- }
diff --git a/tests/test_tutorial/test_security/test_tutorial003_an_py39.py b/tests/test_tutorial/test_security/test_tutorial003_an_py39.py
deleted file mode 100644
index 7b21fbcc9..000000000
--- a/tests/test_tutorial/test_security/test_tutorial003_an_py39.py
+++ /dev/null
@@ -1,223 +0,0 @@
-import pytest
-from dirty_equals import IsDict
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py39
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.security.tutorial003_an_py39 import app
-
- client = TestClient(app)
- return client
-
-
-@needs_py39
-def test_login(client: TestClient):
- response = client.post("/token", data={"username": "johndoe", "password": "secret"})
- assert response.status_code == 200, response.text
- assert response.json() == {"access_token": "johndoe", "token_type": "bearer"}
-
-
-@needs_py39
-def test_login_incorrect_password(client: TestClient):
- response = client.post(
- "/token", data={"username": "johndoe", "password": "incorrect"}
- )
- assert response.status_code == 400, response.text
- assert response.json() == {"detail": "Incorrect username or password"}
-
-
-@needs_py39
-def test_login_incorrect_username(client: TestClient):
- response = client.post("/token", data={"username": "foo", "password": "secret"})
- assert response.status_code == 400, response.text
- assert response.json() == {"detail": "Incorrect username or password"}
-
-
-@needs_py39
-def test_no_token(client: TestClient):
- response = client.get("/users/me")
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Not authenticated"}
- assert response.headers["WWW-Authenticate"] == "Bearer"
-
-
-@needs_py39
-def test_token(client: TestClient):
- response = client.get("/users/me", headers={"Authorization": "Bearer johndoe"})
- assert response.status_code == 200, response.text
- assert response.json() == {
- "username": "johndoe",
- "full_name": "John Doe",
- "email": "johndoe@example.com",
- "hashed_password": "fakehashedsecret",
- "disabled": False,
- }
-
-
-@needs_py39
-def test_incorrect_token(client: TestClient):
- response = client.get("/users/me", headers={"Authorization": "Bearer nonexistent"})
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Invalid authentication credentials"}
- assert response.headers["WWW-Authenticate"] == "Bearer"
-
-
-@needs_py39
-def test_incorrect_token_type(client: TestClient):
- response = client.get(
- "/users/me", headers={"Authorization": "Notexistent testtoken"}
- )
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Not authenticated"}
- assert response.headers["WWW-Authenticate"] == "Bearer"
-
-
-@needs_py39
-def test_inactive_user(client: TestClient):
- response = client.get("/users/me", headers={"Authorization": "Bearer alice"})
- assert response.status_code == 400, response.text
- assert response.json() == {"detail": "Inactive user"}
-
-
-@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": {
- "/token": {
- "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_token_post",
- "requestBody": {
- "content": {
- "application/x-www-form-urlencoded": {
- "schema": {
- "$ref": "#/components/schemas/Body_login_token_post"
- }
- }
- },
- "required": True,
- },
- }
- },
- "/users/me": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- }
- },
- "summary": "Read Users Me",
- "operationId": "read_users_me_users_me_get",
- "security": [{"OAuth2PasswordBearer": []}],
- }
- },
- },
- "components": {
- "schemas": {
- "Body_login_token_post": {
- "title": "Body_login_token_post",
- "required": ["username", "password"],
- "type": "object",
- "properties": {
- "grant_type": IsDict(
- {
- "title": "Grant Type",
- "anyOf": [
- {"pattern": "password", "type": "string"},
- {"type": "null"},
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Grant Type",
- "pattern": "password",
- "type": "string",
- }
- ),
- "username": {"title": "Username", "type": "string"},
- "password": {"title": "Password", "type": "string"},
- "scope": {"title": "Scope", "type": "string", "default": ""},
- "client_id": IsDict(
- {
- "title": "Client Id",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Client Id", "type": "string"}
- ),
- "client_secret": IsDict(
- {
- "title": "Client Secret",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Client Secret", "type": "string"}
- ),
- },
- },
- "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"},
- }
- },
- },
- },
- "securitySchemes": {
- "OAuth2PasswordBearer": {
- "type": "oauth2",
- "flows": {"password": {"scopes": {}, "tokenUrl": "token"}},
- }
- },
- },
- }
diff --git a/tests/test_tutorial/test_security/test_tutorial003_py310.py b/tests/test_tutorial/test_security/test_tutorial003_py310.py
deleted file mode 100644
index 512504534..000000000
--- a/tests/test_tutorial/test_security/test_tutorial003_py310.py
+++ /dev/null
@@ -1,223 +0,0 @@
-import pytest
-from dirty_equals import IsDict
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py310
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.security.tutorial003_py310 import app
-
- client = TestClient(app)
- return client
-
-
-@needs_py310
-def test_login(client: TestClient):
- response = client.post("/token", data={"username": "johndoe", "password": "secret"})
- assert response.status_code == 200, response.text
- assert response.json() == {"access_token": "johndoe", "token_type": "bearer"}
-
-
-@needs_py310
-def test_login_incorrect_password(client: TestClient):
- response = client.post(
- "/token", data={"username": "johndoe", "password": "incorrect"}
- )
- assert response.status_code == 400, response.text
- assert response.json() == {"detail": "Incorrect username or password"}
-
-
-@needs_py310
-def test_login_incorrect_username(client: TestClient):
- response = client.post("/token", data={"username": "foo", "password": "secret"})
- assert response.status_code == 400, response.text
- assert response.json() == {"detail": "Incorrect username or password"}
-
-
-@needs_py310
-def test_no_token(client: TestClient):
- response = client.get("/users/me")
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Not authenticated"}
- assert response.headers["WWW-Authenticate"] == "Bearer"
-
-
-@needs_py310
-def test_token(client: TestClient):
- response = client.get("/users/me", headers={"Authorization": "Bearer johndoe"})
- assert response.status_code == 200, response.text
- assert response.json() == {
- "username": "johndoe",
- "full_name": "John Doe",
- "email": "johndoe@example.com",
- "hashed_password": "fakehashedsecret",
- "disabled": False,
- }
-
-
-@needs_py310
-def test_incorrect_token(client: TestClient):
- response = client.get("/users/me", headers={"Authorization": "Bearer nonexistent"})
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Invalid authentication credentials"}
- assert response.headers["WWW-Authenticate"] == "Bearer"
-
-
-@needs_py310
-def test_incorrect_token_type(client: TestClient):
- response = client.get(
- "/users/me", headers={"Authorization": "Notexistent testtoken"}
- )
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Not authenticated"}
- assert response.headers["WWW-Authenticate"] == "Bearer"
-
-
-@needs_py310
-def test_inactive_user(client: TestClient):
- response = client.get("/users/me", headers={"Authorization": "Bearer alice"})
- assert response.status_code == 400, response.text
- assert response.json() == {"detail": "Inactive user"}
-
-
-@needs_py310
-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": {
- "/token": {
- "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_token_post",
- "requestBody": {
- "content": {
- "application/x-www-form-urlencoded": {
- "schema": {
- "$ref": "#/components/schemas/Body_login_token_post"
- }
- }
- },
- "required": True,
- },
- }
- },
- "/users/me": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- }
- },
- "summary": "Read Users Me",
- "operationId": "read_users_me_users_me_get",
- "security": [{"OAuth2PasswordBearer": []}],
- }
- },
- },
- "components": {
- "schemas": {
- "Body_login_token_post": {
- "title": "Body_login_token_post",
- "required": ["username", "password"],
- "type": "object",
- "properties": {
- "grant_type": IsDict(
- {
- "title": "Grant Type",
- "anyOf": [
- {"pattern": "password", "type": "string"},
- {"type": "null"},
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Grant Type",
- "pattern": "password",
- "type": "string",
- }
- ),
- "username": {"title": "Username", "type": "string"},
- "password": {"title": "Password", "type": "string"},
- "scope": {"title": "Scope", "type": "string", "default": ""},
- "client_id": IsDict(
- {
- "title": "Client Id",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Client Id", "type": "string"}
- ),
- "client_secret": IsDict(
- {
- "title": "Client Secret",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Client Secret", "type": "string"}
- ),
- },
- },
- "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"},
- }
- },
- },
- },
- "securitySchemes": {
- "OAuth2PasswordBearer": {
- "type": "oauth2",
- "flows": {"password": {"scopes": {}, "tokenUrl": "token"}},
- }
- },
- },
- }
diff --git a/tests/test_tutorial/test_security/test_tutorial005.py b/tests/test_tutorial/test_security/test_tutorial005.py
index 2e580dbb3..c7f791b03 100644
--- a/tests/test_tutorial/test_security/test_tutorial005.py
+++ b/tests/test_tutorial/test_security/test_tutorial005.py
@@ -1,18 +1,33 @@
+import importlib
+from types import ModuleType
+
+import pytest
from dirty_equals import IsDict, IsOneOf
from fastapi.testclient import TestClient
-from docs_src.security.tutorial005 import (
- app,
- create_access_token,
- fake_users_db,
- get_password_hash,
- verify_password,
+from ...utils import needs_py39, needs_py310
+
+
+@pytest.fixture(
+ name="mod",
+ params=[
+ "tutorial005",
+ pytest.param("tutorial005_py310", marks=needs_py310),
+ "tutorial005_an",
+ pytest.param("tutorial005_py39", marks=needs_py39),
+ pytest.param("tutorial005_an_py39", marks=needs_py39),
+ pytest.param("tutorial005_an_py310", marks=needs_py310),
+ ],
)
+def get_mod(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.security.{request.param}")
-client = TestClient(app)
+ return mod
-def get_access_token(username="johndoe", password="secret", scope=None):
+def get_access_token(
+ *, username="johndoe", password="secret", scope=None, client: TestClient
+):
data = {"username": username, "password": password}
if scope:
data["scope"] = scope
@@ -22,7 +37,8 @@ def get_access_token(username="johndoe", password="secret", scope=None):
return access_token
-def test_login():
+def test_login(mod: ModuleType):
+ client = TestClient(mod.app)
response = client.post("/token", data={"username": "johndoe", "password": "secret"})
assert response.status_code == 200, response.text
content = response.json()
@@ -30,7 +46,8 @@ def test_login():
assert content["token_type"] == "bearer"
-def test_login_incorrect_password():
+def test_login_incorrect_password(mod: ModuleType):
+ client = TestClient(mod.app)
response = client.post(
"/token", data={"username": "johndoe", "password": "incorrect"}
)
@@ -38,21 +55,24 @@ def test_login_incorrect_password():
assert response.json() == {"detail": "Incorrect username or password"}
-def test_login_incorrect_username():
+def test_login_incorrect_username(mod: ModuleType):
+ client = TestClient(mod.app)
response = client.post("/token", data={"username": "foo", "password": "secret"})
assert response.status_code == 400, response.text
assert response.json() == {"detail": "Incorrect username or password"}
-def test_no_token():
+def test_no_token(mod: ModuleType):
+ client = TestClient(mod.app)
response = client.get("/users/me")
assert response.status_code == 401, response.text
assert response.json() == {"detail": "Not authenticated"}
assert response.headers["WWW-Authenticate"] == "Bearer"
-def test_token():
- access_token = get_access_token(scope="me")
+def test_token(mod: ModuleType):
+ client = TestClient(mod.app)
+ access_token = get_access_token(scope="me", client=client)
response = client.get(
"/users/me", headers={"Authorization": f"Bearer {access_token}"}
)
@@ -65,14 +85,16 @@ def test_token():
}
-def test_incorrect_token():
+def test_incorrect_token(mod: ModuleType):
+ client = TestClient(mod.app)
response = client.get("/users/me", headers={"Authorization": "Bearer nonexistent"})
assert response.status_code == 401, response.text
assert response.json() == {"detail": "Could not validate credentials"}
assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
-def test_incorrect_token_type():
+def test_incorrect_token_type(mod: ModuleType):
+ client = TestClient(mod.app)
response = client.get(
"/users/me", headers={"Authorization": "Notexistent testtoken"}
)
@@ -81,20 +103,24 @@ def test_incorrect_token_type():
assert response.headers["WWW-Authenticate"] == "Bearer"
-def test_verify_password():
- assert verify_password("secret", fake_users_db["johndoe"]["hashed_password"])
+def test_verify_password(mod: ModuleType):
+ assert mod.verify_password(
+ "secret", mod.fake_users_db["johndoe"]["hashed_password"]
+ )
-def test_get_password_hash():
- assert get_password_hash("secretalice")
+def test_get_password_hash(mod: ModuleType):
+ assert mod.get_password_hash("secretalice")
-def test_create_access_token():
- access_token = create_access_token(data={"data": "foo"})
+def test_create_access_token(mod: ModuleType):
+ access_token = mod.create_access_token(data={"data": "foo"})
assert access_token
-def test_token_no_sub():
+def test_token_no_sub(mod: ModuleType):
+ client = TestClient(mod.app)
+
response = client.get(
"/users/me",
headers={
@@ -106,7 +132,9 @@ def test_token_no_sub():
assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
-def test_token_no_username():
+def test_token_no_username(mod: ModuleType):
+ client = TestClient(mod.app)
+
response = client.get(
"/users/me",
headers={
@@ -118,8 +146,10 @@ def test_token_no_username():
assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
-def test_token_no_scope():
- access_token = get_access_token()
+def test_token_no_scope(mod: ModuleType):
+ client = TestClient(mod.app)
+
+ access_token = get_access_token(client=client)
response = client.get(
"/users/me", headers={"Authorization": f"Bearer {access_token}"}
)
@@ -128,7 +158,9 @@ def test_token_no_scope():
assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
-def test_token_nonexistent_user():
+def test_token_nonexistent_user(mod: ModuleType):
+ client = TestClient(mod.app)
+
response = client.get(
"/users/me",
headers={
@@ -140,9 +172,11 @@ def test_token_nonexistent_user():
assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
-def test_token_inactive_user():
+def test_token_inactive_user(mod: ModuleType):
+ client = TestClient(mod.app)
+
access_token = get_access_token(
- username="alice", password="secretalice", scope="me"
+ username="alice", password="secretalice", scope="me", client=client
)
response = client.get(
"/users/me", headers={"Authorization": f"Bearer {access_token}"}
@@ -151,8 +185,9 @@ def test_token_inactive_user():
assert response.json() == {"detail": "Inactive user"}
-def test_read_items():
- access_token = get_access_token(scope="me items")
+def test_read_items(mod: ModuleType):
+ client = TestClient(mod.app)
+ access_token = get_access_token(scope="me items", client=client)
response = client.get(
"/users/me/items/", headers={"Authorization": f"Bearer {access_token}"}
)
@@ -160,8 +195,9 @@ def test_read_items():
assert response.json() == [{"item_id": "Foo", "owner": "johndoe"}]
-def test_read_system_status():
- access_token = get_access_token()
+def test_read_system_status(mod: ModuleType):
+ client = TestClient(mod.app)
+ access_token = get_access_token(client=client)
response = client.get(
"/status/", headers={"Authorization": f"Bearer {access_token}"}
)
@@ -169,14 +205,16 @@ def test_read_system_status():
assert response.json() == {"status": "ok"}
-def test_read_system_status_no_token():
+def test_read_system_status_no_token(mod: ModuleType):
+ client = TestClient(mod.app)
response = client.get("/status/")
assert response.status_code == 401, response.text
assert response.json() == {"detail": "Not authenticated"}
assert response.headers["WWW-Authenticate"] == "Bearer"
-def test_openapi_schema():
+def test_openapi_schema(mod: ModuleType):
+ client = TestClient(mod.app)
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
diff --git a/tests/test_tutorial/test_security/test_tutorial005_an.py b/tests/test_tutorial/test_security/test_tutorial005_an.py
deleted file mode 100644
index 04c7d60bc..000000000
--- a/tests/test_tutorial/test_security/test_tutorial005_an.py
+++ /dev/null
@@ -1,409 +0,0 @@
-from dirty_equals import IsDict, IsOneOf
-from fastapi.testclient import TestClient
-
-from docs_src.security.tutorial005_an import (
- app,
- create_access_token,
- fake_users_db,
- get_password_hash,
- verify_password,
-)
-
-client = TestClient(app)
-
-
-def get_access_token(username="johndoe", password="secret", scope=None):
- data = {"username": username, "password": password}
- if scope:
- data["scope"] = scope
- response = client.post("/token", data=data)
- content = response.json()
- access_token = content.get("access_token")
- return access_token
-
-
-def test_login():
- response = client.post("/token", data={"username": "johndoe", "password": "secret"})
- assert response.status_code == 200, response.text
- content = response.json()
- assert "access_token" in content
- assert content["token_type"] == "bearer"
-
-
-def test_login_incorrect_password():
- response = client.post(
- "/token", data={"username": "johndoe", "password": "incorrect"}
- )
- assert response.status_code == 400, response.text
- assert response.json() == {"detail": "Incorrect username or password"}
-
-
-def test_login_incorrect_username():
- response = client.post("/token", data={"username": "foo", "password": "secret"})
- assert response.status_code == 400, response.text
- assert response.json() == {"detail": "Incorrect username or password"}
-
-
-def test_no_token():
- response = client.get("/users/me")
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Not authenticated"}
- assert response.headers["WWW-Authenticate"] == "Bearer"
-
-
-def test_token():
- access_token = get_access_token(scope="me")
- response = client.get(
- "/users/me", headers={"Authorization": f"Bearer {access_token}"}
- )
- assert response.status_code == 200, response.text
- assert response.json() == {
- "username": "johndoe",
- "full_name": "John Doe",
- "email": "johndoe@example.com",
- "disabled": False,
- }
-
-
-def test_incorrect_token():
- response = client.get("/users/me", headers={"Authorization": "Bearer nonexistent"})
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Could not validate credentials"}
- assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
-
-
-def test_incorrect_token_type():
- response = client.get(
- "/users/me", headers={"Authorization": "Notexistent testtoken"}
- )
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Not authenticated"}
- assert response.headers["WWW-Authenticate"] == "Bearer"
-
-
-def test_verify_password():
- assert verify_password("secret", fake_users_db["johndoe"]["hashed_password"])
-
-
-def test_get_password_hash():
- assert get_password_hash("secretalice")
-
-
-def test_create_access_token():
- access_token = create_access_token(data={"data": "foo"})
- assert access_token
-
-
-def test_token_no_sub():
- response = client.get(
- "/users/me",
- headers={
- "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoiZm9vIn0.9ynBhuYb4e6aW3oJr_K_TBgwcMTDpRToQIE25L57rOE"
- },
- )
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Could not validate credentials"}
- assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
-
-
-def test_token_no_username():
- response = client.get(
- "/users/me",
- headers={
- "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmb28ifQ.NnExK_dlNAYyzACrXtXDrcWOgGY2JuPbI4eDaHdfK5Y"
- },
- )
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Could not validate credentials"}
- assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
-
-
-def test_token_no_scope():
- access_token = get_access_token()
- response = client.get(
- "/users/me", headers={"Authorization": f"Bearer {access_token}"}
- )
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Not enough permissions"}
- assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
-
-
-def test_token_nonexistent_user():
- response = client.get(
- "/users/me",
- headers={
- "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VybmFtZTpib2IifQ.HcfCW67Uda-0gz54ZWTqmtgJnZeNem0Q757eTa9EZuw"
- },
- )
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Could not validate credentials"}
- assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
-
-
-def test_token_inactive_user():
- access_token = get_access_token(
- username="alice", password="secretalice", scope="me"
- )
- response = client.get(
- "/users/me", headers={"Authorization": f"Bearer {access_token}"}
- )
- assert response.status_code == 400, response.text
- assert response.json() == {"detail": "Inactive user"}
-
-
-def test_read_items():
- access_token = get_access_token(scope="me items")
- response = client.get(
- "/users/me/items/", headers={"Authorization": f"Bearer {access_token}"}
- )
- assert response.status_code == 200, response.text
- assert response.json() == [{"item_id": "Foo", "owner": "johndoe"}]
-
-
-def test_read_system_status():
- access_token = get_access_token()
- response = client.get(
- "/status/", headers={"Authorization": f"Bearer {access_token}"}
- )
- assert response.status_code == 200, response.text
- assert response.json() == {"status": "ok"}
-
-
-def test_read_system_status_no_token():
- response = client.get("/status/")
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Not authenticated"}
- assert response.headers["WWW-Authenticate"] == "Bearer"
-
-
-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": {
- "/token": {
- "post": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Token"}
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- "summary": "Login For Access Token",
- "operationId": "login_for_access_token_token_post",
- "requestBody": {
- "content": {
- "application/x-www-form-urlencoded": {
- "schema": {
- "$ref": "#/components/schemas/Body_login_for_access_token_token_post"
- }
- }
- },
- "required": True,
- },
- }
- },
- "/users/me/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/User"}
- }
- },
- }
- },
- "summary": "Read Users Me",
- "operationId": "read_users_me_users_me__get",
- "security": [{"OAuth2PasswordBearer": ["me"]}],
- }
- },
- "/users/me/items/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- }
- },
- "summary": "Read Own Items",
- "operationId": "read_own_items_users_me_items__get",
- "security": [{"OAuth2PasswordBearer": ["items", "me"]}],
- }
- },
- "/status/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- }
- },
- "summary": "Read System Status",
- "operationId": "read_system_status_status__get",
- "security": [{"OAuth2PasswordBearer": []}],
- }
- },
- },
- "components": {
- "schemas": {
- "User": {
- "title": "User",
- "required": IsOneOf(
- ["username", "email", "full_name", "disabled"],
- # TODO: remove when deprecating Pydantic v1
- ["username"],
- ),
- "type": "object",
- "properties": {
- "username": {"title": "Username", "type": "string"},
- "email": IsDict(
- {
- "title": "Email",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Email", "type": "string"}
- ),
- "full_name": IsDict(
- {
- "title": "Full Name",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Full Name", "type": "string"}
- ),
- "disabled": IsDict(
- {
- "title": "Disabled",
- "anyOf": [{"type": "boolean"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Disabled", "type": "boolean"}
- ),
- },
- },
- "Token": {
- "title": "Token",
- "required": ["access_token", "token_type"],
- "type": "object",
- "properties": {
- "access_token": {"title": "Access Token", "type": "string"},
- "token_type": {"title": "Token Type", "type": "string"},
- },
- },
- "Body_login_for_access_token_token_post": {
- "title": "Body_login_for_access_token_token_post",
- "required": ["username", "password"],
- "type": "object",
- "properties": {
- "grant_type": IsDict(
- {
- "title": "Grant Type",
- "anyOf": [
- {"pattern": "password", "type": "string"},
- {"type": "null"},
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Grant Type",
- "pattern": "password",
- "type": "string",
- }
- ),
- "username": {"title": "Username", "type": "string"},
- "password": {"title": "Password", "type": "string"},
- "scope": {"title": "Scope", "type": "string", "default": ""},
- "client_id": IsDict(
- {
- "title": "Client Id",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Client Id", "type": "string"}
- ),
- "client_secret": IsDict(
- {
- "title": "Client Secret",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Client Secret", "type": "string"}
- ),
- },
- },
- "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"},
- }
- },
- },
- },
- "securitySchemes": {
- "OAuth2PasswordBearer": {
- "type": "oauth2",
- "flows": {
- "password": {
- "scopes": {
- "me": "Read information about the current user.",
- "items": "Read items.",
- },
- "tokenUrl": "token",
- }
- },
- }
- },
- },
- }
diff --git a/tests/test_tutorial/test_security/test_tutorial005_an_py310.py b/tests/test_tutorial/test_security/test_tutorial005_an_py310.py
deleted file mode 100644
index 9c7f83ed2..000000000
--- a/tests/test_tutorial/test_security/test_tutorial005_an_py310.py
+++ /dev/null
@@ -1,437 +0,0 @@
-import pytest
-from dirty_equals import IsDict, IsOneOf
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py310
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.security.tutorial005_an_py310 import app
-
- client = TestClient(app)
- return client
-
-
-def get_access_token(
- *, username="johndoe", password="secret", scope=None, client: TestClient
-):
- data = {"username": username, "password": password}
- if scope:
- data["scope"] = scope
- response = client.post("/token", data=data)
- content = response.json()
- access_token = content.get("access_token")
- return access_token
-
-
-@needs_py310
-def test_login(client: TestClient):
- response = client.post("/token", data={"username": "johndoe", "password": "secret"})
- assert response.status_code == 200, response.text
- content = response.json()
- assert "access_token" in content
- assert content["token_type"] == "bearer"
-
-
-@needs_py310
-def test_login_incorrect_password(client: TestClient):
- response = client.post(
- "/token", data={"username": "johndoe", "password": "incorrect"}
- )
- assert response.status_code == 400, response.text
- assert response.json() == {"detail": "Incorrect username or password"}
-
-
-@needs_py310
-def test_login_incorrect_username(client: TestClient):
- response = client.post("/token", data={"username": "foo", "password": "secret"})
- assert response.status_code == 400, response.text
- assert response.json() == {"detail": "Incorrect username or password"}
-
-
-@needs_py310
-def test_no_token(client: TestClient):
- response = client.get("/users/me")
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Not authenticated"}
- assert response.headers["WWW-Authenticate"] == "Bearer"
-
-
-@needs_py310
-def test_token(client: TestClient):
- access_token = get_access_token(scope="me", client=client)
- response = client.get(
- "/users/me", headers={"Authorization": f"Bearer {access_token}"}
- )
- assert response.status_code == 200, response.text
- assert response.json() == {
- "username": "johndoe",
- "full_name": "John Doe",
- "email": "johndoe@example.com",
- "disabled": False,
- }
-
-
-@needs_py310
-def test_incorrect_token(client: TestClient):
- response = client.get("/users/me", headers={"Authorization": "Bearer nonexistent"})
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Could not validate credentials"}
- assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
-
-
-@needs_py310
-def test_incorrect_token_type(client: TestClient):
- response = client.get(
- "/users/me", headers={"Authorization": "Notexistent testtoken"}
- )
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Not authenticated"}
- assert response.headers["WWW-Authenticate"] == "Bearer"
-
-
-@needs_py310
-def test_verify_password():
- from docs_src.security.tutorial005_an_py310 import fake_users_db, verify_password
-
- assert verify_password("secret", fake_users_db["johndoe"]["hashed_password"])
-
-
-@needs_py310
-def test_get_password_hash():
- from docs_src.security.tutorial005_an_py310 import get_password_hash
-
- assert get_password_hash("secretalice")
-
-
-@needs_py310
-def test_create_access_token():
- from docs_src.security.tutorial005_an_py310 import create_access_token
-
- access_token = create_access_token(data={"data": "foo"})
- assert access_token
-
-
-@needs_py310
-def test_token_no_sub(client: TestClient):
- response = client.get(
- "/users/me",
- headers={
- "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoiZm9vIn0.9ynBhuYb4e6aW3oJr_K_TBgwcMTDpRToQIE25L57rOE"
- },
- )
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Could not validate credentials"}
- assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
-
-
-@needs_py310
-def test_token_no_username(client: TestClient):
- response = client.get(
- "/users/me",
- headers={
- "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmb28ifQ.NnExK_dlNAYyzACrXtXDrcWOgGY2JuPbI4eDaHdfK5Y"
- },
- )
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Could not validate credentials"}
- assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
-
-
-@needs_py310
-def test_token_no_scope(client: TestClient):
- access_token = get_access_token(client=client)
- response = client.get(
- "/users/me", headers={"Authorization": f"Bearer {access_token}"}
- )
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Not enough permissions"}
- assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
-
-
-@needs_py310
-def test_token_nonexistent_user(client: TestClient):
- response = client.get(
- "/users/me",
- headers={
- "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VybmFtZTpib2IifQ.HcfCW67Uda-0gz54ZWTqmtgJnZeNem0Q757eTa9EZuw"
- },
- )
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Could not validate credentials"}
- assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
-
-
-@needs_py310
-def test_token_inactive_user(client: TestClient):
- access_token = get_access_token(
- username="alice", password="secretalice", scope="me", client=client
- )
- response = client.get(
- "/users/me", headers={"Authorization": f"Bearer {access_token}"}
- )
- assert response.status_code == 400, response.text
- assert response.json() == {"detail": "Inactive user"}
-
-
-@needs_py310
-def test_read_items(client: TestClient):
- access_token = get_access_token(scope="me items", client=client)
- response = client.get(
- "/users/me/items/", headers={"Authorization": f"Bearer {access_token}"}
- )
- assert response.status_code == 200, response.text
- assert response.json() == [{"item_id": "Foo", "owner": "johndoe"}]
-
-
-@needs_py310
-def test_read_system_status(client: TestClient):
- access_token = get_access_token(client=client)
- response = client.get(
- "/status/", headers={"Authorization": f"Bearer {access_token}"}
- )
- assert response.status_code == 200, response.text
- assert response.json() == {"status": "ok"}
-
-
-@needs_py310
-def test_read_system_status_no_token(client: TestClient):
- response = client.get("/status/")
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Not authenticated"}
- assert response.headers["WWW-Authenticate"] == "Bearer"
-
-
-@needs_py310
-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": {
- "/token": {
- "post": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Token"}
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- "summary": "Login For Access Token",
- "operationId": "login_for_access_token_token_post",
- "requestBody": {
- "content": {
- "application/x-www-form-urlencoded": {
- "schema": {
- "$ref": "#/components/schemas/Body_login_for_access_token_token_post"
- }
- }
- },
- "required": True,
- },
- }
- },
- "/users/me/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/User"}
- }
- },
- }
- },
- "summary": "Read Users Me",
- "operationId": "read_users_me_users_me__get",
- "security": [{"OAuth2PasswordBearer": ["me"]}],
- }
- },
- "/users/me/items/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- }
- },
- "summary": "Read Own Items",
- "operationId": "read_own_items_users_me_items__get",
- "security": [{"OAuth2PasswordBearer": ["items", "me"]}],
- }
- },
- "/status/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- }
- },
- "summary": "Read System Status",
- "operationId": "read_system_status_status__get",
- "security": [{"OAuth2PasswordBearer": []}],
- }
- },
- },
- "components": {
- "schemas": {
- "User": {
- "title": "User",
- "required": IsOneOf(
- ["username", "email", "full_name", "disabled"],
- # TODO: remove when deprecating Pydantic v1
- ["username"],
- ),
- "type": "object",
- "properties": {
- "username": {"title": "Username", "type": "string"},
- "email": IsDict(
- {
- "title": "Email",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Email", "type": "string"}
- ),
- "full_name": IsDict(
- {
- "title": "Full Name",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Full Name", "type": "string"}
- ),
- "disabled": IsDict(
- {
- "title": "Disabled",
- "anyOf": [{"type": "boolean"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Disabled", "type": "boolean"}
- ),
- },
- },
- "Token": {
- "title": "Token",
- "required": ["access_token", "token_type"],
- "type": "object",
- "properties": {
- "access_token": {"title": "Access Token", "type": "string"},
- "token_type": {"title": "Token Type", "type": "string"},
- },
- },
- "Body_login_for_access_token_token_post": {
- "title": "Body_login_for_access_token_token_post",
- "required": ["username", "password"],
- "type": "object",
- "properties": {
- "grant_type": IsDict(
- {
- "title": "Grant Type",
- "anyOf": [
- {"pattern": "password", "type": "string"},
- {"type": "null"},
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Grant Type",
- "pattern": "password",
- "type": "string",
- }
- ),
- "username": {"title": "Username", "type": "string"},
- "password": {"title": "Password", "type": "string"},
- "scope": {"title": "Scope", "type": "string", "default": ""},
- "client_id": IsDict(
- {
- "title": "Client Id",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Client Id", "type": "string"}
- ),
- "client_secret": IsDict(
- {
- "title": "Client Secret",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Client Secret", "type": "string"}
- ),
- },
- },
- "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"},
- }
- },
- },
- },
- "securitySchemes": {
- "OAuth2PasswordBearer": {
- "type": "oauth2",
- "flows": {
- "password": {
- "scopes": {
- "me": "Read information about the current user.",
- "items": "Read items.",
- },
- "tokenUrl": "token",
- }
- },
- }
- },
- },
- }
diff --git a/tests/test_tutorial/test_security/test_tutorial005_an_py39.py b/tests/test_tutorial/test_security/test_tutorial005_an_py39.py
deleted file mode 100644
index 04cc1b014..000000000
--- a/tests/test_tutorial/test_security/test_tutorial005_an_py39.py
+++ /dev/null
@@ -1,437 +0,0 @@
-import pytest
-from dirty_equals import IsDict, IsOneOf
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py39
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.security.tutorial005_an_py39 import app
-
- client = TestClient(app)
- return client
-
-
-def get_access_token(
- *, username="johndoe", password="secret", scope=None, client: TestClient
-):
- data = {"username": username, "password": password}
- if scope:
- data["scope"] = scope
- response = client.post("/token", data=data)
- content = response.json()
- access_token = content.get("access_token")
- return access_token
-
-
-@needs_py39
-def test_login(client: TestClient):
- response = client.post("/token", data={"username": "johndoe", "password": "secret"})
- assert response.status_code == 200, response.text
- content = response.json()
- assert "access_token" in content
- assert content["token_type"] == "bearer"
-
-
-@needs_py39
-def test_login_incorrect_password(client: TestClient):
- response = client.post(
- "/token", data={"username": "johndoe", "password": "incorrect"}
- )
- assert response.status_code == 400, response.text
- assert response.json() == {"detail": "Incorrect username or password"}
-
-
-@needs_py39
-def test_login_incorrect_username(client: TestClient):
- response = client.post("/token", data={"username": "foo", "password": "secret"})
- assert response.status_code == 400, response.text
- assert response.json() == {"detail": "Incorrect username or password"}
-
-
-@needs_py39
-def test_no_token(client: TestClient):
- response = client.get("/users/me")
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Not authenticated"}
- assert response.headers["WWW-Authenticate"] == "Bearer"
-
-
-@needs_py39
-def test_token(client: TestClient):
- access_token = get_access_token(scope="me", client=client)
- response = client.get(
- "/users/me", headers={"Authorization": f"Bearer {access_token}"}
- )
- assert response.status_code == 200, response.text
- assert response.json() == {
- "username": "johndoe",
- "full_name": "John Doe",
- "email": "johndoe@example.com",
- "disabled": False,
- }
-
-
-@needs_py39
-def test_incorrect_token(client: TestClient):
- response = client.get("/users/me", headers={"Authorization": "Bearer nonexistent"})
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Could not validate credentials"}
- assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
-
-
-@needs_py39
-def test_incorrect_token_type(client: TestClient):
- response = client.get(
- "/users/me", headers={"Authorization": "Notexistent testtoken"}
- )
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Not authenticated"}
- assert response.headers["WWW-Authenticate"] == "Bearer"
-
-
-@needs_py39
-def test_verify_password():
- from docs_src.security.tutorial005_an_py39 import fake_users_db, verify_password
-
- assert verify_password("secret", fake_users_db["johndoe"]["hashed_password"])
-
-
-@needs_py39
-def test_get_password_hash():
- from docs_src.security.tutorial005_an_py39 import get_password_hash
-
- assert get_password_hash("secretalice")
-
-
-@needs_py39
-def test_create_access_token():
- from docs_src.security.tutorial005_an_py39 import create_access_token
-
- access_token = create_access_token(data={"data": "foo"})
- assert access_token
-
-
-@needs_py39
-def test_token_no_sub(client: TestClient):
- response = client.get(
- "/users/me",
- headers={
- "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoiZm9vIn0.9ynBhuYb4e6aW3oJr_K_TBgwcMTDpRToQIE25L57rOE"
- },
- )
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Could not validate credentials"}
- assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
-
-
-@needs_py39
-def test_token_no_username(client: TestClient):
- response = client.get(
- "/users/me",
- headers={
- "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmb28ifQ.NnExK_dlNAYyzACrXtXDrcWOgGY2JuPbI4eDaHdfK5Y"
- },
- )
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Could not validate credentials"}
- assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
-
-
-@needs_py39
-def test_token_no_scope(client: TestClient):
- access_token = get_access_token(client=client)
- response = client.get(
- "/users/me", headers={"Authorization": f"Bearer {access_token}"}
- )
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Not enough permissions"}
- assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
-
-
-@needs_py39
-def test_token_nonexistent_user(client: TestClient):
- response = client.get(
- "/users/me",
- headers={
- "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VybmFtZTpib2IifQ.HcfCW67Uda-0gz54ZWTqmtgJnZeNem0Q757eTa9EZuw"
- },
- )
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Could not validate credentials"}
- assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
-
-
-@needs_py39
-def test_token_inactive_user(client: TestClient):
- access_token = get_access_token(
- username="alice", password="secretalice", scope="me", client=client
- )
- response = client.get(
- "/users/me", headers={"Authorization": f"Bearer {access_token}"}
- )
- assert response.status_code == 400, response.text
- assert response.json() == {"detail": "Inactive user"}
-
-
-@needs_py39
-def test_read_items(client: TestClient):
- access_token = get_access_token(scope="me items", client=client)
- response = client.get(
- "/users/me/items/", headers={"Authorization": f"Bearer {access_token}"}
- )
- assert response.status_code == 200, response.text
- assert response.json() == [{"item_id": "Foo", "owner": "johndoe"}]
-
-
-@needs_py39
-def test_read_system_status(client: TestClient):
- access_token = get_access_token(client=client)
- response = client.get(
- "/status/", headers={"Authorization": f"Bearer {access_token}"}
- )
- assert response.status_code == 200, response.text
- assert response.json() == {"status": "ok"}
-
-
-@needs_py39
-def test_read_system_status_no_token(client: TestClient):
- response = client.get("/status/")
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Not authenticated"}
- assert response.headers["WWW-Authenticate"] == "Bearer"
-
-
-@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": {
- "/token": {
- "post": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Token"}
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- "summary": "Login For Access Token",
- "operationId": "login_for_access_token_token_post",
- "requestBody": {
- "content": {
- "application/x-www-form-urlencoded": {
- "schema": {
- "$ref": "#/components/schemas/Body_login_for_access_token_token_post"
- }
- }
- },
- "required": True,
- },
- }
- },
- "/users/me/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/User"}
- }
- },
- }
- },
- "summary": "Read Users Me",
- "operationId": "read_users_me_users_me__get",
- "security": [{"OAuth2PasswordBearer": ["me"]}],
- }
- },
- "/users/me/items/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- }
- },
- "summary": "Read Own Items",
- "operationId": "read_own_items_users_me_items__get",
- "security": [{"OAuth2PasswordBearer": ["items", "me"]}],
- }
- },
- "/status/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- }
- },
- "summary": "Read System Status",
- "operationId": "read_system_status_status__get",
- "security": [{"OAuth2PasswordBearer": []}],
- }
- },
- },
- "components": {
- "schemas": {
- "User": {
- "title": "User",
- "required": IsOneOf(
- ["username", "email", "full_name", "disabled"],
- # TODO: remove when deprecating Pydantic v1
- ["username"],
- ),
- "type": "object",
- "properties": {
- "username": {"title": "Username", "type": "string"},
- "email": IsDict(
- {
- "title": "Email",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Email", "type": "string"}
- ),
- "full_name": IsDict(
- {
- "title": "Full Name",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Full Name", "type": "string"}
- ),
- "disabled": IsDict(
- {
- "title": "Disabled",
- "anyOf": [{"type": "boolean"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Disabled", "type": "boolean"}
- ),
- },
- },
- "Token": {
- "title": "Token",
- "required": ["access_token", "token_type"],
- "type": "object",
- "properties": {
- "access_token": {"title": "Access Token", "type": "string"},
- "token_type": {"title": "Token Type", "type": "string"},
- },
- },
- "Body_login_for_access_token_token_post": {
- "title": "Body_login_for_access_token_token_post",
- "required": ["username", "password"],
- "type": "object",
- "properties": {
- "grant_type": IsDict(
- {
- "title": "Grant Type",
- "anyOf": [
- {"pattern": "password", "type": "string"},
- {"type": "null"},
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Grant Type",
- "pattern": "password",
- "type": "string",
- }
- ),
- "username": {"title": "Username", "type": "string"},
- "password": {"title": "Password", "type": "string"},
- "scope": {"title": "Scope", "type": "string", "default": ""},
- "client_id": IsDict(
- {
- "title": "Client Id",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Client Id", "type": "string"}
- ),
- "client_secret": IsDict(
- {
- "title": "Client Secret",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Client Secret", "type": "string"}
- ),
- },
- },
- "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"},
- }
- },
- },
- },
- "securitySchemes": {
- "OAuth2PasswordBearer": {
- "type": "oauth2",
- "flows": {
- "password": {
- "scopes": {
- "me": "Read information about the current user.",
- "items": "Read items.",
- },
- "tokenUrl": "token",
- }
- },
- }
- },
- },
- }
diff --git a/tests/test_tutorial/test_security/test_tutorial005_py310.py b/tests/test_tutorial/test_security/test_tutorial005_py310.py
deleted file mode 100644
index 98c60c1c2..000000000
--- a/tests/test_tutorial/test_security/test_tutorial005_py310.py
+++ /dev/null
@@ -1,437 +0,0 @@
-import pytest
-from dirty_equals import IsDict, IsOneOf
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py310
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.security.tutorial005_py310 import app
-
- client = TestClient(app)
- return client
-
-
-def get_access_token(
- *, username="johndoe", password="secret", scope=None, client: TestClient
-):
- data = {"username": username, "password": password}
- if scope:
- data["scope"] = scope
- response = client.post("/token", data=data)
- content = response.json()
- access_token = content.get("access_token")
- return access_token
-
-
-@needs_py310
-def test_login(client: TestClient):
- response = client.post("/token", data={"username": "johndoe", "password": "secret"})
- assert response.status_code == 200, response.text
- content = response.json()
- assert "access_token" in content
- assert content["token_type"] == "bearer"
-
-
-@needs_py310
-def test_login_incorrect_password(client: TestClient):
- response = client.post(
- "/token", data={"username": "johndoe", "password": "incorrect"}
- )
- assert response.status_code == 400, response.text
- assert response.json() == {"detail": "Incorrect username or password"}
-
-
-@needs_py310
-def test_login_incorrect_username(client: TestClient):
- response = client.post("/token", data={"username": "foo", "password": "secret"})
- assert response.status_code == 400, response.text
- assert response.json() == {"detail": "Incorrect username or password"}
-
-
-@needs_py310
-def test_no_token(client: TestClient):
- response = client.get("/users/me")
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Not authenticated"}
- assert response.headers["WWW-Authenticate"] == "Bearer"
-
-
-@needs_py310
-def test_token(client: TestClient):
- access_token = get_access_token(scope="me", client=client)
- response = client.get(
- "/users/me", headers={"Authorization": f"Bearer {access_token}"}
- )
- assert response.status_code == 200, response.text
- assert response.json() == {
- "username": "johndoe",
- "full_name": "John Doe",
- "email": "johndoe@example.com",
- "disabled": False,
- }
-
-
-@needs_py310
-def test_incorrect_token(client: TestClient):
- response = client.get("/users/me", headers={"Authorization": "Bearer nonexistent"})
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Could not validate credentials"}
- assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
-
-
-@needs_py310
-def test_incorrect_token_type(client: TestClient):
- response = client.get(
- "/users/me", headers={"Authorization": "Notexistent testtoken"}
- )
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Not authenticated"}
- assert response.headers["WWW-Authenticate"] == "Bearer"
-
-
-@needs_py310
-def test_verify_password():
- from docs_src.security.tutorial005_py310 import fake_users_db, verify_password
-
- assert verify_password("secret", fake_users_db["johndoe"]["hashed_password"])
-
-
-@needs_py310
-def test_get_password_hash():
- from docs_src.security.tutorial005_py310 import get_password_hash
-
- assert get_password_hash("secretalice")
-
-
-@needs_py310
-def test_create_access_token():
- from docs_src.security.tutorial005_py310 import create_access_token
-
- access_token = create_access_token(data={"data": "foo"})
- assert access_token
-
-
-@needs_py310
-def test_token_no_sub(client: TestClient):
- response = client.get(
- "/users/me",
- headers={
- "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoiZm9vIn0.9ynBhuYb4e6aW3oJr_K_TBgwcMTDpRToQIE25L57rOE"
- },
- )
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Could not validate credentials"}
- assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
-
-
-@needs_py310
-def test_token_no_username(client: TestClient):
- response = client.get(
- "/users/me",
- headers={
- "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmb28ifQ.NnExK_dlNAYyzACrXtXDrcWOgGY2JuPbI4eDaHdfK5Y"
- },
- )
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Could not validate credentials"}
- assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
-
-
-@needs_py310
-def test_token_no_scope(client: TestClient):
- access_token = get_access_token(client=client)
- response = client.get(
- "/users/me", headers={"Authorization": f"Bearer {access_token}"}
- )
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Not enough permissions"}
- assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
-
-
-@needs_py310
-def test_token_nonexistent_user(client: TestClient):
- response = client.get(
- "/users/me",
- headers={
- "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VybmFtZTpib2IifQ.HcfCW67Uda-0gz54ZWTqmtgJnZeNem0Q757eTa9EZuw"
- },
- )
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Could not validate credentials"}
- assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
-
-
-@needs_py310
-def test_token_inactive_user(client: TestClient):
- access_token = get_access_token(
- username="alice", password="secretalice", scope="me", client=client
- )
- response = client.get(
- "/users/me", headers={"Authorization": f"Bearer {access_token}"}
- )
- assert response.status_code == 400, response.text
- assert response.json() == {"detail": "Inactive user"}
-
-
-@needs_py310
-def test_read_items(client: TestClient):
- access_token = get_access_token(scope="me items", client=client)
- response = client.get(
- "/users/me/items/", headers={"Authorization": f"Bearer {access_token}"}
- )
- assert response.status_code == 200, response.text
- assert response.json() == [{"item_id": "Foo", "owner": "johndoe"}]
-
-
-@needs_py310
-def test_read_system_status(client: TestClient):
- access_token = get_access_token(client=client)
- response = client.get(
- "/status/", headers={"Authorization": f"Bearer {access_token}"}
- )
- assert response.status_code == 200, response.text
- assert response.json() == {"status": "ok"}
-
-
-@needs_py310
-def test_read_system_status_no_token(client: TestClient):
- response = client.get("/status/")
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Not authenticated"}
- assert response.headers["WWW-Authenticate"] == "Bearer"
-
-
-@needs_py310
-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": {
- "/token": {
- "post": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Token"}
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- "summary": "Login For Access Token",
- "operationId": "login_for_access_token_token_post",
- "requestBody": {
- "content": {
- "application/x-www-form-urlencoded": {
- "schema": {
- "$ref": "#/components/schemas/Body_login_for_access_token_token_post"
- }
- }
- },
- "required": True,
- },
- }
- },
- "/users/me/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/User"}
- }
- },
- }
- },
- "summary": "Read Users Me",
- "operationId": "read_users_me_users_me__get",
- "security": [{"OAuth2PasswordBearer": ["me"]}],
- }
- },
- "/users/me/items/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- }
- },
- "summary": "Read Own Items",
- "operationId": "read_own_items_users_me_items__get",
- "security": [{"OAuth2PasswordBearer": ["items", "me"]}],
- }
- },
- "/status/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- }
- },
- "summary": "Read System Status",
- "operationId": "read_system_status_status__get",
- "security": [{"OAuth2PasswordBearer": []}],
- }
- },
- },
- "components": {
- "schemas": {
- "User": {
- "title": "User",
- "required": IsOneOf(
- ["username", "email", "full_name", "disabled"],
- # TODO: remove when deprecating Pydantic v1
- ["username"],
- ),
- "type": "object",
- "properties": {
- "username": {"title": "Username", "type": "string"},
- "email": IsDict(
- {
- "title": "Email",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Email", "type": "string"}
- ),
- "full_name": IsDict(
- {
- "title": "Full Name",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Full Name", "type": "string"}
- ),
- "disabled": IsDict(
- {
- "title": "Disabled",
- "anyOf": [{"type": "boolean"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Disabled", "type": "boolean"}
- ),
- },
- },
- "Token": {
- "title": "Token",
- "required": ["access_token", "token_type"],
- "type": "object",
- "properties": {
- "access_token": {"title": "Access Token", "type": "string"},
- "token_type": {"title": "Token Type", "type": "string"},
- },
- },
- "Body_login_for_access_token_token_post": {
- "title": "Body_login_for_access_token_token_post",
- "required": ["username", "password"],
- "type": "object",
- "properties": {
- "grant_type": IsDict(
- {
- "title": "Grant Type",
- "anyOf": [
- {"pattern": "password", "type": "string"},
- {"type": "null"},
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Grant Type",
- "pattern": "password",
- "type": "string",
- }
- ),
- "username": {"title": "Username", "type": "string"},
- "password": {"title": "Password", "type": "string"},
- "scope": {"title": "Scope", "type": "string", "default": ""},
- "client_id": IsDict(
- {
- "title": "Client Id",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Client Id", "type": "string"}
- ),
- "client_secret": IsDict(
- {
- "title": "Client Secret",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Client Secret", "type": "string"}
- ),
- },
- },
- "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"},
- }
- },
- },
- },
- "securitySchemes": {
- "OAuth2PasswordBearer": {
- "type": "oauth2",
- "flows": {
- "password": {
- "scopes": {
- "me": "Read information about the current user.",
- "items": "Read items.",
- },
- "tokenUrl": "token",
- }
- },
- }
- },
- },
- }
diff --git a/tests/test_tutorial/test_security/test_tutorial005_py39.py b/tests/test_tutorial/test_security/test_tutorial005_py39.py
deleted file mode 100644
index cd2157d54..000000000
--- a/tests/test_tutorial/test_security/test_tutorial005_py39.py
+++ /dev/null
@@ -1,437 +0,0 @@
-import pytest
-from dirty_equals import IsDict, IsOneOf
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py39
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.security.tutorial005_py39 import app
-
- client = TestClient(app)
- return client
-
-
-def get_access_token(
- *, username="johndoe", password="secret", scope=None, client: TestClient
-):
- data = {"username": username, "password": password}
- if scope:
- data["scope"] = scope
- response = client.post("/token", data=data)
- content = response.json()
- access_token = content.get("access_token")
- return access_token
-
-
-@needs_py39
-def test_login(client: TestClient):
- response = client.post("/token", data={"username": "johndoe", "password": "secret"})
- assert response.status_code == 200, response.text
- content = response.json()
- assert "access_token" in content
- assert content["token_type"] == "bearer"
-
-
-@needs_py39
-def test_login_incorrect_password(client: TestClient):
- response = client.post(
- "/token", data={"username": "johndoe", "password": "incorrect"}
- )
- assert response.status_code == 400, response.text
- assert response.json() == {"detail": "Incorrect username or password"}
-
-
-@needs_py39
-def test_login_incorrect_username(client: TestClient):
- response = client.post("/token", data={"username": "foo", "password": "secret"})
- assert response.status_code == 400, response.text
- assert response.json() == {"detail": "Incorrect username or password"}
-
-
-@needs_py39
-def test_no_token(client: TestClient):
- response = client.get("/users/me")
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Not authenticated"}
- assert response.headers["WWW-Authenticate"] == "Bearer"
-
-
-@needs_py39
-def test_token(client: TestClient):
- access_token = get_access_token(scope="me", client=client)
- response = client.get(
- "/users/me", headers={"Authorization": f"Bearer {access_token}"}
- )
- assert response.status_code == 200, response.text
- assert response.json() == {
- "username": "johndoe",
- "full_name": "John Doe",
- "email": "johndoe@example.com",
- "disabled": False,
- }
-
-
-@needs_py39
-def test_incorrect_token(client: TestClient):
- response = client.get("/users/me", headers={"Authorization": "Bearer nonexistent"})
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Could not validate credentials"}
- assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
-
-
-@needs_py39
-def test_incorrect_token_type(client: TestClient):
- response = client.get(
- "/users/me", headers={"Authorization": "Notexistent testtoken"}
- )
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Not authenticated"}
- assert response.headers["WWW-Authenticate"] == "Bearer"
-
-
-@needs_py39
-def test_verify_password():
- from docs_src.security.tutorial005_py39 import fake_users_db, verify_password
-
- assert verify_password("secret", fake_users_db["johndoe"]["hashed_password"])
-
-
-@needs_py39
-def test_get_password_hash():
- from docs_src.security.tutorial005_py39 import get_password_hash
-
- assert get_password_hash("secretalice")
-
-
-@needs_py39
-def test_create_access_token():
- from docs_src.security.tutorial005_py39 import create_access_token
-
- access_token = create_access_token(data={"data": "foo"})
- assert access_token
-
-
-@needs_py39
-def test_token_no_sub(client: TestClient):
- response = client.get(
- "/users/me",
- headers={
- "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoiZm9vIn0.9ynBhuYb4e6aW3oJr_K_TBgwcMTDpRToQIE25L57rOE"
- },
- )
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Could not validate credentials"}
- assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
-
-
-@needs_py39
-def test_token_no_username(client: TestClient):
- response = client.get(
- "/users/me",
- headers={
- "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmb28ifQ.NnExK_dlNAYyzACrXtXDrcWOgGY2JuPbI4eDaHdfK5Y"
- },
- )
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Could not validate credentials"}
- assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
-
-
-@needs_py39
-def test_token_no_scope(client: TestClient):
- access_token = get_access_token(client=client)
- response = client.get(
- "/users/me", headers={"Authorization": f"Bearer {access_token}"}
- )
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Not enough permissions"}
- assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
-
-
-@needs_py39
-def test_token_nonexistent_user(client: TestClient):
- response = client.get(
- "/users/me",
- headers={
- "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VybmFtZTpib2IifQ.HcfCW67Uda-0gz54ZWTqmtgJnZeNem0Q757eTa9EZuw"
- },
- )
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Could not validate credentials"}
- assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
-
-
-@needs_py39
-def test_token_inactive_user(client: TestClient):
- access_token = get_access_token(
- username="alice", password="secretalice", scope="me", client=client
- )
- response = client.get(
- "/users/me", headers={"Authorization": f"Bearer {access_token}"}
- )
- assert response.status_code == 400, response.text
- assert response.json() == {"detail": "Inactive user"}
-
-
-@needs_py39
-def test_read_items(client: TestClient):
- access_token = get_access_token(scope="me items", client=client)
- response = client.get(
- "/users/me/items/", headers={"Authorization": f"Bearer {access_token}"}
- )
- assert response.status_code == 200, response.text
- assert response.json() == [{"item_id": "Foo", "owner": "johndoe"}]
-
-
-@needs_py39
-def test_read_system_status(client: TestClient):
- access_token = get_access_token(client=client)
- response = client.get(
- "/status/", headers={"Authorization": f"Bearer {access_token}"}
- )
- assert response.status_code == 200, response.text
- assert response.json() == {"status": "ok"}
-
-
-@needs_py39
-def test_read_system_status_no_token(client: TestClient):
- response = client.get("/status/")
- assert response.status_code == 401, response.text
- assert response.json() == {"detail": "Not authenticated"}
- assert response.headers["WWW-Authenticate"] == "Bearer"
-
-
-@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": {
- "/token": {
- "post": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Token"}
- }
- },
- },
- "422": {
- "description": "Validation Error",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPValidationError"
- }
- }
- },
- },
- },
- "summary": "Login For Access Token",
- "operationId": "login_for_access_token_token_post",
- "requestBody": {
- "content": {
- "application/x-www-form-urlencoded": {
- "schema": {
- "$ref": "#/components/schemas/Body_login_for_access_token_token_post"
- }
- }
- },
- "required": True,
- },
- }
- },
- "/users/me/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/User"}
- }
- },
- }
- },
- "summary": "Read Users Me",
- "operationId": "read_users_me_users_me__get",
- "security": [{"OAuth2PasswordBearer": ["me"]}],
- }
- },
- "/users/me/items/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- }
- },
- "summary": "Read Own Items",
- "operationId": "read_own_items_users_me_items__get",
- "security": [{"OAuth2PasswordBearer": ["items", "me"]}],
- }
- },
- "/status/": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- }
- },
- "summary": "Read System Status",
- "operationId": "read_system_status_status__get",
- "security": [{"OAuth2PasswordBearer": []}],
- }
- },
- },
- "components": {
- "schemas": {
- "User": {
- "title": "User",
- "required": IsOneOf(
- ["username", "email", "full_name", "disabled"],
- # TODO: remove when deprecating Pydantic v1
- ["username"],
- ),
- "type": "object",
- "properties": {
- "username": {"title": "Username", "type": "string"},
- "email": IsDict(
- {
- "title": "Email",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Email", "type": "string"}
- ),
- "full_name": IsDict(
- {
- "title": "Full Name",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Full Name", "type": "string"}
- ),
- "disabled": IsDict(
- {
- "title": "Disabled",
- "anyOf": [{"type": "boolean"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Disabled", "type": "boolean"}
- ),
- },
- },
- "Token": {
- "title": "Token",
- "required": ["access_token", "token_type"],
- "type": "object",
- "properties": {
- "access_token": {"title": "Access Token", "type": "string"},
- "token_type": {"title": "Token Type", "type": "string"},
- },
- },
- "Body_login_for_access_token_token_post": {
- "title": "Body_login_for_access_token_token_post",
- "required": ["username", "password"],
- "type": "object",
- "properties": {
- "grant_type": IsDict(
- {
- "title": "Grant Type",
- "anyOf": [
- {"pattern": "password", "type": "string"},
- {"type": "null"},
- ],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {
- "title": "Grant Type",
- "pattern": "password",
- "type": "string",
- }
- ),
- "username": {"title": "Username", "type": "string"},
- "password": {"title": "Password", "type": "string"},
- "scope": {"title": "Scope", "type": "string", "default": ""},
- "client_id": IsDict(
- {
- "title": "Client Id",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Client Id", "type": "string"}
- ),
- "client_secret": IsDict(
- {
- "title": "Client Secret",
- "anyOf": [{"type": "string"}, {"type": "null"}],
- }
- )
- | IsDict(
- # TODO: remove when deprecating Pydantic v1
- {"title": "Client Secret", "type": "string"}
- ),
- },
- },
- "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"},
- }
- },
- },
- },
- "securitySchemes": {
- "OAuth2PasswordBearer": {
- "type": "oauth2",
- "flows": {
- "password": {
- "scopes": {
- "me": "Read information about the current user.",
- "items": "Read items.",
- },
- "tokenUrl": "token",
- }
- },
- }
- },
- },
- }
diff --git a/tests/test_tutorial/test_security/test_tutorial006.py b/tests/test_tutorial/test_security/test_tutorial006.py
index dc459b6fd..40b413806 100644
--- a/tests/test_tutorial/test_security/test_tutorial006.py
+++ b/tests/test_tutorial/test_security/test_tutorial006.py
@@ -1,26 +1,41 @@
+import importlib
from base64 import b64encode
+import pytest
from fastapi.testclient import TestClient
-from docs_src.security.tutorial006 import app
+from ...utils import needs_py39
-client = TestClient(app)
+@pytest.fixture(
+ name="client",
+ params=[
+ "tutorial006",
+ "tutorial006_an",
+ pytest.param("tutorial006_an_py39", marks=needs_py39),
+ ],
+)
+def get_client(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.security.{request.param}")
-def test_security_http_basic():
+ client = TestClient(mod.app)
+ return client
+
+
+def test_security_http_basic(client: TestClient):
response = client.get("/users/me", auth=("john", "secret"))
assert response.status_code == 200, response.text
assert response.json() == {"username": "john", "password": "secret"}
-def test_security_http_basic_no_credentials():
+def test_security_http_basic_no_credentials(client: TestClient):
response = client.get("/users/me")
assert response.json() == {"detail": "Not authenticated"}
assert response.status_code == 401, response.text
assert response.headers["WWW-Authenticate"] == "Basic"
-def test_security_http_basic_invalid_credentials():
+def test_security_http_basic_invalid_credentials(client: TestClient):
response = client.get(
"/users/me", headers={"Authorization": "Basic notabase64token"}
)
@@ -29,7 +44,7 @@ def test_security_http_basic_invalid_credentials():
assert response.json() == {"detail": "Invalid authentication credentials"}
-def test_security_http_basic_non_basic_credentials():
+def test_security_http_basic_non_basic_credentials(client: TestClient):
payload = b64encode(b"johnsecret").decode("ascii")
auth_header = f"Basic {payload}"
response = client.get("/users/me", headers={"Authorization": auth_header})
@@ -38,7 +53,7 @@ def test_security_http_basic_non_basic_credentials():
assert response.json() == {"detail": "Invalid authentication credentials"}
-def test_openapi_schema():
+def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
diff --git a/tests/test_tutorial/test_security/test_tutorial006_an.py b/tests/test_tutorial/test_security/test_tutorial006_an.py
deleted file mode 100644
index 52ddd938f..000000000
--- a/tests/test_tutorial/test_security/test_tutorial006_an.py
+++ /dev/null
@@ -1,65 +0,0 @@
-from base64 import b64encode
-
-from fastapi.testclient import TestClient
-
-from docs_src.security.tutorial006_an import app
-
-client = TestClient(app)
-
-
-def test_security_http_basic():
- response = client.get("/users/me", auth=("john", "secret"))
- assert response.status_code == 200, response.text
- assert response.json() == {"username": "john", "password": "secret"}
-
-
-def test_security_http_basic_no_credentials():
- response = client.get("/users/me")
- assert response.json() == {"detail": "Not authenticated"}
- assert response.status_code == 401, response.text
- assert response.headers["WWW-Authenticate"] == "Basic"
-
-
-def test_security_http_basic_invalid_credentials():
- response = client.get(
- "/users/me", headers={"Authorization": "Basic notabase64token"}
- )
- assert response.status_code == 401, response.text
- assert response.headers["WWW-Authenticate"] == "Basic"
- assert response.json() == {"detail": "Invalid authentication credentials"}
-
-
-def test_security_http_basic_non_basic_credentials():
- payload = b64encode(b"johnsecret").decode("ascii")
- auth_header = f"Basic {payload}"
- response = client.get("/users/me", headers={"Authorization": auth_header})
- assert response.status_code == 401, response.text
- assert response.headers["WWW-Authenticate"] == "Basic"
- assert response.json() == {"detail": "Invalid authentication credentials"}
-
-
-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": {
- "/users/me": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- }
- },
- "summary": "Read Current User",
- "operationId": "read_current_user_users_me_get",
- "security": [{"HTTPBasic": []}],
- }
- }
- },
- "components": {
- "securitySchemes": {"HTTPBasic": {"type": "http", "scheme": "basic"}}
- },
- }
diff --git a/tests/test_tutorial/test_security/test_tutorial006_an_py39.py b/tests/test_tutorial/test_security/test_tutorial006_an_py39.py
deleted file mode 100644
index 52b22e573..000000000
--- a/tests/test_tutorial/test_security/test_tutorial006_an_py39.py
+++ /dev/null
@@ -1,77 +0,0 @@
-from base64 import b64encode
-
-import pytest
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py39
-
-
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.security.tutorial006_an import app
-
- client = TestClient(app)
- return client
-
-
-@needs_py39
-def test_security_http_basic(client: TestClient):
- response = client.get("/users/me", auth=("john", "secret"))
- assert response.status_code == 200, response.text
- assert response.json() == {"username": "john", "password": "secret"}
-
-
-@needs_py39
-def test_security_http_basic_no_credentials(client: TestClient):
- response = client.get("/users/me")
- assert response.json() == {"detail": "Not authenticated"}
- assert response.status_code == 401, response.text
- assert response.headers["WWW-Authenticate"] == "Basic"
-
-
-@needs_py39
-def test_security_http_basic_invalid_credentials(client: TestClient):
- response = client.get(
- "/users/me", headers={"Authorization": "Basic notabase64token"}
- )
- assert response.status_code == 401, response.text
- assert response.headers["WWW-Authenticate"] == "Basic"
- assert response.json() == {"detail": "Invalid authentication credentials"}
-
-
-@needs_py39
-def test_security_http_basic_non_basic_credentials(client: TestClient):
- payload = b64encode(b"johnsecret").decode("ascii")
- auth_header = f"Basic {payload}"
- response = client.get("/users/me", headers={"Authorization": auth_header})
- assert response.status_code == 401, response.text
- assert response.headers["WWW-Authenticate"] == "Basic"
- assert response.json() == {"detail": "Invalid authentication credentials"}
-
-
-@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": {
- "/users/me": {
- "get": {
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {"application/json": {"schema": {}}},
- }
- },
- "summary": "Read Current User",
- "operationId": "read_current_user_users_me_get",
- "security": [{"HTTPBasic": []}],
- }
- }
- },
- "components": {
- "securitySchemes": {"HTTPBasic": {"type": "http", "scheme": "basic"}}
- },
- }
diff --git a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001.py b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001.py
index cdfae9f8c..059fb889b 100644
--- a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001.py
+++ b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001.py
@@ -1,14 +1,23 @@
+import importlib
+
import pytest
from fastapi.testclient import TestClient
-from ...utils import needs_pydanticv2
+from ...utils import needs_py39, needs_py310, needs_pydanticv2
-@pytest.fixture(name="client")
-def get_client() -> TestClient:
- from docs_src.separate_openapi_schemas.tutorial001 import app
+@pytest.fixture(
+ name="client",
+ params=[
+ "tutorial001",
+ pytest.param("tutorial001_py310", marks=needs_py310),
+ pytest.param("tutorial001_py39", marks=needs_py39),
+ ],
+)
+def get_client(request: pytest.FixtureRequest) -> TestClient:
+ mod = importlib.import_module(f"docs_src.separate_openapi_schemas.{request.param}")
- client = TestClient(app)
+ client = TestClient(mod.app)
return client
diff --git a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001_py310.py b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001_py310.py
deleted file mode 100644
index 3b22146f6..000000000
--- a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001_py310.py
+++ /dev/null
@@ -1,136 +0,0 @@
-import pytest
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py310, needs_pydanticv2
-
-
-@pytest.fixture(name="client")
-def get_client() -> TestClient:
- from docs_src.separate_openapi_schemas.tutorial001_py310 import app
-
- client = TestClient(app)
- return client
-
-
-@needs_py310
-def test_create_item(client: TestClient) -> None:
- response = client.post("/items/", json={"name": "Foo"})
- assert response.status_code == 200, response.text
- assert response.json() == {"name": "Foo", "description": None}
-
-
-@needs_py310
-def test_read_items(client: TestClient) -> None:
- response = client.get("/items/")
- assert response.status_code == 200, response.text
- assert response.json() == [
- {
- "name": "Portal Gun",
- "description": "Device to travel through the multi-rick-verse",
- },
- {"name": "Plumbus", "description": None},
- ]
-
-
-@needs_py310
-@needs_pydanticv2
-def test_openapi_schema(client: TestClient) -> None:
- 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": {
- "/items/": {
- "get": {
- "summary": "Read Items",
- "operationId": "read_items_items__get",
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "items": {"$ref": "#/components/schemas/Item"},
- "type": "array",
- "title": "Response Read Items Items Get",
- }
- }
- },
- }
- },
- },
- "post": {
- "summary": "Create Item",
- "operationId": "create_item_items__post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- "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": {
- "HTTPValidationError": {
- "properties": {
- "detail": {
- "items": {"$ref": "#/components/schemas/ValidationError"},
- "type": "array",
- "title": "Detail",
- }
- },
- "type": "object",
- "title": "HTTPValidationError",
- },
- "Item": {
- "properties": {
- "name": {"type": "string", "title": "Name"},
- "description": {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Description",
- },
- },
- "type": "object",
- "required": ["name"],
- "title": "Item",
- },
- "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_tutorial/test_separate_openapi_schemas/test_tutorial001_py39.py b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001_py39.py
deleted file mode 100644
index 991abe811..000000000
--- a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001_py39.py
+++ /dev/null
@@ -1,136 +0,0 @@
-import pytest
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py39, needs_pydanticv2
-
-
-@pytest.fixture(name="client")
-def get_client() -> TestClient:
- from docs_src.separate_openapi_schemas.tutorial001_py39 import app
-
- client = TestClient(app)
- return client
-
-
-@needs_py39
-def test_create_item(client: TestClient) -> None:
- response = client.post("/items/", json={"name": "Foo"})
- assert response.status_code == 200, response.text
- assert response.json() == {"name": "Foo", "description": None}
-
-
-@needs_py39
-def test_read_items(client: TestClient) -> None:
- response = client.get("/items/")
- assert response.status_code == 200, response.text
- assert response.json() == [
- {
- "name": "Portal Gun",
- "description": "Device to travel through the multi-rick-verse",
- },
- {"name": "Plumbus", "description": None},
- ]
-
-
-@needs_py39
-@needs_pydanticv2
-def test_openapi_schema(client: TestClient) -> None:
- 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": {
- "/items/": {
- "get": {
- "summary": "Read Items",
- "operationId": "read_items_items__get",
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "items": {"$ref": "#/components/schemas/Item"},
- "type": "array",
- "title": "Response Read Items Items Get",
- }
- }
- },
- }
- },
- },
- "post": {
- "summary": "Create Item",
- "operationId": "create_item_items__post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- "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": {
- "HTTPValidationError": {
- "properties": {
- "detail": {
- "items": {"$ref": "#/components/schemas/ValidationError"},
- "type": "array",
- "title": "Detail",
- }
- },
- "type": "object",
- "title": "HTTPValidationError",
- },
- "Item": {
- "properties": {
- "name": {"type": "string", "title": "Name"},
- "description": {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Description",
- },
- },
- "type": "object",
- "required": ["name"],
- "title": "Item",
- },
- "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_tutorial/test_separate_openapi_schemas/test_tutorial002.py b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002.py
index d2cf7945b..cc9afeab7 100644
--- a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002.py
+++ b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002.py
@@ -1,14 +1,23 @@
+import importlib
+
import pytest
from fastapi.testclient import TestClient
-from ...utils import needs_pydanticv2
+from ...utils import needs_py39, needs_py310, needs_pydanticv2
-@pytest.fixture(name="client")
-def get_client() -> TestClient:
- from docs_src.separate_openapi_schemas.tutorial002 import app
+@pytest.fixture(
+ name="client",
+ params=[
+ "tutorial002",
+ pytest.param("tutorial002_py310", marks=needs_py310),
+ pytest.param("tutorial002_py39", marks=needs_py39),
+ ],
+)
+def get_client(request: pytest.FixtureRequest) -> TestClient:
+ mod = importlib.import_module(f"docs_src.separate_openapi_schemas.{request.param}")
- client = TestClient(app)
+ client = TestClient(mod.app)
return client
diff --git a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002_py310.py b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002_py310.py
deleted file mode 100644
index 89c9ce977..000000000
--- a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002_py310.py
+++ /dev/null
@@ -1,136 +0,0 @@
-import pytest
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py310, needs_pydanticv2
-
-
-@pytest.fixture(name="client")
-def get_client() -> TestClient:
- from docs_src.separate_openapi_schemas.tutorial002_py310 import app
-
- client = TestClient(app)
- return client
-
-
-@needs_py310
-def test_create_item(client: TestClient) -> None:
- response = client.post("/items/", json={"name": "Foo"})
- assert response.status_code == 200, response.text
- assert response.json() == {"name": "Foo", "description": None}
-
-
-@needs_py310
-def test_read_items(client: TestClient) -> None:
- response = client.get("/items/")
- assert response.status_code == 200, response.text
- assert response.json() == [
- {
- "name": "Portal Gun",
- "description": "Device to travel through the multi-rick-verse",
- },
- {"name": "Plumbus", "description": None},
- ]
-
-
-@needs_py310
-@needs_pydanticv2
-def test_openapi_schema(client: TestClient) -> None:
- 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": {
- "/items/": {
- "get": {
- "summary": "Read Items",
- "operationId": "read_items_items__get",
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "items": {"$ref": "#/components/schemas/Item"},
- "type": "array",
- "title": "Response Read Items Items Get",
- }
- }
- },
- }
- },
- },
- "post": {
- "summary": "Create Item",
- "operationId": "create_item_items__post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- "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": {
- "HTTPValidationError": {
- "properties": {
- "detail": {
- "items": {"$ref": "#/components/schemas/ValidationError"},
- "type": "array",
- "title": "Detail",
- }
- },
- "type": "object",
- "title": "HTTPValidationError",
- },
- "Item": {
- "properties": {
- "name": {"type": "string", "title": "Name"},
- "description": {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Description",
- },
- },
- "type": "object",
- "required": ["name"],
- "title": "Item",
- },
- "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_tutorial/test_separate_openapi_schemas/test_tutorial002_py39.py b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002_py39.py
deleted file mode 100644
index 6ac3d8f79..000000000
--- a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002_py39.py
+++ /dev/null
@@ -1,136 +0,0 @@
-import pytest
-from fastapi.testclient import TestClient
-
-from ...utils import needs_py39, needs_pydanticv2
-
-
-@pytest.fixture(name="client")
-def get_client() -> TestClient:
- from docs_src.separate_openapi_schemas.tutorial002_py39 import app
-
- client = TestClient(app)
- return client
-
-
-@needs_py39
-def test_create_item(client: TestClient) -> None:
- response = client.post("/items/", json={"name": "Foo"})
- assert response.status_code == 200, response.text
- assert response.json() == {"name": "Foo", "description": None}
-
-
-@needs_py39
-def test_read_items(client: TestClient) -> None:
- response = client.get("/items/")
- assert response.status_code == 200, response.text
- assert response.json() == [
- {
- "name": "Portal Gun",
- "description": "Device to travel through the multi-rick-verse",
- },
- {"name": "Plumbus", "description": None},
- ]
-
-
-@needs_py39
-@needs_pydanticv2
-def test_openapi_schema(client: TestClient) -> None:
- 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": {
- "/items/": {
- "get": {
- "summary": "Read Items",
- "operationId": "read_items_items__get",
- "responses": {
- "200": {
- "description": "Successful Response",
- "content": {
- "application/json": {
- "schema": {
- "items": {"$ref": "#/components/schemas/Item"},
- "type": "array",
- "title": "Response Read Items Items Get",
- }
- }
- },
- }
- },
- },
- "post": {
- "summary": "Create Item",
- "operationId": "create_item_items__post",
- "requestBody": {
- "content": {
- "application/json": {
- "schema": {"$ref": "#/components/schemas/Item"}
- }
- },
- "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": {
- "HTTPValidationError": {
- "properties": {
- "detail": {
- "items": {"$ref": "#/components/schemas/ValidationError"},
- "type": "array",
- "title": "Detail",
- }
- },
- "type": "object",
- "title": "HTTPValidationError",
- },
- "Item": {
- "properties": {
- "name": {"type": "string", "title": "Name"},
- "description": {
- "anyOf": [{"type": "string"}, {"type": "null"}],
- "title": "Description",
- },
- },
- "type": "object",
- "required": ["name"],
- "title": "Item",
- },
- "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_tutorial/test_websockets/test_tutorial002.py b/tests/test_tutorial/test_websockets/test_tutorial002.py
index bb5ccbf8e..51aa5752a 100644
--- a/tests/test_tutorial/test_websockets/test_tutorial002.py
+++ b/tests/test_tutorial/test_websockets/test_tutorial002.py
@@ -1,18 +1,37 @@
+import importlib
+
import pytest
+from fastapi import FastAPI
from fastapi.testclient import TestClient
from fastapi.websockets import WebSocketDisconnect
-from docs_src.websockets.tutorial002 import app
+from ...utils import needs_py39, needs_py310
+
+
+@pytest.fixture(
+ name="app",
+ params=[
+ "tutorial002",
+ pytest.param("tutorial002_py310", marks=needs_py310),
+ "tutorial002_an",
+ pytest.param("tutorial002_an_py39", marks=needs_py39),
+ pytest.param("tutorial002_an_py310", marks=needs_py310),
+ ],
+)
+def get_app(request: pytest.FixtureRequest):
+ mod = importlib.import_module(f"docs_src.websockets.{request.param}")
+
+ return mod.app
-def test_main():
+def test_main(app: FastAPI):
client = TestClient(app)
response = client.get("/")
assert response.status_code == 200, response.text
assert b"" in response.content
-def test_websocket_with_cookie():
+def test_websocket_with_cookie(app: FastAPI):
client = TestClient(app, cookies={"session": "fakesession"})
with pytest.raises(WebSocketDisconnect):
with client.websocket_connect("/items/foo/ws") as websocket:
@@ -30,7 +49,7 @@ def test_websocket_with_cookie():
assert data == f"Message text was: {message}, for item ID: foo"
-def test_websocket_with_header():
+def test_websocket_with_header(app: FastAPI):
client = TestClient(app)
with pytest.raises(WebSocketDisconnect):
with client.websocket_connect("/items/bar/ws?token=some-token") as websocket:
@@ -48,7 +67,7 @@ def test_websocket_with_header():
assert data == f"Message text was: {message}, for item ID: bar"
-def test_websocket_with_header_and_query():
+def test_websocket_with_header_and_query(app: FastAPI):
client = TestClient(app)
with pytest.raises(WebSocketDisconnect):
with client.websocket_connect("/items/2/ws?q=3&token=some-token") as websocket:
@@ -70,7 +89,7 @@ def test_websocket_with_header_and_query():
assert data == f"Message text was: {message}, for item ID: 2"
-def test_websocket_no_credentials():
+def test_websocket_no_credentials(app: FastAPI):
client = TestClient(app)
with pytest.raises(WebSocketDisconnect):
with client.websocket_connect("/items/foo/ws"):
@@ -79,7 +98,7 @@ def test_websocket_no_credentials():
) # pragma: no cover
-def test_websocket_invalid_data():
+def test_websocket_invalid_data(app: FastAPI):
client = TestClient(app)
with pytest.raises(WebSocketDisconnect):
with client.websocket_connect("/items/foo/ws?q=bar&token=some-token"):
diff --git a/tests/test_tutorial/test_websockets/test_tutorial002_an.py b/tests/test_tutorial/test_websockets/test_tutorial002_an.py
deleted file mode 100644
index ec78d70d3..000000000
--- a/tests/test_tutorial/test_websockets/test_tutorial002_an.py
+++ /dev/null
@@ -1,88 +0,0 @@
-import pytest
-from fastapi.testclient import TestClient
-from fastapi.websockets import WebSocketDisconnect
-
-from docs_src.websockets.tutorial002_an import app
-
-
-def test_main():
- client = TestClient(app)
- response = client.get("/")
- assert response.status_code == 200, response.text
- assert b"" in response.content
-
-
-def test_websocket_with_cookie():
- client = TestClient(app, cookies={"session": "fakesession"})
- with pytest.raises(WebSocketDisconnect):
- with client.websocket_connect("/items/foo/ws") as websocket:
- message = "Message one"
- websocket.send_text(message)
- data = websocket.receive_text()
- assert data == "Session cookie or query token value is: fakesession"
- data = websocket.receive_text()
- assert data == f"Message text was: {message}, for item ID: foo"
- message = "Message two"
- websocket.send_text(message)
- data = websocket.receive_text()
- assert data == "Session cookie or query token value is: fakesession"
- data = websocket.receive_text()
- assert data == f"Message text was: {message}, for item ID: foo"
-
-
-def test_websocket_with_header():
- client = TestClient(app)
- with pytest.raises(WebSocketDisconnect):
- with client.websocket_connect("/items/bar/ws?token=some-token") as websocket:
- message = "Message one"
- websocket.send_text(message)
- data = websocket.receive_text()
- assert data == "Session cookie or query token value is: some-token"
- data = websocket.receive_text()
- assert data == f"Message text was: {message}, for item ID: bar"
- message = "Message two"
- websocket.send_text(message)
- data = websocket.receive_text()
- assert data == "Session cookie or query token value is: some-token"
- data = websocket.receive_text()
- assert data == f"Message text was: {message}, for item ID: bar"
-
-
-def test_websocket_with_header_and_query():
- client = TestClient(app)
- with pytest.raises(WebSocketDisconnect):
- with client.websocket_connect("/items/2/ws?q=3&token=some-token") as websocket:
- message = "Message one"
- websocket.send_text(message)
- data = websocket.receive_text()
- assert data == "Session cookie or query token value is: some-token"
- data = websocket.receive_text()
- assert data == "Query parameter q is: 3"
- data = websocket.receive_text()
- assert data == f"Message text was: {message}, for item ID: 2"
- message = "Message two"
- websocket.send_text(message)
- data = websocket.receive_text()
- assert data == "Session cookie or query token value is: some-token"
- data = websocket.receive_text()
- assert data == "Query parameter q is: 3"
- data = websocket.receive_text()
- assert data == f"Message text was: {message}, for item ID: 2"
-
-
-def test_websocket_no_credentials():
- client = TestClient(app)
- with pytest.raises(WebSocketDisconnect):
- with client.websocket_connect("/items/foo/ws"):
- pytest.fail(
- "did not raise WebSocketDisconnect on __enter__"
- ) # pragma: no cover
-
-
-def test_websocket_invalid_data():
- client = TestClient(app)
- with pytest.raises(WebSocketDisconnect):
- with client.websocket_connect("/items/foo/ws?q=bar&token=some-token"):
- pytest.fail(
- "did not raise WebSocketDisconnect on __enter__"
- ) # pragma: no cover
diff --git a/tests/test_tutorial/test_websockets/test_tutorial002_an_py310.py b/tests/test_tutorial/test_websockets/test_tutorial002_an_py310.py
deleted file mode 100644
index 23b4bcb78..000000000
--- a/tests/test_tutorial/test_websockets/test_tutorial002_an_py310.py
+++ /dev/null
@@ -1,102 +0,0 @@
-import pytest
-from fastapi import FastAPI
-from fastapi.testclient import TestClient
-from fastapi.websockets import WebSocketDisconnect
-
-from ...utils import needs_py310
-
-
-@pytest.fixture(name="app")
-def get_app():
- from docs_src.websockets.tutorial002_an_py310 import app
-
- return app
-
-
-@needs_py310
-def test_main(app: FastAPI):
- client = TestClient(app)
- response = client.get("/")
- assert response.status_code == 200, response.text
- assert b"" in response.content
-
-
-@needs_py310
-def test_websocket_with_cookie(app: FastAPI):
- client = TestClient(app, cookies={"session": "fakesession"})
- with pytest.raises(WebSocketDisconnect):
- with client.websocket_connect("/items/foo/ws") as websocket:
- message = "Message one"
- websocket.send_text(message)
- data = websocket.receive_text()
- assert data == "Session cookie or query token value is: fakesession"
- data = websocket.receive_text()
- assert data == f"Message text was: {message}, for item ID: foo"
- message = "Message two"
- websocket.send_text(message)
- data = websocket.receive_text()
- assert data == "Session cookie or query token value is: fakesession"
- data = websocket.receive_text()
- assert data == f"Message text was: {message}, for item ID: foo"
-
-
-@needs_py310
-def test_websocket_with_header(app: FastAPI):
- client = TestClient(app)
- with pytest.raises(WebSocketDisconnect):
- with client.websocket_connect("/items/bar/ws?token=some-token") as websocket:
- message = "Message one"
- websocket.send_text(message)
- data = websocket.receive_text()
- assert data == "Session cookie or query token value is: some-token"
- data = websocket.receive_text()
- assert data == f"Message text was: {message}, for item ID: bar"
- message = "Message two"
- websocket.send_text(message)
- data = websocket.receive_text()
- assert data == "Session cookie or query token value is: some-token"
- data = websocket.receive_text()
- assert data == f"Message text was: {message}, for item ID: bar"
-
-
-@needs_py310
-def test_websocket_with_header_and_query(app: FastAPI):
- client = TestClient(app)
- with pytest.raises(WebSocketDisconnect):
- with client.websocket_connect("/items/2/ws?q=3&token=some-token") as websocket:
- message = "Message one"
- websocket.send_text(message)
- data = websocket.receive_text()
- assert data == "Session cookie or query token value is: some-token"
- data = websocket.receive_text()
- assert data == "Query parameter q is: 3"
- data = websocket.receive_text()
- assert data == f"Message text was: {message}, for item ID: 2"
- message = "Message two"
- websocket.send_text(message)
- data = websocket.receive_text()
- assert data == "Session cookie or query token value is: some-token"
- data = websocket.receive_text()
- assert data == "Query parameter q is: 3"
- data = websocket.receive_text()
- assert data == f"Message text was: {message}, for item ID: 2"
-
-
-@needs_py310
-def test_websocket_no_credentials(app: FastAPI):
- client = TestClient(app)
- with pytest.raises(WebSocketDisconnect):
- with client.websocket_connect("/items/foo/ws"):
- pytest.fail(
- "did not raise WebSocketDisconnect on __enter__"
- ) # pragma: no cover
-
-
-@needs_py310
-def test_websocket_invalid_data(app: FastAPI):
- client = TestClient(app)
- with pytest.raises(WebSocketDisconnect):
- with client.websocket_connect("/items/foo/ws?q=bar&token=some-token"):
- pytest.fail(
- "did not raise WebSocketDisconnect on __enter__"
- ) # pragma: no cover
diff --git a/tests/test_tutorial/test_websockets/test_tutorial002_an_py39.py b/tests/test_tutorial/test_websockets/test_tutorial002_an_py39.py
deleted file mode 100644
index 2d77f05b3..000000000
--- a/tests/test_tutorial/test_websockets/test_tutorial002_an_py39.py
+++ /dev/null
@@ -1,102 +0,0 @@
-import pytest
-from fastapi import FastAPI
-from fastapi.testclient import TestClient
-from fastapi.websockets import WebSocketDisconnect
-
-from ...utils import needs_py39
-
-
-@pytest.fixture(name="app")
-def get_app():
- from docs_src.websockets.tutorial002_an_py39 import app
-
- return app
-
-
-@needs_py39
-def test_main(app: FastAPI):
- client = TestClient(app)
- response = client.get("/")
- assert response.status_code == 200, response.text
- assert b"" in response.content
-
-
-@needs_py39
-def test_websocket_with_cookie(app: FastAPI):
- client = TestClient(app, cookies={"session": "fakesession"})
- with pytest.raises(WebSocketDisconnect):
- with client.websocket_connect("/items/foo/ws") as websocket:
- message = "Message one"
- websocket.send_text(message)
- data = websocket.receive_text()
- assert data == "Session cookie or query token value is: fakesession"
- data = websocket.receive_text()
- assert data == f"Message text was: {message}, for item ID: foo"
- message = "Message two"
- websocket.send_text(message)
- data = websocket.receive_text()
- assert data == "Session cookie or query token value is: fakesession"
- data = websocket.receive_text()
- assert data == f"Message text was: {message}, for item ID: foo"
-
-
-@needs_py39
-def test_websocket_with_header(app: FastAPI):
- client = TestClient(app)
- with pytest.raises(WebSocketDisconnect):
- with client.websocket_connect("/items/bar/ws?token=some-token") as websocket:
- message = "Message one"
- websocket.send_text(message)
- data = websocket.receive_text()
- assert data == "Session cookie or query token value is: some-token"
- data = websocket.receive_text()
- assert data == f"Message text was: {message}, for item ID: bar"
- message = "Message two"
- websocket.send_text(message)
- data = websocket.receive_text()
- assert data == "Session cookie or query token value is: some-token"
- data = websocket.receive_text()
- assert data == f"Message text was: {message}, for item ID: bar"
-
-
-@needs_py39
-def test_websocket_with_header_and_query(app: FastAPI):
- client = TestClient(app)
- with pytest.raises(WebSocketDisconnect):
- with client.websocket_connect("/items/2/ws?q=3&token=some-token") as websocket:
- message = "Message one"
- websocket.send_text(message)
- data = websocket.receive_text()
- assert data == "Session cookie or query token value is: some-token"
- data = websocket.receive_text()
- assert data == "Query parameter q is: 3"
- data = websocket.receive_text()
- assert data == f"Message text was: {message}, for item ID: 2"
- message = "Message two"
- websocket.send_text(message)
- data = websocket.receive_text()
- assert data == "Session cookie or query token value is: some-token"
- data = websocket.receive_text()
- assert data == "Query parameter q is: 3"
- data = websocket.receive_text()
- assert data == f"Message text was: {message}, for item ID: 2"
-
-
-@needs_py39
-def test_websocket_no_credentials(app: FastAPI):
- client = TestClient(app)
- with pytest.raises(WebSocketDisconnect):
- with client.websocket_connect("/items/foo/ws"):
- pytest.fail(
- "did not raise WebSocketDisconnect on __enter__"
- ) # pragma: no cover
-
-
-@needs_py39
-def test_websocket_invalid_data(app: FastAPI):
- client = TestClient(app)
- with pytest.raises(WebSocketDisconnect):
- with client.websocket_connect("/items/foo/ws?q=bar&token=some-token"):
- pytest.fail(
- "did not raise WebSocketDisconnect on __enter__"
- ) # pragma: no cover
diff --git a/tests/test_tutorial/test_websockets/test_tutorial002_py310.py b/tests/test_tutorial/test_websockets/test_tutorial002_py310.py
deleted file mode 100644
index 03bc27bdf..000000000
--- a/tests/test_tutorial/test_websockets/test_tutorial002_py310.py
+++ /dev/null
@@ -1,102 +0,0 @@
-import pytest
-from fastapi import FastAPI
-from fastapi.testclient import TestClient
-from fastapi.websockets import WebSocketDisconnect
-
-from ...utils import needs_py310
-
-
-@pytest.fixture(name="app")
-def get_app():
- from docs_src.websockets.tutorial002_py310 import app
-
- return app
-
-
-@needs_py310
-def test_main(app: FastAPI):
- client = TestClient(app)
- response = client.get("/")
- assert response.status_code == 200, response.text
- assert b"" in response.content
-
-
-@needs_py310
-def test_websocket_with_cookie(app: FastAPI):
- client = TestClient(app, cookies={"session": "fakesession"})
- with pytest.raises(WebSocketDisconnect):
- with client.websocket_connect("/items/foo/ws") as websocket:
- message = "Message one"
- websocket.send_text(message)
- data = websocket.receive_text()
- assert data == "Session cookie or query token value is: fakesession"
- data = websocket.receive_text()
- assert data == f"Message text was: {message}, for item ID: foo"
- message = "Message two"
- websocket.send_text(message)
- data = websocket.receive_text()
- assert data == "Session cookie or query token value is: fakesession"
- data = websocket.receive_text()
- assert data == f"Message text was: {message}, for item ID: foo"
-
-
-@needs_py310
-def test_websocket_with_header(app: FastAPI):
- client = TestClient(app)
- with pytest.raises(WebSocketDisconnect):
- with client.websocket_connect("/items/bar/ws?token=some-token") as websocket:
- message = "Message one"
- websocket.send_text(message)
- data = websocket.receive_text()
- assert data == "Session cookie or query token value is: some-token"
- data = websocket.receive_text()
- assert data == f"Message text was: {message}, for item ID: bar"
- message = "Message two"
- websocket.send_text(message)
- data = websocket.receive_text()
- assert data == "Session cookie or query token value is: some-token"
- data = websocket.receive_text()
- assert data == f"Message text was: {message}, for item ID: bar"
-
-
-@needs_py310
-def test_websocket_with_header_and_query(app: FastAPI):
- client = TestClient(app)
- with pytest.raises(WebSocketDisconnect):
- with client.websocket_connect("/items/2/ws?q=3&token=some-token") as websocket:
- message = "Message one"
- websocket.send_text(message)
- data = websocket.receive_text()
- assert data == "Session cookie or query token value is: some-token"
- data = websocket.receive_text()
- assert data == "Query parameter q is: 3"
- data = websocket.receive_text()
- assert data == f"Message text was: {message}, for item ID: 2"
- message = "Message two"
- websocket.send_text(message)
- data = websocket.receive_text()
- assert data == "Session cookie or query token value is: some-token"
- data = websocket.receive_text()
- assert data == "Query parameter q is: 3"
- data = websocket.receive_text()
- assert data == f"Message text was: {message}, for item ID: 2"
-
-
-@needs_py310
-def test_websocket_no_credentials(app: FastAPI):
- client = TestClient(app)
- with pytest.raises(WebSocketDisconnect):
- with client.websocket_connect("/items/foo/ws"):
- pytest.fail(
- "did not raise WebSocketDisconnect on __enter__"
- ) # pragma: no cover
-
-
-@needs_py310
-def test_websocket_invalid_data(app: FastAPI):
- client = TestClient(app)
- with pytest.raises(WebSocketDisconnect):
- with client.websocket_connect("/items/foo/ws?q=bar&token=some-token"):
- pytest.fail(
- "did not raise WebSocketDisconnect on __enter__"
- ) # pragma: no cover