diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 3ba13e0c..55749398 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1 +1,4 @@
blank_issues_enabled: false
+contact_links:
+ - name: Security Contact
+ about: Please report security vulnerabilities to security@tiangolo.com
diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml
new file mode 100644
index 00000000..8176602a
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature-request.yml
@@ -0,0 +1,181 @@
+name: Feature Request
+description: Suggest an idea or ask for a feature that you would like to have in FastAPI
+labels: [enhancement]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for your interest in FastAPI! 🚀
+
+ Please follow these instructions, fill every question, and do every step. 🙏
+
+ I'm asking this because answering questions and solving problems in GitHub issues is what consumes most of the time.
+
+ I end up not being able to add new features, fix bugs, review pull requests, etc. as fast as I wish because I have to spend too much time handling issues.
+
+ All that, on top of all the incredible help provided by a bunch of community members, the [FastAPI Experts](https://fastapi.tiangolo.com/fastapi-people/#experts), that give a lot of their time to come here and help others.
+
+ That's a lot of work they are doing, but if more FastAPI users came to help others like them just a little bit more, it would be much less effort for them (and you and me 😅).
+
+ By asking questions in a structured way (following this) it will be much easier to help you.
+
+ And there's a high chance that you will find the solution along the way and you won't even have to submit it and wait for an answer. 😎
+
+ As there are too many issues with questions, I'll have to close the incomplete ones. That will allow me (and others) to focus on helping people like you that follow the whole process and help us help you. 🤓
+ - type: checkboxes
+ id: checks
+ attributes:
+ label: First Check
+ description: Please confirm and check all the following options.
+ options:
+ - label: I added a very descriptive title to this issue.
+ required: true
+ - label: I used the GitHub search to find a similar issue and didn't find it.
+ required: true
+ - label: I searched the FastAPI documentation, with the integrated search.
+ required: true
+ - label: I already searched in Google "How to X in FastAPI" and didn't find any information.
+ required: true
+ - label: I already read and followed all the tutorial in the docs and didn't find an answer.
+ required: true
+ - label: I already checked if it is not related to FastAPI but to [Pydantic](https://github.com/samuelcolvin/pydantic).
+ required: true
+ - label: I already checked if it is not related to FastAPI but to [Swagger UI](https://github.com/swagger-api/swagger-ui).
+ required: true
+ - label: I already checked if it is not related to FastAPI but to [ReDoc](https://github.com/Redocly/redoc).
+ required: true
+ - type: checkboxes
+ id: help
+ attributes:
+ label: Commit to Help
+ description: |
+ After submitting this, I commit to one of:
+
+ * Read open issues with questions until I find 2 issues where I can help someone and add a comment to help there.
+ * I already hit the "watch" button in this repository to receive notifications and I commit to help at least 2 people that ask questions in the future.
+ * Implement a Pull Request for a confirmed bug.
+
+ options:
+ - label: I commit to help with one of those options 👆
+ required: true
+ - type: textarea
+ id: example
+ attributes:
+ label: Example Code
+ description: |
+ Please add a self-contained, [minimal, reproducible, example](https://stackoverflow.com/help/minimal-reproducible-example) with your use case.
+
+ If I (or someone) can copy it, run it, and see it right away, there's a much higher chance I (or someone) will be able to help you.
+
+ placeholder: |
+ from fastapi import FastAPI
+
+ app = FastAPI()
+
+
+ @app.get("/")
+ def read_root():
+ return {"Hello": "World"}
+ render: python
+ validations:
+ required: true
+ - type: textarea
+ id: description
+ attributes:
+ label: Description
+ description: |
+ What is your feature request?
+
+ Write a short description telling me what you are trying to solve and what you are currently doing.
+ placeholder: |
+ * Open the browser and call the endpoint `/`.
+ * It returns a JSON with `{"Hello": "World"}`.
+ * I would like it to have an extra parameter to teleport me to the moon and back.
+ validations:
+ required: true
+ - type: textarea
+ id: wanted-solution
+ attributes:
+ label: Wanted Solution
+ description: |
+ Tell me what's the solution you would like.
+ placeholder: |
+ I would like it to have a `teleport_to_moon` parameter that defaults to `False`, and can be set to `True` to teleport me.
+ validations:
+ required: true
+ - type: textarea
+ id: wanted-code
+ attributes:
+ label: Wanted Code
+ description: Show me an example of how you would want the code to look like.
+ placeholder: |
+ from fastapi import FastAPI
+
+ app = FastAPI()
+
+
+ @app.get("/", teleport_to_moon=True)
+ def read_root():
+ return {"Hello": "World"}
+ render: python
+ validations:
+ required: true
+ - type: textarea
+ id: alternatives
+ attributes:
+ label: Alternatives
+ description: |
+ Tell me about alternatives you've considered.
+ placeholder: |
+ To wait for Space X moon travel plans to drop down long after they release them. But I would rather teleport.
+ - type: dropdown
+ id: os
+ attributes:
+ label: Operating System
+ description: What operating system are you on?
+ multiple: true
+ options:
+ - Linux
+ - Windows
+ - macOS
+ - Other
+ validations:
+ required: true
+ - type: textarea
+ id: os-details
+ attributes:
+ label: Operating System Details
+ description: You can add more details about your operating system here, in particular if you chose "Other".
+ - type: input
+ id: fastapi-version
+ attributes:
+ label: FastAPI Version
+ description: |
+ What FastAPI version are you using?
+
+ You can find the FastAPI version with:
+
+ ```bash
+ python -c "import fastapi; print(fastapi.__version__)"
+ ```
+ validations:
+ required: true
+ - type: input
+ id: python-version
+ attributes:
+ label: Python Version
+ description: |
+ What Python version are you using?
+
+ You can find the Python version with:
+
+ ```bash
+ python --version
+ ```
+ validations:
+ required: true
+ - type: textarea
+ id: context
+ attributes:
+ label: Additional Context
+ description: Add any additional context information or screenshots you think are useful.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
deleted file mode 100644
index 75c02cc1..00000000
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ /dev/null
@@ -1,104 +0,0 @@
----
-name: Feature request
-about: Suggest an idea for this project
-title: ""
-labels: enhancement
-assignees: ''
-
----
-
-### First check
-
-* [ ] I added a very descriptive title to this issue.
-* [ ] I used the GitHub search to find a similar issue and didn't find it.
-* [ ] I searched the FastAPI documentation, with the integrated search.
-* [ ] I already searched in Google "How to X in FastAPI" and didn't find any information.
-* [ ] I already read and followed all the tutorial in the docs and didn't find an answer.
-* [ ] I already checked if it is not related to FastAPI but to [Pydantic](https://github.com/samuelcolvin/pydantic).
-* [ ] I already checked if it is not related to FastAPI but to [Swagger UI](https://github.com/swagger-api/swagger-ui).
-* [ ] I already checked if it is not related to FastAPI but to [ReDoc](https://github.com/Redocly/redoc).
-* [ ] After submitting this, I commit to:
- * Read open issues with questions until I find 2 issues where I can help someone and add a comment to help there.
- * Or, I already hit the "watch" button in this repository to receive notifications and I commit to help at least 2 people that ask questions in the future.
- * Implement a Pull Request for a confirmed bug.
-
-
-
-### Example
-
-Here's a self-contained [minimal, reproducible, example](https://stackoverflow.com/help/minimal-reproducible-example) with my use case:
-
-
-
-```Python
-from fastapi import FastAPI
-
-app = FastAPI()
-
-
-@app.get("/")
-def read_root():
- return {"Hello": "World"}
-```
-
-### Description
-
-
-
-* Open the browser and call the endpoint `/`.
-* It returns a JSON with `{"Hello": "World"}`.
-* I would like it to have an extra parameter to teleport me to the moon and back.
-
-### The solution you would like
-
-
-
-I would like it to have a `teleport_to_moon` parameter that defaults to `False`, and can be set to `True` to teleport me:
-
-```Python
-from fastapi import FastAPI
-
-app = FastAPI()
-
-
-@app.get("/", teleport_to_moon=True)
-def read_root():
- return {"Hello": "World"}
-```
-
-### Describe alternatives you've considered
-
-
-
-To wait for Space X moon travel plans to drop down long after they release them. But I would rather teleport.
-
-### Environment
-
-* OS: [e.g. Linux / Windows / macOS]:
-* FastAPI Version [e.g. 0.3.0]:
-
-To know the FastAPI version use:
-
-```bash
-python -c "import fastapi; print(fastapi.__version__)"
-```
-
-* Python version:
-
-To know the Python version use:
-
-```bash
-python --version
-```
-
-### Additional context
-
-
diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md
deleted file mode 100644
index c4953891..00000000
--- a/.github/ISSUE_TEMPLATE/question.md
+++ /dev/null
@@ -1,81 +0,0 @@
----
-name: Question or Problem
-about: Ask a question or ask about a problem
-title: ""
-labels: question
-assignees: ""
-
----
-
-### First check
-
-* [ ] I added a very descriptive title to this issue.
-* [ ] I used the GitHub search to find a similar issue and didn't find it.
-* [ ] I searched the FastAPI documentation, with the integrated search.
-* [ ] I already searched in Google "How to X in FastAPI" and didn't find any information.
-* [ ] I already read and followed all the tutorial in the docs and didn't find an answer.
-* [ ] I already checked if it is not related to FastAPI but to [Pydantic](https://github.com/samuelcolvin/pydantic).
-* [ ] I already checked if it is not related to FastAPI but to [Swagger UI](https://github.com/swagger-api/swagger-ui).
-* [ ] I already checked if it is not related to FastAPI but to [ReDoc](https://github.com/Redocly/redoc).
-* [ ] After submitting this, I commit to one of:
- * Read open issues with questions until I find 2 issues where I can help someone and add a comment to help there.
- * I already hit the "watch" button in this repository to receive notifications and I commit to help at least 2 people that ask questions in the future.
- * Implement a Pull Request for a confirmed bug.
-
-
-
-### Example
-
-Here's a self-contained, [minimal, reproducible, example](https://stackoverflow.com/help/minimal-reproducible-example) with my use case:
-
-
-
-```Python
-from fastapi import FastAPI
-
-app = FastAPI()
-
-
-@app.get("/")
-def read_root():
- return {"Hello": "World"}
-```
-
-### Description
-
-
-
-* Open the browser and call the endpoint `/`.
-* It returns a JSON with `{"Hello": "World"}`.
-* But I expected it to return `{"Hello": "Sara"}`.
-
-### Environment
-
-* OS: [e.g. Linux / Windows / macOS]:
-* FastAPI Version [e.g. 0.3.0]:
-
-To know the FastAPI version use:
-
-```bash
-python -c "import fastapi; print(fastapi.__version__)"
-```
-
-* Python version:
-
-To know the Python version use:
-
-```bash
-python --version
-```
-
-### Additional context
-
-
diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml
new file mode 100644
index 00000000..5c76fd17
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/question.yml
@@ -0,0 +1,146 @@
+name: Question or Problem
+description: Ask a question or ask about a problem
+labels: [question]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for your interest in FastAPI! 🚀
+
+ Please follow these instructions, fill every question, and do every step. 🙏
+
+ I'm asking this because answering questions and solving problems in GitHub issues is what consumes most of the time.
+
+ I end up not being able to add new features, fix bugs, review pull requests, etc. as fast as I wish because I have to spend too much time handling issues.
+
+ All that, on top of all the incredible help provided by a bunch of community members, the [FastAPI Experts](https://fastapi.tiangolo.com/fastapi-people/#experts), that give a lot of their time to come here and help others.
+
+ That's a lot of work they are doing, but if more FastAPI users came to help others like them just a little bit more, it would be much less effort for them (and you and me 😅).
+
+ By asking questions in a structured way (following this) it will be much easier to help you.
+
+ And there's a high chance that you will find the solution along the way and you won't even have to submit it and wait for an answer. 😎
+
+ As there are too many issues with questions, I'll have to close the incomplete ones. That will allow me (and others) to focus on helping people like you that follow the whole process and help us help you. 🤓
+ - type: checkboxes
+ id: checks
+ attributes:
+ label: First Check
+ description: Please confirm and check all the following options.
+ options:
+ - label: I added a very descriptive title to this issue.
+ required: true
+ - label: I used the GitHub search to find a similar issue and didn't find it.
+ required: true
+ - label: I searched the FastAPI documentation, with the integrated search.
+ required: true
+ - label: I already searched in Google "How to X in FastAPI" and didn't find any information.
+ required: true
+ - label: I already read and followed all the tutorial in the docs and didn't find an answer.
+ required: true
+ - label: I already checked if it is not related to FastAPI but to [Pydantic](https://github.com/samuelcolvin/pydantic).
+ required: true
+ - label: I already checked if it is not related to FastAPI but to [Swagger UI](https://github.com/swagger-api/swagger-ui).
+ required: true
+ - label: I already checked if it is not related to FastAPI but to [ReDoc](https://github.com/Redocly/redoc).
+ required: true
+ - type: checkboxes
+ id: help
+ attributes:
+ label: Commit to Help
+ description: |
+ After submitting this, I commit to one of:
+
+ * Read open issues with questions until I find 2 issues where I can help someone and add a comment to help there.
+ * I already hit the "watch" button in this repository to receive notifications and I commit to help at least 2 people that ask questions in the future.
+ * Implement a Pull Request for a confirmed bug.
+
+ options:
+ - label: I commit to help with one of those options 👆
+ required: true
+ - type: textarea
+ id: example
+ attributes:
+ label: Example Code
+ description: |
+ Please add a self-contained, [minimal, reproducible, example](https://stackoverflow.com/help/minimal-reproducible-example) with your use case.
+
+ If I (or someone) can copy it, run it, and see it right away, there's a much higher chance I (or someone) will be able to help you.
+
+ placeholder: |
+ from fastapi import FastAPI
+
+ app = FastAPI()
+
+
+ @app.get("/")
+ def read_root():
+ return {"Hello": "World"}
+ render: python
+ validations:
+ required: true
+ - type: textarea
+ id: description
+ attributes:
+ label: Description
+ description: |
+ What is the problem, question, or error?
+
+ Write a short description telling me what you are doing, what you expect to happen, and what is currently happening.
+ placeholder: |
+ * Open the browser and call the endpoint `/`.
+ * It returns a JSON with `{"Hello": "World"}`.
+ * But I expected it to return `{"Hello": "Sara"}`.
+ validations:
+ required: true
+ - type: dropdown
+ id: os
+ attributes:
+ label: Operating System
+ description: What operating system are you on?
+ multiple: true
+ options:
+ - Linux
+ - Windows
+ - macOS
+ - Other
+ validations:
+ required: true
+ - type: textarea
+ id: os-details
+ attributes:
+ label: Operating System Details
+ description: You can add more details about your operating system here, in particular if you chose "Other".
+ - type: input
+ id: fastapi-version
+ attributes:
+ label: FastAPI Version
+ description: |
+ What FastAPI version are you using?
+
+ You can find the FastAPI version with:
+
+ ```bash
+ python -c "import fastapi; print(fastapi.__version__)"
+ ```
+ validations:
+ required: true
+ - type: input
+ id: python-version
+ attributes:
+ label: Python Version
+ description: |
+ What Python version are you using?
+
+ You can find the Python version with:
+
+ ```bash
+ python --version
+ ```
+ validations:
+ required: true
+ - type: textarea
+ id: context
+ attributes:
+ label: Additional Context
+ description: Add any additional context information or screenshots you think are useful.
diff --git a/.github/actions/notify-translations/Dockerfile b/.github/actions/notify-translations/Dockerfile
new file mode 100644
index 00000000..fa4197e6
--- /dev/null
+++ b/.github/actions/notify-translations/Dockerfile
@@ -0,0 +1,7 @@
+FROM python:3.7
+
+RUN pip install httpx PyGithub "pydantic==1.5.1" "pyyaml>=5.3.1,<6.0.0"
+
+COPY ./app /app
+
+CMD ["python", "/app/main.py"]
diff --git a/.github/actions/notify-translations/action.yml b/.github/actions/notify-translations/action.yml
new file mode 100644
index 00000000..c3579977
--- /dev/null
+++ b/.github/actions/notify-translations/action.yml
@@ -0,0 +1,10 @@
+name: "Notify Translations"
+description: "Notify in the issue for a translation when there's a new PR available"
+author: "Sebastián Ramírez
-
-
+
+
@@ -14,6 +14,9 @@
+
+
+
+
-
+
+
+requests - Required if you want to use the `TestClient`.
-* aiofiles - Required if you want to use `FileResponse` or `StaticFiles`.
* jinja2 - Required if you want to use the default template configuration.
* python-multipart - Required if you want to support form "parsing", with `request.form()`.
* itsdangerous - Required for `SessionMiddleware` support.
* pyyaml - Required for Starlette's `SchemaGenerator` support (you probably don't need it with FastAPI).
-* graphene - Required for `GraphQLApp` support.
* ujson - Required if you want to use `UJSONResponse`.
Used by FastAPI / Starlette:
@@ -453,7 +460,7 @@ Used by FastAPI / Starlette:
* uvicorn - for the server that loads and serves your application.
* orjson - Required if you want to use `ORJSONResponse`.
-You can install all of these with `pip install fastapi[all]`.
+You can install all of these with `pip install "fastapi[all]"`.
## License
diff --git a/docs/az/mkdocs.yml b/docs/az/mkdocs.yml
new file mode 100644
index 00000000..58bbb075
--- /dev/null
+++ b/docs/az/mkdocs.yml
@@ -0,0 +1,135 @@
+site_name: FastAPI
+site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production
+site_url: https://fastapi.tiangolo.com/az/
+theme:
+ name: material
+ custom_dir: overrides
+ palette:
+ - scheme: default
+ primary: teal
+ accent: amber
+ toggle:
+ icon: material/lightbulb
+ name: Switch to light mode
+ - scheme: slate
+ primary: teal
+ accent: amber
+ toggle:
+ icon: material/lightbulb-outline
+ name: Switch to dark mode
+ features:
+ - search.suggest
+ - search.highlight
+ - content.tabs.link
+ icon:
+ repo: fontawesome/brands/github-alt
+ logo: https://fastapi.tiangolo.com/img/icon-white.svg
+ favicon: https://fastapi.tiangolo.com/img/favicon.png
+ language: en
+repo_name: tiangolo/fastapi
+repo_url: https://github.com/tiangolo/fastapi
+edit_uri: ''
+plugins:
+- search
+- markdownextradata:
+ data: data
+nav:
+- FastAPI: index.md
+- Languages:
+ - en: /
+ - az: /az/
+ - de: /de/
+ - es: /es/
+ - fa: /fa/
+ - fr: /fr/
+ - id: /id/
+ - it: /it/
+ - ja: /ja/
+ - ko: /ko/
+ - nl: /nl/
+ - pl: /pl/
+ - pt: /pt/
+ - ru: /ru/
+ - sq: /sq/
+ - tr: /tr/
+ - uk: /uk/
+ - zh: /zh/
+markdown_extensions:
+- toc:
+ permalink: true
+- markdown.extensions.codehilite:
+ guess_lang: false
+- mdx_include:
+ base_path: docs
+- admonition
+- codehilite
+- extra
+- pymdownx.superfences:
+ custom_fences:
+ - name: mermaid
+ class: mermaid
+ format: !!python/name:pymdownx.superfences.fence_code_format ''
+- pymdownx.tabbed:
+ alternate_style: true
+extra:
+ analytics:
+ provider: google
+ property: UA-133183413-1
+ social:
+ - icon: fontawesome/brands/github-alt
+ link: https://github.com/tiangolo/fastapi
+ - icon: fontawesome/brands/discord
+ link: https://discord.gg/VQjSZaeJmf
+ - icon: fontawesome/brands/twitter
+ link: https://twitter.com/fastapi
+ - icon: fontawesome/brands/linkedin
+ link: https://www.linkedin.com/in/tiangolo
+ - icon: fontawesome/brands/dev
+ link: https://dev.to/tiangolo
+ - icon: fontawesome/brands/medium
+ link: https://medium.com/@tiangolo
+ - icon: fontawesome/solid/globe
+ link: https://tiangolo.com
+ alternate:
+ - link: /
+ name: en - English
+ - link: /az/
+ name: az
+ - link: /de/
+ name: de
+ - link: /es/
+ name: es - español
+ - link: /fa/
+ name: fa
+ - link: /fr/
+ name: fr - français
+ - link: /id/
+ name: id
+ - link: /it/
+ name: it - italiano
+ - link: /ja/
+ name: ja - 日本語
+ - link: /ko/
+ name: ko - 한국어
+ - link: /nl/
+ name: nl
+ - link: /pl/
+ name: pl
+ - link: /pt/
+ name: pt - português
+ - link: /ru/
+ name: ru - русский язык
+ - link: /sq/
+ name: sq - shqip
+ - link: /tr/
+ name: tr - Türkçe
+ - link: /uk/
+ name: uk - українська мова
+ - link: /zh/
+ name: zh - 汉语
+extra_css:
+- https://fastapi.tiangolo.com/css/termynal.css
+- https://fastapi.tiangolo.com/css/custom.css
+extra_javascript:
+- https://fastapi.tiangolo.com/js/termynal.js
+- https://fastapi.tiangolo.com/js/custom.js
diff --git a/docs/az/overrides/.gitignore b/docs/az/overrides/.gitignore
new file mode 100644
index 00000000..e69de29b
diff --git a/docs/de/docs/features.md b/docs/de/docs/features.md
new file mode 100644
index 00000000..a92a2bfe
--- /dev/null
+++ b/docs/de/docs/features.md
@@ -0,0 +1,203 @@
+# Merkmale
+
+## FastAPI Merkmale
+
+**FastAPI** ermöglicht Ihnen folgendes:
+
+### Basiert auf offenen Standards
+
+* OpenAPI für API-Erstellung, zusammen mit Deklarationen von Pfad Operationen, Parameter, Nachrichtenrumpf-Anfragen (englisch: body request), Sicherheit, etc.
+* Automatische Dokumentation der Datenentitäten mit dem JSON Schema (OpenAPI basiert selber auf dem JSON Schema).
+* Entworfen auf Grundlage dieser Standards nach einer sorgfältigen Studie, statt einer nachträglichen Schicht über diesen Standards.
+* Dies ermöglicht automatische **Quellcode-Generierung auf Benutzerebene** in vielen Sprachen.
+
+### Automatische Dokumentation
+
+Mit einer interaktiven API-Dokumentation und explorativen webbasierten Benutzerschnittstellen. Da FastAPI auf OpenAPI basiert, gibt es hierzu mehrere Optionen, wobei zwei standardmäßig vorhanden sind.
+
+* Swagger UI, bietet interaktive Exploration: testen und rufen Sie ihre API direkt vom Webbrowser auf.
+
+
+
+* Alternative API-Dokumentation mit ReDoc.
+
+
+
+### Nur modernes Python
+
+Alles basiert auf **Python 3.6 Typ**-Deklarationen (dank Pydantic). Es muss keine neue Syntax gelernt werden, nur standardisiertes modernes Python.
+
+
+
+Wenn Sie eine kurze, zweiminütige, Auffrischung in der Benutzung von Python Typ-Deklarationen benötigen (auch wenn Sie FastAPI nicht nutzen), schauen Sie sich diese kurze Einführung an (Englisch): Python Types{.internal-link target=_blank}.
+
+Sie schreiben Standard-Python mit Typ-Deklarationen:
+
+```Python
+from typing import List, Dict
+from datetime import date
+
+from pydantic import BaseModel
+
+# Deklariere eine Variable als str
+# und bekomme Editor-Unterstütung innerhalb der Funktion
+def main(user_id: str):
+ return user_id
+
+
+# Ein Pydantic model
+class User(BaseModel):
+ id: int
+ name: str
+ joined: date
+```
+
+Dies kann nun wiefolgt benutzt werden:
+
+```Python
+my_user: User = User(id=3, name="John Doe", joined="2018-07-19")
+
+second_user_data = {
+ "id": 4,
+ "name": "Mary",
+ "joined": "2018-11-30",
+}
+
+my_second_user: User = User(**second_user_data)
+```
+
+!!! info
+ `**second_user_data` bedeutet:
+
+ Übergebe die Schlüssel und die zugehörigen Werte des `second_user_data` Datenwörterbuches direkt als Schlüssel-Wert Argumente, äquivalent zu: `User(id=4, name="Mary", joined="2018-11-30")`
+
+### Editor Unterstützung
+
+FastAPI wurde so entworfen, dass es einfach und intuitiv zu benutzen ist; alle Entscheidungen wurden auf mehreren Editoren getestet (sogar vor der eigentlichen Implementierung), um so eine best mögliche Entwicklererfahrung zu gewährleisten.
+
+In der letzen Python Entwickler Umfrage stellte sich heraus, dass die meist genutzte Funktion die "Autovervollständigung" ist.
+
+Die gesamte Struktur von **FastAPI** soll dem gerecht werden. Autovervollständigung funktioniert überall.
+
+Sie müssen selten in die Dokumentation schauen.
+
+So kann ihr Editor Sie unterstützen:
+
+* in Visual Studio Code:
+
+
+
+* in PyCharm:
+
+
+
+Sie bekommen Autovervollständigung an Stellen, an denen Sie dies vorher nicht für möglich gehalten hätten. Zum Beispiel der `price` Schlüssel aus einem JSON Datensatz (dieser könnte auch verschachtelt sein) aus einer Anfrage.
+
+Hierdurch werden Sie nie wieder einen falschen Schlüsselnamen benutzen und sparen sich lästiges Suchen in der Dokumentation, um beispielsweise herauszufinden ob Sie `username` oder `user_name` als Schlüssel verwenden.
+
+### Kompakt
+
+FastAPI nutzt für alles sinnvolle **Standard-Einstellungen**, welche optional überall konfiguriert werden können. Alle Parameter können ganz genau an Ihre Bedürfnisse angepasst werden, sodass sie genau die API definieren können, die sie brauchen.
+
+Aber standardmäßig, **"funktioniert einfach"** alles.
+
+### Validierung
+
+* Validierung für die meisten (oder alle?) Python **Datentypen**, hierzu gehören:
+ * JSON Objekte (`dict`).
+ * JSON Listen (`list`), die den Typ ihrer Elemente definieren.
+ * Zeichenketten (`str`), mit definierter minimaler und maximaler Länge.
+ * Zahlen (`int`, `float`) mit minimaler und maximaler Größe, usw.
+
+* Validierung für ungewöhnliche Typen, wie:
+ * URL.
+ * Email.
+ * UUID.
+ * ... und andere.
+
+Die gesamte Validierung übernimmt das etablierte und robuste **Pydantic**.
+
+### Sicherheit und Authentifizierung
+
+Sicherheit und Authentifizierung integriert. Ohne einen Kompromiss aufgrund einer Datenbank oder den Datenentitäten.
+
+Unterstützt alle von OpenAPI definierten Sicherheitsschemata, hierzu gehören:
+
+* HTTP Basis Authentifizierung.
+* **OAuth2** (auch mit **JWT Zugriffstokens**). Schauen Sie sich hierzu dieses Tutorial an: [OAuth2 mit JWT](tutorial/security/oauth2-jwt.md){.internal-link target=_blank}.
+* API Schlüssel in:
+ * Kopfzeile (HTTP Header).
+ * Anfrageparametern.
+ * Cookies, etc.
+
+Zusätzlich gibt es alle Sicherheitsfunktionen von Starlette (auch **session cookies**).
+
+Alles wurde als wiederverwendbare Werkzeuge und Komponenten geschaffen, die einfach in ihre Systeme, Datenablagen, relationale und nicht-relationale Datenbanken, ..., integriert werden können.
+
+### Einbringen von Abhängigkeiten (meist: Dependency Injection)
+
+FastAPI enthält ein extrem einfaches, aber extrem mächtiges Dependency Injection System.
+
+* Selbst Abhängigkeiten können Abhängigkeiten haben, woraus eine Hierachie oder ein **"Graph" von Abhängigkeiten** entsteht.
+* **Automatische Umsetzung** durch FastAPI.
+* Alle abhängigen Komponenten könnten Daten von Anfragen, **Erweiterungen der Pfadoperations-**Einschränkungen und der automatisierten Dokumentation benötigen.
+* **Automatische Validierung** selbst für *Pfadoperationen*-Parameter, die in den Abhängigkeiten definiert wurden.
+* Unterstützt komplexe Benutzerauthentifizierungssysteme, mit **Datenbankverbindungen**, usw.
+* **Keine Kompromisse** bei Datenbanken, Eingabemasken, usw., sondern einfache Integration von allen.
+
+### Unbegrenzte Erweiterungen
+
+Oder mit anderen Worten, sie werden nicht benötigt. Importieren und nutzen Sie Quellcode nach Bedarf.
+
+Jede Integration wurde so entworfen, dass sie einfach zu nutzen ist (mit Abhängigkeiten), sodass Sie eine Erweiterung für Ihre Anwendung mit nur zwei Zeilen an Quellcode implementieren können. Hierbei nutzen Sie die selbe Struktur und Syntax, wie bei Pfadoperationen.
+
+### Getestet
+
+* 100% Testabdeckung.
+* 100% Typen annotiert.
+* Verwendet in Produktionsanwendungen.
+
+## Starlette's Merkmale
+
+**FastAPI** ist vollkommen kompatibel (und basiert auf) Starlette. Das bedeutet, auch ihr eigener Starlette Quellcode funktioniert.
+
+`FastAPI` ist eigentlich eine Unterklasse von `Starlette`. Wenn Sie also bereits Starlette kennen oder benutzen, können Sie das meiste Ihres Wissens direkt anwenden.
+
+Mit **FastAPI** bekommen Sie viele von **Starlette**'s Funktionen (da FastAPI nur Starlette auf Steroiden ist):
+
+* Stark beeindruckende Performanz. Es ist eines der schnellsten Python frameworks, auf Augenhöhe mit **NodeJS** und **Go**.
+* **WebSocket**-Unterstützung.
+* Hintergrundaufgaben im selben Prozess.
+* Ereignisse für das Starten und Herunterfahren.
+* Testclient basierend auf `requests`.
+* **CORS**, GZip, statische Dateien, Antwortfluss.
+* **Sitzungs und Cookie** Unterstützung.
+* 100% Testabdeckung.
+* 100% Typen annotiert.
+
+## Pydantic's Merkmale
+
+**FastAPI** ist vollkommen kompatibel (und basiert auf) Pydantic. Das bedeutet, auch jeder zusätzliche Pydantic Quellcode funktioniert.
+
+Verfügbar sind ebenso externe auf Pydantic basierende Bibliotheken, wie ORMs, ODMs für Datenbanken.
+
+Daher können Sie in vielen Fällen das Objekt einer Anfrage **direkt zur Datenbank** schicken, weil alles automatisch validiert wird.
+
+Das selbe gilt auch für die andere Richtung: Sie können jedes Objekt aus der Datenbank **direkt zum Klienten** schicken.
+
+Mit **FastAPI** bekommen Sie alle Funktionen von **Pydantic** (da FastAPI für die gesamte Datenverarbeitung Pydantic nutzt):
+
+* **Kein Kopfzerbrechen**:
+ * Sie müssen keine neue Schemadefinitionssprache lernen.
+ * Wenn Sie mit Python's Typisierung arbeiten können, können Sie auch mit Pydantic arbeiten.
+* Gutes Zusammenspiel mit Ihrer/Ihrem **IDE/linter/Gehirn**:
+ * Weil Datenstrukturen von Pydantic einfach nur Instanzen ihrer definierten Klassen sind, sollten Autovervollständigung, Linting, mypy und ihre Intuition einwandfrei funktionieren.
+* **Schnell**:
+ * In Vergleichen ist Pydantic schneller als jede andere getestete Bibliothek.
+* Validierung von **komplexen Strukturen**:
+ * Benutzung von hierachischen Pydantic Schemata, Python `typing`’s `List` und `Dict`, etc.
+ * Validierungen erlauben klare und einfache Datenschemadefinition, überprüft und dokumentiert als JSON Schema.
+ * Sie können stark **verschachtelte JSON** Objekte haben und diese sind trotzdem validiert und annotiert.
+* **Erweiterbar**:
+ * Pydantic erlaubt die Definition von eigenen Datentypen oder Sie können die Validierung mit einer `validator` dekorierten Methode erweitern..
+* 100% Testabdeckung.
diff --git a/docs/de/docs/index.md b/docs/de/docs/index.md
new file mode 100644
index 00000000..cdce6622
--- /dev/null
+++ b/docs/de/docs/index.md
@@ -0,0 +1,464 @@
+
+{!../../../docs/missing-translation.md!}
+
+
+
++ FastAPI framework, high performance, easy to learn, fast to code, ready for production +
+ + +--- + +**Documentation**: https://fastapi.tiangolo.com + +**Source Code**: https://github.com/tiangolo/fastapi + +--- + +FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints. + +The key features are: + +* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance). + +* **Fast to code**: Increase the speed to develop features by about 200% to 300%. * +* **Fewer bugs**: Reduce about 40% of human (developer) induced errors. * +* **Intuitive**: Great editor support. Completion everywhere. Less time debugging. +* **Easy**: Designed to be easy to use and learn. Less time reading docs. +* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs. +* **Robust**: Get production-ready code. With automatic interactive documentation. +* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: OpenAPI (previously known as Swagger) and JSON Schema. + +* estimation based on tests on an internal development team, building production applications. + +## Sponsors + + + +{% if sponsors %} +{% for sponsor in sponsors.gold -%} +async def...uvicorn main:app --reload...ujson - for faster JSON "parsing".
+* email_validator - for email validation.
+
+Used by Starlette:
+
+* requests - Required if you want to use the `TestClient`.
+* jinja2 - Required if you want to use the default template configuration.
+* python-multipart - Required if you want to support form "parsing", with `request.form()`.
+* itsdangerous - Required for `SessionMiddleware` support.
+* pyyaml - Required for Starlette's `SchemaGenerator` support (you probably don't need it with FastAPI).
+* ujson - Required if you want to use `UJSONResponse`.
+
+Used by FastAPI / Starlette:
+
+* uvicorn - for the server that loads and serves your application.
+* orjson - Required if you want to use `ORJSONResponse`.
+
+You can install all of these with `pip install fastapi[all]`.
+
+## License
+
+This project is licensed under the terms of the MIT license.
diff --git a/docs/de/mkdocs.yml b/docs/de/mkdocs.yml
new file mode 100644
index 00000000..1242af50
--- /dev/null
+++ b/docs/de/mkdocs.yml
@@ -0,0 +1,136 @@
+site_name: FastAPI
+site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production
+site_url: https://fastapi.tiangolo.com/de/
+theme:
+ name: material
+ custom_dir: overrides
+ palette:
+ - scheme: default
+ primary: teal
+ accent: amber
+ toggle:
+ icon: material/lightbulb
+ name: Switch to light mode
+ - scheme: slate
+ primary: teal
+ accent: amber
+ toggle:
+ icon: material/lightbulb-outline
+ name: Switch to dark mode
+ features:
+ - search.suggest
+ - search.highlight
+ - content.tabs.link
+ icon:
+ repo: fontawesome/brands/github-alt
+ logo: https://fastapi.tiangolo.com/img/icon-white.svg
+ favicon: https://fastapi.tiangolo.com/img/favicon.png
+ language: de
+repo_name: tiangolo/fastapi
+repo_url: https://github.com/tiangolo/fastapi
+edit_uri: ''
+plugins:
+- search
+- markdownextradata:
+ data: data
+nav:
+- FastAPI: index.md
+- Languages:
+ - en: /
+ - az: /az/
+ - de: /de/
+ - es: /es/
+ - fa: /fa/
+ - fr: /fr/
+ - id: /id/
+ - it: /it/
+ - ja: /ja/
+ - ko: /ko/
+ - nl: /nl/
+ - pl: /pl/
+ - pt: /pt/
+ - ru: /ru/
+ - sq: /sq/
+ - tr: /tr/
+ - uk: /uk/
+ - zh: /zh/
+- features.md
+markdown_extensions:
+- toc:
+ permalink: true
+- markdown.extensions.codehilite:
+ guess_lang: false
+- mdx_include:
+ base_path: docs
+- admonition
+- codehilite
+- extra
+- pymdownx.superfences:
+ custom_fences:
+ - name: mermaid
+ class: mermaid
+ format: !!python/name:pymdownx.superfences.fence_code_format ''
+- pymdownx.tabbed:
+ alternate_style: true
+extra:
+ analytics:
+ provider: google
+ property: UA-133183413-1
+ social:
+ - icon: fontawesome/brands/github-alt
+ link: https://github.com/tiangolo/fastapi
+ - icon: fontawesome/brands/discord
+ link: https://discord.gg/VQjSZaeJmf
+ - icon: fontawesome/brands/twitter
+ link: https://twitter.com/fastapi
+ - icon: fontawesome/brands/linkedin
+ link: https://www.linkedin.com/in/tiangolo
+ - icon: fontawesome/brands/dev
+ link: https://dev.to/tiangolo
+ - icon: fontawesome/brands/medium
+ link: https://medium.com/@tiangolo
+ - icon: fontawesome/solid/globe
+ link: https://tiangolo.com
+ alternate:
+ - link: /
+ name: en - English
+ - link: /az/
+ name: az
+ - link: /de/
+ name: de
+ - link: /es/
+ name: es - español
+ - link: /fa/
+ name: fa
+ - link: /fr/
+ name: fr - français
+ - link: /id/
+ name: id
+ - link: /it/
+ name: it - italiano
+ - link: /ja/
+ name: ja - 日本語
+ - link: /ko/
+ name: ko - 한국어
+ - link: /nl/
+ name: nl
+ - link: /pl/
+ name: pl
+ - link: /pt/
+ name: pt - português
+ - link: /ru/
+ name: ru - русский язык
+ - link: /sq/
+ name: sq - shqip
+ - link: /tr/
+ name: tr - Türkçe
+ - link: /uk/
+ name: uk - українська мова
+ - link: /zh/
+ name: zh - 汉语
+extra_css:
+- https://fastapi.tiangolo.com/css/termynal.css
+- https://fastapi.tiangolo.com/css/custom.css
+extra_javascript:
+- https://fastapi.tiangolo.com/js/termynal.js
+- https://fastapi.tiangolo.com/js/custom.js
diff --git a/docs/de/overrides/.gitignore b/docs/de/overrides/.gitignore
new file mode 100644
index 00000000..e69de29b
diff --git a/docs/en/data/external_links.yml b/docs/en/data/external_links.yml
index 8d175cbe..b918c8fd 100644
--- a/docs/en/data/external_links.yml
+++ b/docs/en/data/external_links.yml
@@ -1,258 +1,310 @@
articles:
english:
- - link: https://medium.com/@williamhayes/fastapi-starlette-debug-vs-prod-5f7561db3a59
- title: FastAPI/Starlette debug vs prod
- author_link: https://medium.com/@williamhayes
- author: William Hayes
- - link: https://medium.com/data-rebels/fastapi-google-as-an-external-authentication-provider-3a527672cf33
- title: FastAPI — Google as an external authentication provider
- author_link: https://medium.com/@nilsdebruin
- author: Nils de Bruin
- - link: https://medium.com/data-rebels/fastapi-how-to-add-basic-and-cookie-authentication-a45c85ef47d3
- title: FastAPI — How to add basic and cookie authentication
- author_link: https://medium.com/@nilsdebruin
- author: Nils de Bruin
- - link: https://dev.to/errietta/introduction-to-the-fastapi-python-framework-2n10
- title: Introduction to the fastapi python framework
- author_link: https://dev.to/errietta
- author: Errieta Kostala
- - link: https://nickc1.github.io/api,/scikit-learn/2019/01/10/scikit-fastapi.html
- title: "FastAPI and Scikit-Learn: Easily Deploy Models"
- author_link: https://nickc1.github.io/
- author: Nick Cortale
- - link: https://medium.com/data-rebels/fastapi-authentication-revisited-enabling-api-key-authentication-122dc5975680
- title: "FastAPI authentication revisited: Enabling API key authentication"
- author_link: https://medium.com/@nilsdebruin
- author: Nils de Bruin
- - link: https://medium.com/@nico.axtmann95/deploying-a-scikit-learn-model-with-onnx-und-fastapi-1af398268915
- title: Deploying a scikit-learn model with ONNX and FastAPI
- author_link: https://www.linkedin.com/in/nico-axtmann
- author: Nico Axtmann
- - link: https://geekflare.com/python-asynchronous-web-frameworks/
- title: Top 5 Asynchronous Web Frameworks for Python
- author_link: https://geekflare.com/author/ankush/
- author: Ankush Thakur
- - link: https://medium.com/@gntrm/jwt-authentication-with-fastapi-and-aws-cognito-1333f7f2729e
- title: JWT Authentication with FastAPI and AWS Cognito
- author_link: https://twitter.com/gntrm
- author: Johannes Gontrum
- - link: https://towardsdatascience.com/how-to-deploy-a-machine-learning-model-dc51200fe8cf
- title: How to Deploy a Machine Learning Model
- author_link: https://www.linkedin.com/in/mgrootendorst/
- author: Maarten Grootendorst
- - link: https://eng.uber.com/ludwig-v0-2/
- title: "Uber: Ludwig v0.2 Adds New Features and Other Improvements to its Deep Learning Toolbox [including a FastAPI server]"
- author_link: https://eng.uber.com
- author: Uber Engineering
- - link: https://gitlab.com/euri10/fastapi_cheatsheet
- title: A FastAPI and Swagger UI visual cheatsheet
- author_link: https://gitlab.com/euri10
- author: "@euri10"
- - link: https://medium.com/@mike.p.moritz/using-docker-compose-to-deploy-a-lightweight-python-rest-api-with-a-job-queue-37e6072a209b
- title: Using Docker Compose to deploy a lightweight Python REST API with a job queue
- author_link: https://medium.com/@mike.p.moritz
- author: Mike Moritz
- - link: https://robwagner.dev/tortoise-fastapi-setup/
- title: Setting up Tortoise ORM with FastAPI
- author_link: https://robwagner.dev/
- author: Rob Wagner
- - link: https://dev.to/dbanty/why-i-m-leaving-flask-3ki6
- title: Why I'm Leaving Flask
- author_link: https://dev.to/dbanty
- author: Dylan Anthony
- - link: https://medium.com/python-data/how-to-deploy-tensorflow-2-0-models-as-an-api-service-with-fastapi-docker-128b177e81f3
- title: How To Deploy Tensorflow 2.0 Models As An API Service With FastAPI & Docker
- author_link: https://medium.com/@bbrenyah
- author: Bernard Brenyah
- - link: https://testdriven.io/blog/fastapi-crud/
- title: "TestDriven.io: Developing and Testing an Asynchronous API with FastAPI and Pytest"
- author_link: https://testdriven.io/authors/herman
- author: Michael Herman
- - link: https://towardsdatascience.com/deploying-iris-classifications-with-fastapi-and-docker-7c9b83fdec3a
- title: "Towards Data Science: Deploying Iris Classifications with FastAPI and Docker"
- author_link: https://towardsdatascience.com/@mandygu
- author: Mandy Gu
- - link: https://medium.com/analytics-vidhya/deploy-machine-learning-models-with-keras-fastapi-redis-and-docker-4940df614ece
- title: Deploy Machine Learning Models with Keras, FastAPI, Redis and Docker
- author_link: https://medium.com/@shane.soh
- author: Shane Soh
- - link: https://medium.com/@arthur393/another-boilerplate-to-fastapi-azure-pipeline-ci-pytest-3c8d9a4be0bb
- title: "Another Boilerplate to FastAPI: Azure Pipeline CI + Pytest"
- author_link: https://twitter.com/arthurheinrique
- author: Arthur Henrique
- - link: https://iwpnd.pw/articles/2020-01/deploy-fastapi-to-aws-lambda
- title: How to continuously deploy a FastAPI to AWS Lambda with AWS SAM
- author_link: https://iwpnd.pw
- author: Benjamin Ramser
- - link: https://www.tutlinks.com/create-and-deploy-fastapi-app-to-heroku/
- title: Create and Deploy FastAPI app to Heroku without using Docker
- author_link: https://www.linkedin.com/in/navule/
- author: Navule Pavan Kumar Rao
- - link: https://iwpnd.pw/articles/2020-03/apache-kafka-fastapi-geostream
- title: Apache Kafka producer and consumer with FastAPI and aiokafka
- author_link: https://iwpnd.pw
- author: Benjamin Ramser
- - link: https://wuilly.com/2019/10/real-time-notifications-with-python-and-postgres/
- title: Real-time Notifications with Python and Postgres
- author_link: https://wuilly.com/
- author: Guillermo Cruz
- - link: https://dev.to/paurakhsharma/microservice-in-python-using-fastapi-24cc
- title: Microservice in Python using FastAPI
- author_link: https://twitter.com/PaurakhSharma
- author: Paurakh Sharma Humagain
- - link: https://dev.to/cuongld2/build-simple-api-service-with-python-fastapi-part-1-581o
- title: Build simple API service with Python FastAPI — Part 1
- author_link: https://dev.to/cuongld2
- author: cuongld2
- - link: https://paulsec.github.io/posts/fastapi_plus_zeit_serverless_fu/
- title: FastAPI + Zeit.co = 🚀
- author_link: https://twitter.com/PaulWebSec
- author: Paul Sec
- - link: https://dev.to/tiangolo/build-a-web-api-from-scratch-with-fastapi-the-workshop-2ehe
- title: Build a web API from scratch with FastAPI - the workshop
- author_link: https://twitter.com/tiangolo
- author: Sebastián Ramírez (tiangolo)
- - link: https://www.twilio.com/blog/build-secure-twilio-webhook-python-fastapi
- title: Build a Secure Twilio Webhook with Python and FastAPI
- author_link: https://www.twilio.com
- author: Twilio
- - link: https://www.stavros.io/posts/fastapi-with-django/
- title: Using FastAPI with Django
- author_link: https://twitter.com/Stavros
- author: Stavros Korokithakis
- - link: https://netflixtechblog.com/introducing-dispatch-da4b8a2a8072
- title: Introducing Dispatch
- author_link: https://netflixtechblog.com/
- author: Netflix
- - link: https://davidefiocco.github.io/streamlit-fastapi-ml-serving/
- title: Machine learning model serving in Python using FastAPI and streamlit
- author_link: https://github.com/davidefiocco
- author: Davide Fiocco
- - link: https://www.tutlinks.com/deploy-fastapi-on-azure/
- title: Deploy FastAPI on Azure App Service
- author_link: https://www.linkedin.com/in/navule/
- author: Navule Pavan Kumar Rao
- - link: https://towardsdatascience.com/build-and-host-fast-data-science-applications-using-fastapi-823be8a1d6a0
- title: Build And Host Fast Data Science Applications Using FastAPI
- author_link: https://medium.com/@farhadmalik
- author: Farhad Malik
- - link: https://medium.com/@gabbyprecious2000/creating-a-crud-app-with-fastapi-part-one-7c049292ad37
- title: Creating a CRUD App with FastAPI (Part one)
- author_link: https://medium.com/@gabbyprecious2000
- author: Precious Ndubueze
- - link: https://julienharbulot.com/notification-server.html
- title: HTTP server to display desktop notifications
- author_link: https://julienharbulot.com/
- author: Julien Harbulot
- - link: https://guitton.co/posts/fastapi-monitoring/
- title: How to monitor your FastAPI service
- author_link: https://twitter.com/louis_guitton
- author: Louis Guitton
- - link: https://amitness.com/2020/06/fastapi-vs-flask/
- title: FastAPI for Flask Users
- author_link: https://twitter.com/amitness
- author: Amit Chaudhary
- - link: https://valonjanuzaj.medium.com/deploy-a-dockerized-fastapi-application-to-aws-cc757830ba1b
- title: Deploy a dockerized FastAPI application to AWS
- author_link: https://www.linkedin.com/in/valon-januzaj-b02692187/
- author: Valon Januzaj
- - link: https://dompatmore.com/blog/authenticate-your-fastapi-app-with-auth0
- title: Authenticate Your FastAPI App with auth0
- author_link: https://twitter.com/dompatmore
- author: Dom Patmore
- japanese:
- - link: https://qiita.com/mtitg/items/47770e9a562dd150631d
- title: FastAPI|DB接続してCRUDするPython製APIサーバーを構築
- author_link: https://qiita.com/mtitg
- author: "@mtitg"
- - link: https://qiita.com/ryoryomaru/items/59958ed385b3571d50de
- title: python製の最新APIフレームワーク FastAPI を触ってみた
- author_link: https://qiita.com/ryoryomaru
- author: "@ryoryomaru"
- - link: https://qiita.com/angel_katayoku/items/0e1f5dbbe62efc612a78
- title: FastAPIでCORSを回避
- author_link: https://qiita.com/angel_katayoku
- author: "@angel_katayoku"
- - link: https://qiita.com/angel_katayoku/items/4fbc1a4e2b33fa2237d2
- title: FastAPIをMySQLと接続してDockerで管理してみる
- author_link: https://qiita.com/angel_katayoku
- author: "@angel_katayoku"
- - link: https://qiita.com/angel_katayoku/items/8a458a8952f50b73f420
- title: FastAPIでPOSTされたJSONのレスポンスbodyを受け取る
- author_link: https://qiita.com/angel_katayoku
- author: "@angel_katayoku"
- - link: https://qiita.com/hikarut/items/b178af2e2440c67c6ac4
- title: フロントエンド開発者向けのDockerによるPython開発環境構築
- author_link: https://qiita.com/hikarut
- author: Hikaru Takahashi
- - link: https://rightcode.co.jp/blog/information-technology/fastapi-tutorial-todo-apps-environment
- title: "【第1回】FastAPIチュートリアル: ToDoアプリを作ってみよう【環境構築編】"
- author_link: https://rightcode.co.jp/author/jun
- author: ライトコードメディア編集部
- - link: https://rightcode.co.jp/blog/information-technology/fastapi-tutorial-todo-apps-model-building
- title: "【第2回】FastAPIチュートリアル: ToDoアプリを作ってみよう【モデル構築編】"
- author_link: https://rightcode.co.jp/author/jun
- author: ライトコードメディア編集部
- - link: https://rightcode.co.jp/blog/information-technology/fastapi-tutorial-todo-apps-authentication-user-registration
- title: "【第3回】FastAPIチュートリアル: toDoアプリを作ってみよう【認証・ユーザ登録編】"
- author_link: https://rightcode.co.jp/author/jun
- author: ライトコードメディア編集部
- - link: https://rightcode.co.jp/blog/information-technology/fastapi-tutorial-todo-apps-admin-page-improvement
- title: "【第4回】FastAPIチュートリアル: toDoアプリを作ってみよう【管理者ページ改良編】"
- author_link: https://rightcode.co.jp/author/jun
- author: ライトコードメディア編集部
- - link: https://qiita.com/bee2/items/0ad260ab9835a2087dae
- title: PythonのWeb frameworkのパフォーマンス比較 (Django, Flask, responder, FastAPI, japronto)
- author_link: https://qiita.com/bee2
- author: "@bee2"
- - link: https://qiita.com/bee2/items/75d9c0d7ba20e7a4a0e9
- title: "[FastAPI] Python製のASGI Web フレームワーク FastAPIに入門する"
- author_link: https://qiita.com/bee2
- author: "@bee2"
- vietnamese:
- - link: https://fullstackstation.com/fastapi-trien-khai-bang-docker/
- title: "FASTAPI: TRIỂN KHAI BẰNG DOCKER"
- author_link: https://fullstackstation.com/author/figonking/
- author: Nguyễn Nhân
- russian:
- - link: https://habr.com/ru/post/454440/
- title: "Мелкая питонячая радость #2: Starlette - Солидная примочка – FastAPI"
- author_link: https://habr.com/ru/users/57uff3r/
- author: Andrey Korchak
- - link: https://habr.com/ru/post/478620/
- title: Почему Вы должны попробовать FastAPI?
- author_link: https://github.com/prostomarkeloff
- author: prostomarkeloff
- - link: https://trkohler.com/fast-api-introduction-to-framework
- title: "FastAPI: знакомимся с фреймворком"
- author_link: https://www.linkedin.com/in/trkohler/
- author: Troy Köhler
+ - author: Silvan Melchior
+ author_link: https://github.com/silvanmelchior
+ link: https://blog.devgenius.io/seamless-fastapi-configuration-with-confz-90949c14ea12
+ title: Seamless FastAPI Configuration with ConfZ
+ - author: Kaustubh Gupta
+ author_link: https://medium.com/@kaustubhgupta1828/
+ link: https://levelup.gitconnected.com/5-advance-features-of-fastapi-you-should-try-7c0ac7eebb3e
+ title: 5 Advanced Features of FastAPI You Should Try
+ - author: Kaustubh Gupta
+ author_link: https://medium.com/@kaustubhgupta1828/
+ link: https://www.analyticsvidhya.com/blog/2021/06/deploying-ml-models-as-api-using-fastapi-and-heroku/
+ title: Deploying ML Models as API Using FastAPI and Heroku
+ - link: https://jarmos.netlify.app/posts/using-github-actions-to-deploy-a-fastapi-project-to-heroku/
+ title: Using GitHub Actions to Deploy a FastAPI Project to Heroku
+ author_link: https://jarmos.netlify.app/
+ author: Somraj Saha
+ - author: "@pystar"
+ author_link: https://pystar.substack.com/
+ link: https://pystar.substack.com/p/how-to-create-a-fake-certificate
+ title: How to Create A Fake Certificate Authority And Generate TLS Certs for FastAPI
+ - author: Ben Gamble
+ author_link: https://uk.linkedin.com/in/bengamble7
+ link: https://ably.com/blog/realtime-ticket-booking-solution-kafka-fastapi-ably
+ title: Building a realtime ticket booking solution with Kafka, FastAPI, and Ably
+ - author: Shahriyar(Shako) Rzayev
+ author_link: https://www.linkedin.com/in/shahriyar-rzayev/
+ link: https://www.azepug.az/posts/fastapi/#building-simple-e-commerce-with-nuxtjs-and-fastapi-series
+ title: Building simple E-Commerce with NuxtJS and FastAPI
+ - author: Rodrigo Arenas
+ author_link: https://rodrigo-arenas.medium.com/
+ link: https://medium.com/analytics-vidhya/serve-a-machine-learning-model-using-sklearn-fastapi-and-docker-85aabf96729b
+ title: "Serve a machine learning model using Sklearn, FastAPI and Docker"
+ - author: Yashasvi Singh
+ author_link: https://hashnode.com/@aUnicornDev
+ link: https://aunicorndev.hashnode.dev/series/supafast-api
+ title: "Building an API with FastAPI and Supabase and Deploying on Deta"
+ - author: Navule Pavan Kumar Rao
+ author_link: https://www.linkedin.com/in/navule/
+ link: https://www.tutlinks.com/deploy-fastapi-on-ubuntu-gunicorn-caddy-2/
+ title: Deploy FastAPI on Ubuntu and Serve using Caddy 2 Web Server
+ - author: Patrick Ladon
+ author_link: https://dev.to/factorlive
+ link: https://dev.to/factorlive/python-facebook-messenger-webhook-with-fastapi-on-glitch-4n90
+ title: Python Facebook messenger webhook with FastAPI on Glitch
+ - author: Dom Patmore
+ author_link: https://twitter.com/dompatmore
+ link: https://dompatmore.com/blog/authenticate-your-fastapi-app-with-auth0
+ title: Authenticate Your FastAPI App with auth0
+ - author: Valon Januzaj
+ author_link: https://www.linkedin.com/in/valon-januzaj-b02692187/
+ link: https://valonjanuzaj.medium.com/deploy-a-dockerized-fastapi-application-to-aws-cc757830ba1b
+ title: Deploy a dockerized FastAPI application to AWS
+ - author: Amit Chaudhary
+ author_link: https://twitter.com/amitness
+ link: https://amitness.com/2020/06/fastapi-vs-flask/
+ title: FastAPI for Flask Users
+ - author: Louis Guitton
+ author_link: https://twitter.com/louis_guitton
+ link: https://guitton.co/posts/fastapi-monitoring/
+ title: How to monitor your FastAPI service
+ - author: Julien Harbulot
+ author_link: https://julienharbulot.com/
+ link: https://julienharbulot.com/notification-server.html
+ title: HTTP server to display desktop notifications
+ - author: Precious Ndubueze
+ author_link: https://medium.com/@gabbyprecious2000
+ link: https://medium.com/@gabbyprecious2000/creating-a-crud-app-with-fastapi-part-one-7c049292ad37
+ title: Creating a CRUD App with FastAPI (Part one)
+ - author: Farhad Malik
+ author_link: https://medium.com/@farhadmalik
+ link: https://towardsdatascience.com/build-and-host-fast-data-science-applications-using-fastapi-823be8a1d6a0
+ title: Build And Host Fast Data Science Applications Using FastAPI
+ - author: Navule Pavan Kumar Rao
+ author_link: https://www.linkedin.com/in/navule/
+ link: https://www.tutlinks.com/deploy-fastapi-on-azure/
+ title: Deploy FastAPI on Azure App Service
+ - author: Davide Fiocco
+ author_link: https://github.com/davidefiocco
+ link: https://davidefiocco.github.io/streamlit-fastapi-ml-serving/
+ title: Machine learning model serving in Python using FastAPI and streamlit
+ - author: Netflix
+ author_link: https://netflixtechblog.com/
+ link: https://netflixtechblog.com/introducing-dispatch-da4b8a2a8072
+ title: Introducing Dispatch
+ - author: Stavros Korokithakis
+ author_link: https://twitter.com/Stavros
+ link: https://www.stavros.io/posts/fastapi-with-django/
+ title: Using FastAPI with Django
+ - author: Twilio
+ author_link: https://www.twilio.com
+ link: https://www.twilio.com/blog/build-secure-twilio-webhook-python-fastapi
+ title: Build a Secure Twilio Webhook with Python and FastAPI
+ - author: Sebastián Ramírez (tiangolo)
+ author_link: https://twitter.com/tiangolo
+ link: https://dev.to/tiangolo/build-a-web-api-from-scratch-with-fastapi-the-workshop-2ehe
+ title: Build a web API from scratch with FastAPI - the workshop
+ - author: Paul Sec
+ author_link: https://twitter.com/PaulWebSec
+ link: https://paulsec.github.io/posts/fastapi_plus_zeit_serverless_fu/
+ title: FastAPI + Zeit.co = 🚀
+ - author: cuongld2
+ author_link: https://dev.to/cuongld2
+ link: https://dev.to/cuongld2/build-simple-api-service-with-python-fastapi-part-1-581o
+ title: Build simple API service with Python FastAPI — Part 1
+ - author: Paurakh Sharma Humagain
+ author_link: https://twitter.com/PaurakhSharma
+ link: https://dev.to/paurakhsharma/microservice-in-python-using-fastapi-24cc
+ title: Microservice in Python using FastAPI
+ - author: Guillermo Cruz
+ author_link: https://wuilly.com/
+ link: https://wuilly.com/2019/10/real-time-notifications-with-python-and-postgres/
+ title: Real-time Notifications with Python and Postgres
+ - author: Benjamin Ramser
+ author_link: https://iwpnd.pw
+ link: https://iwpnd.pw/articles/2020-03/apache-kafka-fastapi-geostream
+ title: Apache Kafka producer and consumer with FastAPI and aiokafka
+ - author: Navule Pavan Kumar Rao
+ author_link: https://www.linkedin.com/in/navule/
+ link: https://www.tutlinks.com/create-and-deploy-fastapi-app-to-heroku/
+ title: Create and Deploy FastAPI app to Heroku without using Docker
+ - author: Benjamin Ramser
+ author_link: https://iwpnd.pw
+ link: https://iwpnd.pw/articles/2020-01/deploy-fastapi-to-aws-lambda
+ title: How to continuously deploy a FastAPI to AWS Lambda with AWS SAM
+ - author: Arthur Henrique
+ author_link: https://twitter.com/arthurheinrique
+ link: https://medium.com/@arthur393/another-boilerplate-to-fastapi-azure-pipeline-ci-pytest-3c8d9a4be0bb
+ title: 'Another Boilerplate to FastAPI: Azure Pipeline CI + Pytest'
+ - author: Shane Soh
+ author_link: https://medium.com/@shane.soh
+ link: https://medium.com/analytics-vidhya/deploy-machine-learning-models-with-keras-fastapi-redis-and-docker-4940df614ece
+ title: Deploy Machine Learning Models with Keras, FastAPI, Redis and Docker
+ - author: Mandy Gu
+ author_link: https://towardsdatascience.com/@mandygu
+ link: https://towardsdatascience.com/deploying-iris-classifications-with-fastapi-and-docker-7c9b83fdec3a
+ title: 'Towards Data Science: Deploying Iris Classifications with FastAPI and Docker'
+ - author: Michael Herman
+ author_link: https://testdriven.io/authors/herman
+ link: https://testdriven.io/blog/fastapi-crud/
+ title: 'TestDriven.io: Developing and Testing an Asynchronous API with FastAPI and Pytest'
+ - author: Bernard Brenyah
+ author_link: https://medium.com/@bbrenyah
+ link: https://medium.com/python-data/how-to-deploy-tensorflow-2-0-models-as-an-api-service-with-fastapi-docker-128b177e81f3
+ title: How To Deploy Tensorflow 2.0 Models As An API Service With FastAPI & Docker
+ - author: Dylan Anthony
+ author_link: https://dev.to/dbanty
+ link: https://dev.to/dbanty/why-i-m-leaving-flask-3ki6
+ title: Why I'm Leaving Flask
+ - author: Rob Wagner
+ author_link: https://robwagner.dev/
+ link: https://robwagner.dev/tortoise-fastapi-setup/
+ title: Setting up Tortoise ORM with FastAPI
+ - author: Mike Moritz
+ author_link: https://medium.com/@mike.p.moritz
+ link: https://medium.com/@mike.p.moritz/using-docker-compose-to-deploy-a-lightweight-python-rest-api-with-a-job-queue-37e6072a209b
+ title: Using Docker Compose to deploy a lightweight Python REST API with a job queue
+ - author: '@euri10'
+ author_link: https://gitlab.com/euri10
+ link: https://gitlab.com/euri10/fastapi_cheatsheet
+ title: A FastAPI and Swagger UI visual cheatsheet
+ - author: Uber Engineering
+ author_link: https://eng.uber.com
+ link: https://eng.uber.com/ludwig-v0-2/
+ title: 'Uber: Ludwig v0.2 Adds New Features and Other Improvements to its Deep Learning Toolbox [including a FastAPI server]'
+ - author: Maarten Grootendorst
+ author_link: https://www.linkedin.com/in/mgrootendorst/
+ link: https://towardsdatascience.com/how-to-deploy-a-machine-learning-model-dc51200fe8cf
+ title: How to Deploy a Machine Learning Model
+ - author: Johannes Gontrum
+ author_link: https://twitter.com/gntrm
+ link: https://medium.com/@gntrm/jwt-authentication-with-fastapi-and-aws-cognito-1333f7f2729e
+ title: JWT Authentication with FastAPI and AWS Cognito
+ - author: Ankush Thakur
+ author_link: https://geekflare.com/author/ankush/
+ link: https://geekflare.com/python-asynchronous-web-frameworks/
+ title: Top 5 Asynchronous Web Frameworks for Python
+ - author: Nico Axtmann
+ author_link: https://www.linkedin.com/in/nico-axtmann
+ link: https://medium.com/@nico.axtmann95/deploying-a-scikit-learn-model-with-onnx-und-fastapi-1af398268915
+ title: Deploying a scikit-learn model with ONNX and FastAPI
+ - author: Nils de Bruin
+ author_link: https://medium.com/@nilsdebruin
+ link: https://medium.com/data-rebels/fastapi-authentication-revisited-enabling-api-key-authentication-122dc5975680
+ title: 'FastAPI authentication revisited: Enabling API key authentication'
+ - author: Nick Cortale
+ author_link: https://nickc1.github.io/
+ link: https://nickc1.github.io/api,/scikit-learn/2019/01/10/scikit-fastapi.html
+ title: 'FastAPI and Scikit-Learn: Easily Deploy Models'
+ - author: Errieta Kostala
+ author_link: https://dev.to/errietta
+ link: https://dev.to/errietta/introduction-to-the-fastapi-python-framework-2n10
+ title: Introduction to the fastapi python framework
+ - author: Nils de Bruin
+ author_link: https://medium.com/@nilsdebruin
+ link: https://medium.com/data-rebels/fastapi-how-to-add-basic-and-cookie-authentication-a45c85ef47d3
+ title: FastAPI — How to add basic and cookie authentication
+ - author: Nils de Bruin
+ author_link: https://medium.com/@nilsdebruin
+ link: https://medium.com/data-rebels/fastapi-google-as-an-external-authentication-provider-3a527672cf33
+ title: FastAPI — Google as an external authentication provider
+ - author: William Hayes
+ author_link: https://medium.com/@williamhayes
+ link: https://medium.com/@williamhayes/fastapi-starlette-debug-vs-prod-5f7561db3a59
+ title: FastAPI/Starlette debug vs prod
+ - author: Mukul Mantosh
+ author_link: https://twitter.com/MantoshMukul
+ link: https://www.jetbrains.com/pycharm/guide/tutorials/fastapi-aws-kubernetes/
+ title: Developing FastAPI Application using K8s & AWS
german:
- - link: https://blog.codecentric.de/2019/08/inbetriebnahme-eines-scikit-learn-modells-mit-onnx-und-fastapi/
- title: Inbetriebnahme eines scikit-learn-Modells mit ONNX und FastAPI
- author_link: https://twitter.com/_nicoax
- author: Nico Axtmann
+ - author: Nico Axtmann
+ author_link: https://twitter.com/_nicoax
+ link: https://blog.codecentric.de/2019/08/inbetriebnahme-eines-scikit-learn-modells-mit-onnx-und-fastapi/
+ title: Inbetriebnahme eines scikit-learn-Modells mit ONNX und FastAPI
+ - author: Felix Schürmeyer
+ author_link: https://hellocoding.de/autor/felix-schuermeyer/
+ link: https://hellocoding.de/blog/coding-language/python/fastapi
+ title: REST-API Programmieren mittels Python und dem FastAPI Modul
+ japanese:
+ - author: '@bee2'
+ author_link: https://qiita.com/bee2
+ link: https://qiita.com/bee2/items/75d9c0d7ba20e7a4a0e9
+ title: '[FastAPI] Python製のASGI Web フレームワーク FastAPIに入門する'
+ - author: '@bee2'
+ author_link: https://qiita.com/bee2
+ link: https://qiita.com/bee2/items/0ad260ab9835a2087dae
+ title: PythonのWeb frameworkのパフォーマンス比較 (Django, Flask, responder, FastAPI, japronto)
+ - author: ライトコードメディア編集部
+ author_link: https://rightcode.co.jp/author/jun
+ link: https://rightcode.co.jp/blog/information-technology/fastapi-tutorial-todo-apps-admin-page-improvement
+ title: '【第4回】FastAPIチュートリアル: toDoアプリを作ってみよう【管理者ページ改良編】'
+ - author: ライトコードメディア編集部
+ author_link: https://rightcode.co.jp/author/jun
+ link: https://rightcode.co.jp/blog/information-technology/fastapi-tutorial-todo-apps-authentication-user-registration
+ title: '【第3回】FastAPIチュートリアル: toDoアプリを作ってみよう【認証・ユーザ登録編】'
+ - author: ライトコードメディア編集部
+ author_link: https://rightcode.co.jp/author/jun
+ link: https://rightcode.co.jp/blog/information-technology/fastapi-tutorial-todo-apps-model-building
+ title: '【第2回】FastAPIチュートリアル: ToDoアプリを作ってみよう【モデル構築編】'
+ - author: ライトコードメディア編集部
+ author_link: https://rightcode.co.jp/author/jun
+ link: https://rightcode.co.jp/blog/information-technology/fastapi-tutorial-todo-apps-environment
+ title: '【第1回】FastAPIチュートリアル: ToDoアプリを作ってみよう【環境構築編】'
+ - author: Hikaru Takahashi
+ author_link: https://qiita.com/hikarut
+ link: https://qiita.com/hikarut/items/b178af2e2440c67c6ac4
+ title: フロントエンド開発者向けのDockerによるPython開発環境構築
+ - author: '@angel_katayoku'
+ author_link: https://qiita.com/angel_katayoku
+ link: https://qiita.com/angel_katayoku/items/8a458a8952f50b73f420
+ title: FastAPIでPOSTされたJSONのレスポンスbodyを受け取る
+ - author: '@angel_katayoku'
+ author_link: https://qiita.com/angel_katayoku
+ link: https://qiita.com/angel_katayoku/items/4fbc1a4e2b33fa2237d2
+ title: FastAPIをMySQLと接続してDockerで管理してみる
+ - author: '@angel_katayoku'
+ author_link: https://qiita.com/angel_katayoku
+ link: https://qiita.com/angel_katayoku/items/0e1f5dbbe62efc612a78
+ title: FastAPIでCORSを回避
+ - author: '@ryoryomaru'
+ author_link: https://qiita.com/ryoryomaru
+ link: https://qiita.com/ryoryomaru/items/59958ed385b3571d50de
+ title: python製の最新APIフレームワーク FastAPI を触ってみた
+ - author: '@mtitg'
+ author_link: https://qiita.com/mtitg
+ link: https://qiita.com/mtitg/items/47770e9a562dd150631d
+ title: FastAPI|DB接続してCRUDするPython製APIサーバーを構築
+ russian:
+ - author: Troy Köhler
+ author_link: https://www.linkedin.com/in/trkohler/
+ link: https://trkohler.com/fast-api-introduction-to-framework
+ title: 'FastAPI: знакомимся с фреймворком'
+ - author: prostomarkeloff
+ author_link: https://github.com/prostomarkeloff
+ link: https://habr.com/ru/post/478620/
+ title: Почему Вы должны попробовать FastAPI?
+ - author: Andrey Korchak
+ author_link: https://habr.com/ru/users/57uff3r/
+ link: https://habr.com/ru/post/454440/
+ title: 'Мелкая питонячая радость #2: Starlette - Солидная примочка – FastAPI'
+ vietnamese:
+ - author: Nguyễn Nhân
+ author_link: https://fullstackstation.com/author/figonking/
+ link: https://fullstackstation.com/fastapi-trien-khai-bang-docker/
+ title: 'FASTAPI: TRIỂN KHAI BẰNG DOCKER'
podcasts:
english:
- - link: https://pythonbytes.fm/episodes/show/123/time-to-right-the-py-wrongs?time_in_sec=855
- title: FastAPI on PythonBytes
- author_link: https://pythonbytes.fm/
- author: Python Bytes FM
- - link: https://www.pythonpodcast.com/fastapi-web-application-framework-episode-259/
- title: "Build The Next Generation Of Python Web Applications With FastAPI - Episode 259 - interview to Sebastían Ramírez (tiangolo)"
- author_link: https://www.pythonpodcast.com/
- author: Podcast.`__init__`
+ - author: Podcast.`__init__`
+ author_link: https://www.pythonpodcast.com/
+ link: https://www.pythonpodcast.com/fastapi-web-application-framework-episode-259/
+ title: Build The Next Generation Of Python Web Applications With FastAPI - Episode 259 - interview to Sebastían Ramírez (tiangolo)
+ - author: Python Bytes FM
+ author_link: https://pythonbytes.fm/
+ link: https://pythonbytes.fm/episodes/show/123/time-to-right-the-py-wrongs?time_in_sec=855
+ title: FastAPI on PythonBytes
talks:
english:
- - link: https://www.youtube.com/watch?v=3DLwPcrE5mA
- title: "PyCon UK 2019: FastAPI from the ground up"
- author_link: https://twitter.com/chriswithers13
- author: Chris Withers
- - link: https://www.youtube.com/watch?v=z9K5pwb0rt8
- title: "PyConBY 2020: Serve ML models easily with FastAPI"
- author_link: https://twitter.com/tiangolo
- author: "Sebastián Ramírez (tiangolo)"
- - link: https://www.youtube.com/watch?v=PnpTY1f4k2U
- title: "[VIRTUAL] Py.Amsterdam's flying Software Circus: Intro to FastAPI"
- author_link: https://twitter.com/tiangolo
- author: "Sebastián Ramírez (tiangolo)"
+ - author: Sebastián Ramírez (tiangolo)
+ author_link: https://twitter.com/tiangolo
+ link: https://www.youtube.com/watch?v=PnpTY1f4k2U
+ title: '[VIRTUAL] Py.Amsterdam''s flying Software Circus: Intro to FastAPI'
+ - author: Sebastián Ramírez (tiangolo)
+ author_link: https://twitter.com/tiangolo
+ link: https://www.youtube.com/watch?v=z9K5pwb0rt8
+ title: 'PyConBY 2020: Serve ML models easily with FastAPI'
+ - author: Chris Withers
+ author_link: https://twitter.com/chriswithers13
+ link: https://www.youtube.com/watch?v=3DLwPcrE5mA
+ title: 'PyCon UK 2019: FastAPI from the ground up'
diff --git a/docs/en/data/github_sponsors.yml b/docs/en/data/github_sponsors.yml
new file mode 100644
index 00000000..db4a9acc
--- /dev/null
+++ b/docs/en/data/github_sponsors.yml
@@ -0,0 +1,496 @@
+sponsors:
+- - login: cryptapi
+ avatarUrl: https://avatars.githubusercontent.com/u/44925437?u=61369138589bc7fee6c417f3fbd50fbd38286cc4&v=4
+ url: https://github.com/cryptapi
+ - login: jina-ai
+ avatarUrl: https://avatars.githubusercontent.com/u/60539444?v=4
+ url: https://github.com/jina-ai
+ - login: DropbaseHQ
+ avatarUrl: https://avatars.githubusercontent.com/u/85367855?v=4
+ url: https://github.com/DropbaseHQ
+- - login: sushi2all
+ avatarUrl: https://avatars.githubusercontent.com/u/1043732?v=4
+ url: https://github.com/sushi2all
+ - login: chaserowbotham
+ avatarUrl: https://avatars.githubusercontent.com/u/97751084?v=4
+ url: https://github.com/chaserowbotham
+- - login: mikeckennedy
+ avatarUrl: https://avatars.githubusercontent.com/u/2035561?u=1bb18268bcd4d9249e1f783a063c27df9a84c05b&v=4
+ url: https://github.com/mikeckennedy
+ - login: Trivie
+ avatarUrl: https://avatars.githubusercontent.com/u/8161763?v=4
+ url: https://github.com/Trivie
+ - login: deta
+ avatarUrl: https://avatars.githubusercontent.com/u/47275976?v=4
+ url: https://github.com/deta
+ - login: deepset-ai
+ avatarUrl: https://avatars.githubusercontent.com/u/51827949?v=4
+ url: https://github.com/deepset-ai
+ - login: investsuite
+ avatarUrl: https://avatars.githubusercontent.com/u/73833632?v=4
+ url: https://github.com/investsuite
+ - login: VincentParedes
+ avatarUrl: https://avatars.githubusercontent.com/u/103889729?v=4
+ url: https://github.com/VincentParedes
+- - login: plocher
+ avatarUrl: https://avatars.githubusercontent.com/u/1082871?v=4
+ url: https://github.com/plocher
+- - login: InesIvanova
+ avatarUrl: https://avatars.githubusercontent.com/u/22920417?u=409882ec1df6dbd77455788bb383a8de223dbf6f&v=4
+ url: https://github.com/InesIvanova
+- - login: SendCloud
+ avatarUrl: https://avatars.githubusercontent.com/u/7831959?v=4
+ url: https://github.com/SendCloud
+ - login: qaas
+ avatarUrl: https://avatars.githubusercontent.com/u/8503759?u=10a6b4391ad6ab4cf9487ce54e3fcb61322d1efc&v=4
+ url: https://github.com/qaas
+ - login: xoflare
+ avatarUrl: https://avatars.githubusercontent.com/u/74335107?v=4
+ url: https://github.com/xoflare
+ - login: Striveworks
+ avatarUrl: https://avatars.githubusercontent.com/u/45523576?v=4
+ url: https://github.com/Striveworks
+ - login: BoostryJP
+ avatarUrl: https://avatars.githubusercontent.com/u/57932412?v=4
+ url: https://github.com/BoostryJP
+- - login: johnadjei
+ avatarUrl: https://avatars.githubusercontent.com/u/767860?v=4
+ url: https://github.com/johnadjei
+ - login: HiredScore
+ avatarUrl: https://avatars.githubusercontent.com/u/3908850?v=4
+ url: https://github.com/HiredScore
+ - login: wdwinslow
+ avatarUrl: https://avatars.githubusercontent.com/u/11562137?u=dc01daafb354135603a263729e3d26d939c0c452&v=4
+ url: https://github.com/wdwinslow
+- - login: moellenbeck
+ avatarUrl: https://avatars.githubusercontent.com/u/169372?v=4
+ url: https://github.com/moellenbeck
+ - login: RodneyU215
+ avatarUrl: https://avatars.githubusercontent.com/u/3329665?u=ec6a9adf8e7e8e306eed7d49687c398608d1604f&v=4
+ url: https://github.com/RodneyU215
+ - login: grillazz
+ avatarUrl: https://avatars.githubusercontent.com/u/3415861?u=0b32b7073ae1ab8b7f6d2db0188c2e1e357ff451&v=4
+ url: https://github.com/grillazz
+ - login: tizz98
+ avatarUrl: https://avatars.githubusercontent.com/u/5739698?u=f095a3659e3a8e7c69ccd822696990b521ea25f9&v=4
+ url: https://github.com/tizz98
+ - login: jmaralc
+ avatarUrl: https://avatars.githubusercontent.com/u/21101214?u=b15a9f07b7cbf6c9dcdbcb6550bbd2c52f55aa50&v=4
+ url: https://github.com/jmaralc
+ - login: marutoraman
+ avatarUrl: https://avatars.githubusercontent.com/u/33813153?u=2d0522bceba0b8b69adf1f2db866503bd96f944e&v=4
+ url: https://github.com/marutoraman
+ - login: mainframeindustries
+ avatarUrl: https://avatars.githubusercontent.com/u/55092103?v=4
+ url: https://github.com/mainframeindustries
+ - login: A-Edge
+ avatarUrl: https://avatars.githubusercontent.com/u/59514131?v=4
+ url: https://github.com/A-Edge
+- - login: Kludex
+ avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4
+ url: https://github.com/Kludex
+ - login: samuelcolvin
+ avatarUrl: https://avatars.githubusercontent.com/u/4039449?u=807390ba9cfe23906c3bf8a0d56aaca3cf2bfa0d&v=4
+ url: https://github.com/samuelcolvin
+ - login: jokull
+ avatarUrl: https://avatars.githubusercontent.com/u/701?u=0532b62166893d5160ef795c4c8b7512d971af05&v=4
+ url: https://github.com/jokull
+ - login: jefftriplett
+ avatarUrl: https://avatars.githubusercontent.com/u/50527?u=af1ddfd50f6afd6d99f333ba2ac8d0a5b245ea74&v=4
+ url: https://github.com/jefftriplett
+ - login: kamalgill
+ avatarUrl: https://avatars.githubusercontent.com/u/133923?u=0df9181d97436ce330e9acf90ab8a54b7022efe7&v=4
+ url: https://github.com/kamalgill
+ - login: jsutton
+ avatarUrl: https://avatars.githubusercontent.com/u/280777?v=4
+ url: https://github.com/jsutton
+ - login: deserat
+ avatarUrl: https://avatars.githubusercontent.com/u/299332?v=4
+ url: https://github.com/deserat
+ - login: ericof
+ avatarUrl: https://avatars.githubusercontent.com/u/306014?u=cf7c8733620397e6584a451505581c01c5d842d7&v=4
+ url: https://github.com/ericof
+ - login: wshayes
+ avatarUrl: https://avatars.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4
+ url: https://github.com/wshayes
+ - login: koxudaxi
+ avatarUrl: https://avatars.githubusercontent.com/u/630670?u=507d8577b4b3670546b449c4c2ccbc5af40d72f7&v=4
+ url: https://github.com/koxudaxi
+ - login: jqueguiner
+ avatarUrl: https://avatars.githubusercontent.com/u/690878?u=e4835b2a985a0f2d52018e4926cb5a58c26a62e8&v=4
+ url: https://github.com/jqueguiner
+ - login: ltieman
+ avatarUrl: https://avatars.githubusercontent.com/u/1084689?u=e69b17de17cb3ca141a17daa7ccbe173ceb1eb17&v=4
+ url: https://github.com/ltieman
+ - login: westonsteimel
+ avatarUrl: https://avatars.githubusercontent.com/u/1593939?u=0f2c0e3647f916fe295d62fa70da7a4c177115e3&v=4
+ url: https://github.com/westonsteimel
+ - login: corleyma
+ avatarUrl: https://avatars.githubusercontent.com/u/2080732?u=aed2ff652294a87d666b1c3f6dbe98104db76d26&v=4
+ url: https://github.com/corleyma
+ - login: madisonmay
+ avatarUrl: https://avatars.githubusercontent.com/u/2645393?u=f22b93c6ea345a4d26a90a3834dfc7f0789fcb63&v=4
+ url: https://github.com/madisonmay
+ - login: saivarunk
+ avatarUrl: https://avatars.githubusercontent.com/u/2976867?u=71f4385e781e9a9e871a52f2d4686f9a8d69ba2f&v=4
+ url: https://github.com/saivarunk
+ - login: andre1sk
+ avatarUrl: https://avatars.githubusercontent.com/u/3148093?v=4
+ url: https://github.com/andre1sk
+ - login: Shark009
+ avatarUrl: https://avatars.githubusercontent.com/u/3163309?u=0c6f4091b0eda05c44c390466199826e6dc6e431&v=4
+ url: https://github.com/Shark009
+ - login: dblackrun
+ avatarUrl: https://avatars.githubusercontent.com/u/3528486?v=4
+ url: https://github.com/dblackrun
+ - login: zsinx6
+ avatarUrl: https://avatars.githubusercontent.com/u/3532625?u=ba75a5dc744d1116ccfeaaf30d41cb2fe81fe8dd&v=4
+ url: https://github.com/zsinx6
+ - login: anomaly
+ avatarUrl: https://avatars.githubusercontent.com/u/3654837?v=4
+ url: https://github.com/anomaly
+ - login: peterHoburg
+ avatarUrl: https://avatars.githubusercontent.com/u/3860655?u=f55f47eb2d6a9b495e806ac5a044e3ae01ccc1fa&v=4
+ url: https://github.com/peterHoburg
+ - login: jaredtrog
+ avatarUrl: https://avatars.githubusercontent.com/u/4381365?v=4
+ url: https://github.com/jaredtrog
+ - login: oliverxchen
+ avatarUrl: https://avatars.githubusercontent.com/u/4471774?u=534191f25e32eeaadda22dfab4b0a428733d5489&v=4
+ url: https://github.com/oliverxchen
+ - login: CINOAdam
+ avatarUrl: https://avatars.githubusercontent.com/u/4728508?u=76ef23f06ae7c604e009873bc27cf0ea9ba738c9&v=4
+ url: https://github.com/CINOAdam
+ - login: ScrimForever
+ avatarUrl: https://avatars.githubusercontent.com/u/5040124?u=091ec38bfe16d6e762099e91309b59f248616a65&v=4
+ url: https://github.com/ScrimForever
+ - login: ennui93
+ avatarUrl: https://avatars.githubusercontent.com/u/5300907?u=5b5452725ddb391b2caaebf34e05aba873591c3a&v=4
+ url: https://github.com/ennui93
+ - login: MacroPower
+ avatarUrl: https://avatars.githubusercontent.com/u/5648814?u=e13991efd1e03c44c911f919872e750530ded633&v=4
+ url: https://github.com/MacroPower
+ - login: Yaleesa
+ avatarUrl: https://avatars.githubusercontent.com/u/6135475?v=4
+ url: https://github.com/Yaleesa
+ - login: iwpnd
+ avatarUrl: https://avatars.githubusercontent.com/u/6152183?u=b2286006daafff5f991557344fee20b5da59639a&v=4
+ url: https://github.com/iwpnd
+ - login: simw
+ avatarUrl: https://avatars.githubusercontent.com/u/6322526?v=4
+ url: https://github.com/simw
+ - login: pkucmus
+ avatarUrl: https://avatars.githubusercontent.com/u/6347418?u=98f5918b32e214a168a2f5d59b0b8ebdf57dca0d&v=4
+ url: https://github.com/pkucmus
+ - login: s3ich4n
+ avatarUrl: https://avatars.githubusercontent.com/u/6926298?u=ba3025d698e1c986655e776ae383a3d60d9d578e&v=4
+ url: https://github.com/s3ich4n
+ - login: Rehket
+ avatarUrl: https://avatars.githubusercontent.com/u/7015688?u=3afb0ba200feebbc7f958950e92db34df2a3c172&v=4
+ url: https://github.com/Rehket
+ - login: hiancdtrsnm
+ avatarUrl: https://avatars.githubusercontent.com/u/7343177?v=4
+ url: https://github.com/hiancdtrsnm
+ - login: Shackelford-Arden
+ avatarUrl: https://avatars.githubusercontent.com/u/7362263?v=4
+ url: https://github.com/Shackelford-Arden
+ - login: Vikka
+ avatarUrl: https://avatars.githubusercontent.com/u/9381120?u=4bfc7032a824d1ed1994aa8256dfa597c8f187ad&v=4
+ url: https://github.com/Vikka
+ - login: Ge0f3
+ avatarUrl: https://avatars.githubusercontent.com/u/11887760?u=ccd80f1ac36dcb8517ef5c4e702e8cc5a80cad2f&v=4
+ url: https://github.com/Ge0f3
+ - login: gokulyc
+ avatarUrl: https://avatars.githubusercontent.com/u/13468848?u=269f269d3e70407b5fb80138c52daba7af783997&v=4
+ url: https://github.com/gokulyc
+ - login: dannywade
+ avatarUrl: https://avatars.githubusercontent.com/u/13680237?u=418ee985bd41577b20fde81417fb2d901e875e8a&v=4
+ url: https://github.com/dannywade
+ - login: pablonnaoji
+ avatarUrl: https://avatars.githubusercontent.com/u/15187159?u=afc15bd5a4ba9c5c7206bbb1bcaeef606a0932e0&v=4
+ url: https://github.com/pablonnaoji
+ - login: robintully
+ avatarUrl: https://avatars.githubusercontent.com/u/17059673?u=862b9bb01513f5acd30df97433cb97a24dbfb772&v=4
+ url: https://github.com/robintully
+ - login: wedwardbeck
+ avatarUrl: https://avatars.githubusercontent.com/u/19333237?u=1de4ae2bf8d59eb4c013f21d863cbe0f2010575f&v=4
+ url: https://github.com/wedwardbeck
+ - login: linusg
+ avatarUrl: https://avatars.githubusercontent.com/u/19366641?u=125e390abef8fff3b3b0d370c369cba5d7fd4c67&v=4
+ url: https://github.com/linusg
+ - login: stradivari96
+ avatarUrl: https://avatars.githubusercontent.com/u/19752586?u=255f5f06a768f518b20cebd6963e840ac49294fd&v=4
+ url: https://github.com/stradivari96
+ - login: RedCarpetUp
+ avatarUrl: https://avatars.githubusercontent.com/u/20360440?v=4
+ url: https://github.com/RedCarpetUp
+ - login: Filimoa
+ avatarUrl: https://avatars.githubusercontent.com/u/21352040?u=75e02d102d2ee3e3d793e555fa5c63045913ccb0&v=4
+ url: https://github.com/Filimoa
+ - login: shuheng-liu
+ avatarUrl: https://avatars.githubusercontent.com/u/22414322?u=813c45f30786c6b511b21a661def025d8f7b609e&v=4
+ url: https://github.com/shuheng-liu
+ - login: cometa-haley
+ avatarUrl: https://avatars.githubusercontent.com/u/25950317?u=cec1a3e0643b785288ae8260cc295a85ab344995&v=4
+ url: https://github.com/cometa-haley
+ - login: LarryGF
+ avatarUrl: https://avatars.githubusercontent.com/u/26148349?u=431bb34d36d41c172466252242175281ae132152&v=4
+ url: https://github.com/LarryGF
+ - login: veprimk
+ avatarUrl: https://avatars.githubusercontent.com/u/29689749?u=f8cb5a15a286e522e5b189bc572d5a1a90217fb2&v=4
+ url: https://github.com/veprimk
+ - login: meysam81
+ avatarUrl: https://avatars.githubusercontent.com/u/30233243?u=64dc9fc62d039892c6fb44d804251cad5537132b&v=4
+ url: https://github.com/meysam81
+ - login: mauroalejandrojm
+ avatarUrl: https://avatars.githubusercontent.com/u/31569442?u=cdada990a1527926a36e95f62c30a8b48bbc49a1&v=4
+ url: https://github.com/mauroalejandrojm
+ - login: Leay15
+ avatarUrl: https://avatars.githubusercontent.com/u/32212558?u=c4aa9c1737e515959382a5515381757b1fd86c53&v=4
+ url: https://github.com/Leay15
+ - login: AlrasheedA
+ avatarUrl: https://avatars.githubusercontent.com/u/33544979?u=7fe66bf62b47682612b222e3e8f4795ef3be769b&v=4
+ url: https://github.com/AlrasheedA
+ - login: ProteinQure
+ avatarUrl: https://avatars.githubusercontent.com/u/33707203?v=4
+ url: https://github.com/ProteinQure
+ - login: guligon90
+ avatarUrl: https://avatars.githubusercontent.com/u/35070513?u=b48c05f669d1ea1d329f90dc70e45f10b569ef55&v=4
+ url: https://github.com/guligon90
+ - login: ybressler
+ avatarUrl: https://avatars.githubusercontent.com/u/40807730?u=6621dc9ab53b697912ab2a32211bb29ae90a9112&v=4
+ url: https://github.com/ybressler
+ - login: iamkarshe
+ avatarUrl: https://avatars.githubusercontent.com/u/43641892?u=d08c901b359c931784501740610d416558ff3e24&v=4
+ url: https://github.com/iamkarshe
+ - login: dbanty
+ avatarUrl: https://avatars.githubusercontent.com/u/43723790?u=9bcce836bbce55835291c5b2ac93a4e311f4b3c3&v=4
+ url: https://github.com/dbanty
+ - login: rafsaf
+ avatarUrl: https://avatars.githubusercontent.com/u/51059348?u=be9f06b8ced2d2b677297decc781fa8ce4f7ddbd&v=4
+ url: https://github.com/rafsaf
+ - login: dudikbender
+ avatarUrl: https://avatars.githubusercontent.com/u/53487583?u=494f85229115076121b3639a3806bbac1c6ae7f6&v=4
+ url: https://github.com/dudikbender
+ - login: daisuke8000
+ avatarUrl: https://avatars.githubusercontent.com/u/55035595?u=5025e379cd3655ae1a96039efc85223a873d2e38&v=4
+ url: https://github.com/daisuke8000
+ - login: yakkonaut
+ avatarUrl: https://avatars.githubusercontent.com/u/60633704?u=90a71fd631aa998ba4a96480788f017c9904e07b&v=4
+ url: https://github.com/yakkonaut
+ - login: primer-io
+ avatarUrl: https://avatars.githubusercontent.com/u/62146168?v=4
+ url: https://github.com/primer-io
+ - login: around
+ avatarUrl: https://avatars.githubusercontent.com/u/62425723?v=4
+ url: https://github.com/around
+ - login: predictionmachine
+ avatarUrl: https://avatars.githubusercontent.com/u/63719559?v=4
+ url: https://github.com/predictionmachine
+ - login: daverin
+ avatarUrl: https://avatars.githubusercontent.com/u/70378377?u=6d1814195c0de7162820eaad95a25b423a3869c0&v=4
+ url: https://github.com/daverin
+ - login: anthonycepeda
+ avatarUrl: https://avatars.githubusercontent.com/u/72019805?u=892f700c79f9732211bd5221bf16eec32356a732&v=4
+ url: https://github.com/anthonycepeda
+ - login: NinaHwang
+ avatarUrl: https://avatars.githubusercontent.com/u/79563565?u=1741703bd6c8f491503354b363a86e879b4c1cab&v=4
+ url: https://github.com/NinaHwang
+ - login: dotlas
+ avatarUrl: https://avatars.githubusercontent.com/u/88832003?v=4
+ url: https://github.com/dotlas
+ - login: pyt3h
+ avatarUrl: https://avatars.githubusercontent.com/u/99658549?v=4
+ url: https://github.com/pyt3h
+- - login: linux-china
+ avatarUrl: https://avatars.githubusercontent.com/u/46711?v=4
+ url: https://github.com/linux-china
+ - login: ddanier
+ avatarUrl: https://avatars.githubusercontent.com/u/113563?v=4
+ url: https://github.com/ddanier
+ - login: jhb
+ avatarUrl: https://avatars.githubusercontent.com/u/142217?v=4
+ url: https://github.com/jhb
+ - login: justinrmiller
+ avatarUrl: https://avatars.githubusercontent.com/u/143998?u=b507a940394d4fc2bc1c27cea2ca9c22538874bd&v=4
+ url: https://github.com/justinrmiller
+ - login: bryanculbertson
+ avatarUrl: https://avatars.githubusercontent.com/u/144028?u=defda4f90e93429221cc667500944abde60ebe4a&v=4
+ url: https://github.com/bryanculbertson
+ - login: yourkin
+ avatarUrl: https://avatars.githubusercontent.com/u/178984?u=fa7c3503b47bf16405b96d21554bc59f07a65523&v=4
+ url: https://github.com/yourkin
+ - login: slafs
+ avatarUrl: https://avatars.githubusercontent.com/u/210173?v=4
+ url: https://github.com/slafs
+ - login: assem-ch
+ avatarUrl: https://avatars.githubusercontent.com/u/315228?u=e0c5ab30726d3243a40974bb9bae327866e42d9b&v=4
+ url: https://github.com/assem-ch
+ - login: adamghill
+ avatarUrl: https://avatars.githubusercontent.com/u/317045?u=f1349d5ffe84a19f324e204777859fbf69ddf633&v=4
+ url: https://github.com/adamghill
+ - login: eteq
+ avatarUrl: https://avatars.githubusercontent.com/u/346587?v=4
+ url: https://github.com/eteq
+ - login: dmig
+ avatarUrl: https://avatars.githubusercontent.com/u/388564?v=4
+ url: https://github.com/dmig
+ - login: rinckd
+ avatarUrl: https://avatars.githubusercontent.com/u/546002?u=1fcc7e664dc86524a0af6837a0c222829c3fd4e5&v=4
+ url: https://github.com/rinckd
+ - login: securancy
+ avatarUrl: https://avatars.githubusercontent.com/u/606673?v=4
+ url: https://github.com/securancy
+ - login: falkben
+ avatarUrl: https://avatars.githubusercontent.com/u/653031?u=0c8d8f33d87f1aa1a6488d3f02105e9abc838105&v=4
+ url: https://github.com/falkben
+ - login: hardbyte
+ avatarUrl: https://avatars.githubusercontent.com/u/855189?u=aa29e92f34708814d6b67fcd47ca4cf2ce1c04ed&v=4
+ url: https://github.com/hardbyte
+ - login: janfilips
+ avatarUrl: https://avatars.githubusercontent.com/u/870699?u=6034d81731ecb41ae5c717e56a901ed46fc039a8&v=4
+ url: https://github.com/janfilips
+ - login: scari
+ avatarUrl: https://avatars.githubusercontent.com/u/964251?v=4
+ url: https://github.com/scari
+ - login: Pytlicek
+ avatarUrl: https://avatars.githubusercontent.com/u/1430522?u=169dba3bfbc04ed214a914640ff435969f19ddb3&v=4
+ url: https://github.com/Pytlicek
+ - login: Celeborn2BeAlive
+ avatarUrl: https://avatars.githubusercontent.com/u/1659465?u=944517e4db0f6df65070074e81cabdad9c8a434b&v=4
+ url: https://github.com/Celeborn2BeAlive
+ - login: WillHogan
+ avatarUrl: https://avatars.githubusercontent.com/u/1661551?u=7036c064cf29781470573865264ec8e60b6b809f&v=4
+ url: https://github.com/WillHogan
+ - login: cbonoz
+ avatarUrl: https://avatars.githubusercontent.com/u/2351087?u=fd3e8030b2cc9fbfbb54a65e9890c548a016f58b&v=4
+ url: https://github.com/cbonoz
+ - login: Abbe98
+ avatarUrl: https://avatars.githubusercontent.com/u/2631719?u=8a064aba9a710229ad28c616549d81a24191a5df&v=4
+ url: https://github.com/Abbe98
+ - login: rglsk
+ avatarUrl: https://avatars.githubusercontent.com/u/2768101?u=e349c88673f2155fe021331377c656a9d74bcc25&v=4
+ url: https://github.com/rglsk
+ - login: paul121
+ avatarUrl: https://avatars.githubusercontent.com/u/3116995?u=6e2d8691cc345e63ee02e4eb4d7cef82b1fcbedc&v=4
+ url: https://github.com/paul121
+ - login: igorcorrea
+ avatarUrl: https://avatars.githubusercontent.com/u/3438238?u=c57605077c31a8f7b2341fc4912507f91b4a5621&v=4
+ url: https://github.com/igorcorrea
+ - login: anthonycorletti
+ avatarUrl: https://avatars.githubusercontent.com/u/3477132?v=4
+ url: https://github.com/anthonycorletti
+ - login: pawamoy
+ avatarUrl: https://avatars.githubusercontent.com/u/3999221?u=b030e4c89df2f3a36bc4710b925bdeb6745c9856&v=4
+ url: https://github.com/pawamoy
+ - login: Alisa-lisa
+ avatarUrl: https://avatars.githubusercontent.com/u/4137964?u=e7e393504f554f4ff15863a1e01a5746863ef9ce&v=4
+ url: https://github.com/Alisa-lisa
+ - login: unredundant
+ avatarUrl: https://avatars.githubusercontent.com/u/5607577?u=57dd0023365bec03f4fc566df6b81bc0a264a47d&v=4
+ url: https://github.com/unredundant
+ - login: holec
+ avatarUrl: https://avatars.githubusercontent.com/u/6438041?u=f5af71ec85b3a9d7b8139cb5af0512b02fa9ab1e&v=4
+ url: https://github.com/holec
+ - login: moonape1226
+ avatarUrl: https://avatars.githubusercontent.com/u/8532038?u=d9f8b855a429fff9397c3833c2ff83849ebf989d&v=4
+ url: https://github.com/moonape1226
+ - login: davanstrien
+ avatarUrl: https://avatars.githubusercontent.com/u/8995957?u=fb2aad2b52bb4e7b56db6d7c8ecc9ae1eac1b984&v=4
+ url: https://github.com/davanstrien
+ - login: yenchenLiu
+ avatarUrl: https://avatars.githubusercontent.com/u/9199638?u=8cdf5ae507448430d90f6f3518d1665a23afe99b&v=4
+ url: https://github.com/yenchenLiu
+ - login: VivianSolide
+ avatarUrl: https://avatars.githubusercontent.com/u/9358572?u=4a38ef72dd39e8b262bd5ab819992128b55c52b4&v=4
+ url: https://github.com/VivianSolide
+ - login: xncbf
+ avatarUrl: https://avatars.githubusercontent.com/u/9462045?u=866a1311e4bd3ec5ae84185c4fcc99f397c883d7&v=4
+ url: https://github.com/xncbf
+ - login: DMantis
+ avatarUrl: https://avatars.githubusercontent.com/u/9536869?v=4
+ url: https://github.com/DMantis
+ - login: hard-coders
+ avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=95db33927bbff1ed1c07efddeb97ac2ff33068ed&v=4
+ url: https://github.com/hard-coders
+ - login: satwikkansal
+ avatarUrl: https://avatars.githubusercontent.com/u/10217535?u=b12d6ef74ea297de9e46da6933b1a5b7ba9e6a61&v=4
+ url: https://github.com/satwikkansal
+ - login: pheanex
+ avatarUrl: https://avatars.githubusercontent.com/u/10408624?u=5b6bab6ee174aa6e991333e06eb29f628741013d&v=4
+ url: https://github.com/pheanex
+ - login: JimFawkes
+ avatarUrl: https://avatars.githubusercontent.com/u/12075115?u=dc58ecfd064d72887c34bf500ddfd52592509acd&v=4
+ url: https://github.com/JimFawkes
+ - login: logan-connolly
+ avatarUrl: https://avatars.githubusercontent.com/u/16244943?u=8ae66dfbba936463cc8aa0dd7a6d2b4c0cc757eb&v=4
+ url: https://github.com/logan-connolly
+ - login: cdsre
+ avatarUrl: https://avatars.githubusercontent.com/u/16945936?v=4
+ url: https://github.com/cdsre
+ - login: jangia
+ avatarUrl: https://avatars.githubusercontent.com/u/17927101?u=9261b9bb0c3e3bb1ecba43e8915dc58d8c9a077e&v=4
+ url: https://github.com/jangia
+ - login: ghandic
+ avatarUrl: https://avatars.githubusercontent.com/u/23500353?u=e2e1d736f924d9be81e8bfc565b6d8836ba99773&v=4
+ url: https://github.com/ghandic
+ - login: fstau
+ avatarUrl: https://avatars.githubusercontent.com/u/24669867?u=60e7c8c09f8dafabee8fc3edcd6f9e19abbff918&v=4
+ url: https://github.com/fstau
+ - login: mertguvencli
+ avatarUrl: https://avatars.githubusercontent.com/u/29762151?u=16a906d90df96c8cff9ea131a575c4bc171b1523&v=4
+ url: https://github.com/mertguvencli
+ - login: dwreeves
+ avatarUrl: https://avatars.githubusercontent.com/u/31971762?u=69732aba05aa5cf0780866349ebe109cf632b047&v=4
+ url: https://github.com/dwreeves
+ - login: kitaramu0401
+ avatarUrl: https://avatars.githubusercontent.com/u/33246506?u=929e6efa2c518033b8097ba524eb5347a069bb3b&v=4
+ url: https://github.com/kitaramu0401
+ - login: engineerjoe440
+ avatarUrl: https://avatars.githubusercontent.com/u/33275230?u=eb223cad27017bb1e936ee9b429b450d092d0236&v=4
+ url: https://github.com/engineerjoe440
+ - login: declon
+ avatarUrl: https://avatars.githubusercontent.com/u/36180226?v=4
+ url: https://github.com/declon
+ - login: d-e-h-i-o
+ avatarUrl: https://avatars.githubusercontent.com/u/36816716?v=4
+ url: https://github.com/d-e-h-i-o
+ - login: ilias-ant
+ avatarUrl: https://avatars.githubusercontent.com/u/42189572?u=a2d6121bac4d125d92ec207460fa3f1842d37e66&v=4
+ url: https://github.com/ilias-ant
+ - login: arrrrrmin
+ avatarUrl: https://avatars.githubusercontent.com/u/43553423?u=fee5739394fea074cb0b66929d070114a5067aae&v=4
+ url: https://github.com/arrrrrmin
+ - login: Nephilim-Jack
+ avatarUrl: https://avatars.githubusercontent.com/u/48372168?u=6f2bb405238d7efc467536fe01f58df6779c58a9&v=4
+ url: https://github.com/Nephilim-Jack
+ - login: akanz1
+ avatarUrl: https://avatars.githubusercontent.com/u/51492342?u=2280f57134118714645e16b535c1a37adf6b369b&v=4
+ url: https://github.com/akanz1
+ - login: rooflexx
+ avatarUrl: https://avatars.githubusercontent.com/u/58993673?u=f8ba450460f1aea18430ed1e4a3889049a3b4dfa&v=4
+ url: https://github.com/rooflexx
+ - login: denisyao1
+ avatarUrl: https://avatars.githubusercontent.com/u/60019356?v=4
+ url: https://github.com/denisyao1
+ - login: apar-tiwari
+ avatarUrl: https://avatars.githubusercontent.com/u/61064197?v=4
+ url: https://github.com/apar-tiwari
+ - login: 0417taehyun
+ avatarUrl: https://avatars.githubusercontent.com/u/63915557?u=47debaa860fd52c9b98c97ef357ddcec3b3fb399&v=4
+ url: https://github.com/0417taehyun
+ - login: alessio-proietti
+ avatarUrl: https://avatars.githubusercontent.com/u/67370599?u=8ac73db1e18e946a7681f173abdb640516f88515&v=4
+ url: https://github.com/alessio-proietti
+- - login: backbord
+ avatarUrl: https://avatars.githubusercontent.com/u/6814946?v=4
+ url: https://github.com/backbord
+ - login: sadikkuzu
+ avatarUrl: https://avatars.githubusercontent.com/u/23168063?u=765ed469c44c004560079210ccdad5b29938eaa9&v=4
+ url: https://github.com/sadikkuzu
+ - login: gabrielmbmb
+ avatarUrl: https://avatars.githubusercontent.com/u/29572918?u=6d1e00b5d558e96718312ff910a2318f47cc3145&v=4
+ url: https://github.com/gabrielmbmb
+ - login: danburonline
+ avatarUrl: https://avatars.githubusercontent.com/u/34251194?u=2cad4388c1544e539ecb732d656e42fb07b4ff2d&v=4
+ url: https://github.com/danburonline
diff --git a/docs/en/data/people.yml b/docs/en/data/people.yml
index 6356a507..92aab109 100644
--- a/docs/en/data/people.yml
+++ b/docs/en/data/people.yml
@@ -1,24 +1,24 @@
maintainers:
- login: tiangolo
- answers: 1225
- prs: 232
- avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=05f95ca7fdead36edd9c86be46b4ef6c3c71f876&v=4
+ answers: 1243
+ prs: 300
+ avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=5cad72c846b7aba2e960546af490edc7375dafc4&v=4
url: https://github.com/tiangolo
experts:
- login: Kludex
- count: 267
- avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=cf8455cb899806b774a3a71073f88583adec99f6&v=4
+ count: 335
+ avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4
url: https://github.com/Kludex
- login: dmontagu
count: 262
avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=58ed2a45798a4339700e2f62b2e12e6e54bf0396&v=4
url: https://github.com/dmontagu
- login: ycd
- count: 216
+ count: 221
avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=826f228edf0bab0d19ad1d5c4ba4df1047ccffef&v=4
url: https://github.com/ycd
- login: Mause
- count: 182
+ count: 207
avatarUrl: https://avatars.githubusercontent.com/u/1405026?v=4
url: https://github.com/Mause
- login: euri10
@@ -29,70 +29,90 @@ experts:
count: 130
avatarUrl: https://avatars.githubusercontent.com/u/331403?v=4
url: https://github.com/phy25
-- login: ArcLightSlavik
- count: 64
- avatarUrl: https://avatars.githubusercontent.com/u/31127044?u=81a84af39c89b898b0fbc5a04e8834f60f23e55a&v=4
- url: https://github.com/ArcLightSlavik
-- login: falkben
- count: 56
- avatarUrl: https://avatars.githubusercontent.com/u/653031?u=0c8d8f33d87f1aa1a6488d3f02105e9abc838105&v=4
- url: https://github.com/falkben
- login: raphaelauv
- count: 47
+ count: 71
avatarUrl: https://avatars.githubusercontent.com/u/10202690?u=e6f86f5c0c3026a15d6b51792fa3e532b12f1371&v=4
url: https://github.com/raphaelauv
+- login: ArcLightSlavik
+ count: 71
+ avatarUrl: https://avatars.githubusercontent.com/u/31127044?u=b0f2c37142f4b762e41ad65dc49581813422bd71&v=4
+ url: https://github.com/ArcLightSlavik
+- login: falkben
+ count: 58
+ avatarUrl: https://avatars.githubusercontent.com/u/653031?u=0c8d8f33d87f1aa1a6488d3f02105e9abc838105&v=4
+ url: https://github.com/falkben
- login: sm-Fifteen
- count: 46
+ count: 48
avatarUrl: https://avatars.githubusercontent.com/u/516999?u=437c0c5038558c67e887ccd863c1ba0f846c03da&v=4
url: https://github.com/sm-Fifteen
+- login: insomnes
+ count: 46
+ avatarUrl: https://avatars.githubusercontent.com/u/16958893?u=f8be7088d5076d963984a21f95f44e559192d912&v=4
+ url: https://github.com/insomnes
+- login: Dustyposa
+ count: 43
+ avatarUrl: https://avatars.githubusercontent.com/u/27180793?u=5cf2877f50b3eb2bc55086089a78a36f07042889&v=4
+ url: https://github.com/Dustyposa
- login: includeamin
- count: 38
+ count: 39
avatarUrl: https://avatars.githubusercontent.com/u/11836741?u=8bd5ef7e62fe6a82055e33c4c0e0a7879ff8cfb6&v=4
url: https://github.com/includeamin
+- login: jgould22
+ count: 38
+ avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4
+ url: https://github.com/jgould22
+- login: STeveShary
+ count: 37
+ avatarUrl: https://avatars.githubusercontent.com/u/5167622?u=de8f597c81d6336fcebc37b32dfd61a3f877160c&v=4
+ url: https://github.com/STeveShary
+- login: adriangb
+ count: 36
+ avatarUrl: https://avatars.githubusercontent.com/u/1755071?u=81f0262df34e1460ca546fbd0c211169c2478532&v=4
+ url: https://github.com/adriangb
- login: prostomarkeloff
count: 33
avatarUrl: https://avatars.githubusercontent.com/u/28061158?u=72309cc1f2e04e40fa38b29969cb4e9d3f722e7b&v=4
url: https://github.com/prostomarkeloff
-- login: Dustyposa
- count: 32
- avatarUrl: https://avatars.githubusercontent.com/u/27180793?u=5cf2877f50b3eb2bc55086089a78a36f07042889&v=4
- url: https://github.com/Dustyposa
+- login: frankie567
+ count: 31
+ avatarUrl: https://avatars.githubusercontent.com/u/1144727?u=85c025e3fcc7bd79a5665c63ee87cdf8aae13374&v=4
+ url: https://github.com/frankie567
- login: krishnardt
- count: 30
+ count: 31
avatarUrl: https://avatars.githubusercontent.com/u/31960541?u=47f4829c77f4962ab437ffb7995951e41eeebe9b&v=4
url: https://github.com/krishnardt
-- login: insomnes
- count: 30
- avatarUrl: https://avatars.githubusercontent.com/u/16958893?u=f8be7088d5076d963984a21f95f44e559192d912&v=4
- url: https://github.com/insomnes
- login: wshayes
count: 29
avatarUrl: https://avatars.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4
url: https://github.com/wshayes
+- login: chbndrhnns
+ count: 28
+ avatarUrl: https://avatars.githubusercontent.com/u/7534547?v=4
+ url: https://github.com/chbndrhnns
+- login: panla
+ count: 26
+ avatarUrl: https://avatars.githubusercontent.com/u/41326348?u=ba2fda6b30110411ecbf406d187907e2b420ac19&v=4
+ url: https://github.com/panla
+- login: ghandic
+ count: 25
+ avatarUrl: https://avatars.githubusercontent.com/u/23500353?u=e2e1d736f924d9be81e8bfc565b6d8836ba99773&v=4
+ url: https://github.com/ghandic
- login: dbanty
count: 25
- avatarUrl: https://avatars.githubusercontent.com/u/43723790?u=0cf33e4f40efc2ea206a1189fd63a11344eb88ed&v=4
+ avatarUrl: https://avatars.githubusercontent.com/u/43723790?u=9bcce836bbce55835291c5b2ac93a4e311f4b3c3&v=4
url: https://github.com/dbanty
- login: SirTelemak
count: 24
avatarUrl: https://avatars.githubusercontent.com/u/9435877?u=719327b7d2c4c62212456d771bfa7c6b8dbb9eac&v=4
url: https://github.com/SirTelemak
-- login: chbndrhnns
- count: 22
- avatarUrl: https://avatars.githubusercontent.com/u/7534547?v=4
- url: https://github.com/chbndrhnns
- login: acnebs
count: 22
- avatarUrl: https://avatars.githubusercontent.com/u/9054108?u=bfd127b3e6200f4d00afd714f0fc95c2512df19b&v=4
+ avatarUrl: https://avatars.githubusercontent.com/u/9054108?u=c27e50269f1ef8ea950cc6f0268c8ec5cebbe9c9&v=4
url: https://github.com/acnebs
- login: nsidnev
count: 22
avatarUrl: https://avatars.githubusercontent.com/u/22559461?u=a9cc3238217e21dc8796a1a500f01b722adb082c&v=4
url: https://github.com/nsidnev
-- login: frankie567
- count: 21
- avatarUrl: https://avatars.githubusercontent.com/u/1144727?u=72adf1cb1d29787305c99700d669561952cea0af&v=4
- url: https://github.com/frankie567
- login: chris-allnutt
count: 21
avatarUrl: https://avatars.githubusercontent.com/u/565544?v=4
@@ -101,6 +121,10 @@ experts:
count: 19
avatarUrl: https://avatars.githubusercontent.com/u/24581770?v=4
url: https://github.com/retnikt
+- login: acidjunk
+ count: 18
+ avatarUrl: https://avatars.githubusercontent.com/u/685002?u=b5094ab4527fc84b006c0ac9ff54367bdebb2267&v=4
+ url: https://github.com/acidjunk
- login: Hultner
count: 18
avatarUrl: https://avatars.githubusercontent.com/u/2669034?u=115e53df959309898ad8dc9443fbb35fee71df07&v=4
@@ -113,18 +137,50 @@ experts:
count: 17
avatarUrl: https://avatars.githubusercontent.com/u/28262306?u=66ee21316275ef356081c2efc4ed7a4572e690dc&v=4
url: https://github.com/nkhitrov
+- login: harunyasar
+ count: 17
+ avatarUrl: https://avatars.githubusercontent.com/u/1765494?u=5b1ab7c582db4b4016fa31affe977d10af108ad4&v=4
+ url: https://github.com/harunyasar
+- login: rafsaf
+ count: 17
+ avatarUrl: https://avatars.githubusercontent.com/u/51059348?u=be9f06b8ced2d2b677297decc781fa8ce4f7ddbd&v=4
+ url: https://github.com/rafsaf
- login: waynerv
- count: 15
+ count: 16
avatarUrl: https://avatars.githubusercontent.com/u/39515546?u=ec35139777597cdbbbddda29bf8b9d4396b429a9&v=4
url: https://github.com/waynerv
+- login: dstlny
+ count: 16
+ avatarUrl: https://avatars.githubusercontent.com/u/41964673?u=9f2174f9d61c15c6e3a4c9e3aeee66f711ce311f&v=4
+ url: https://github.com/dstlny
- login: haizaar
count: 13
avatarUrl: https://avatars.githubusercontent.com/u/58201?u=4f1f9843d69433ca0d380d95146cfe119e5fdac4&v=4
url: https://github.com/haizaar
-- login: acidjunk
+- login: valentin994
+ count: 13
+ avatarUrl: https://avatars.githubusercontent.com/u/42819267?u=fdeeaa9242a59b243f8603496b00994f6951d5a2&v=4
+ url: https://github.com/valentin994
+- login: hellocoldworld
+ count: 12
+ avatarUrl: https://avatars.githubusercontent.com/u/47581948?v=4
+ url: https://github.com/hellocoldworld
+- login: David-Lor
+ count: 12
+ avatarUrl: https://avatars.githubusercontent.com/u/17401854?u=474680c02b94cba810cb9032fb7eb787d9cc9d22&v=4
+ url: https://github.com/David-Lor
+- login: yinziyan1206
+ count: 12
+ avatarUrl: https://avatars.githubusercontent.com/u/37829370?v=4
+ url: https://github.com/yinziyan1206
+- login: jonatasoli
+ count: 12
+ avatarUrl: https://avatars.githubusercontent.com/u/26334101?u=071c062d2861d3dd127f6b4a5258cd8ef55d4c50&v=4
+ url: https://github.com/jonatasoli
+- login: lowercase00
count: 11
- avatarUrl: https://avatars.githubusercontent.com/u/685002?u=b5094ab4527fc84b006c0ac9ff54367bdebb2267&v=4
- url: https://github.com/acidjunk
+ avatarUrl: https://avatars.githubusercontent.com/u/21188280?v=4
+ url: https://github.com/lowercase00
- login: zamiramir
count: 11
avatarUrl: https://avatars.githubusercontent.com/u/40475662?u=e58ef61034e8d0d6a312cc956fb09b9c3332b449&v=4
@@ -133,51 +189,35 @@ experts:
count: 11
avatarUrl: https://avatars.githubusercontent.com/u/8134632?v=4
url: https://github.com/juntatalor
-- login: valentin994
+- login: n8sty
count: 11
- avatarUrl: https://avatars.githubusercontent.com/u/42819267?u=fdeeaa9242a59b243f8603496b00994f6951d5a2&v=4
- url: https://github.com/valentin994
+ avatarUrl: https://avatars.githubusercontent.com/u/2964996?v=4
+ url: https://github.com/n8sty
- login: aalifadv
count: 11
avatarUrl: https://avatars.githubusercontent.com/u/78442260?v=4
url: https://github.com/aalifadv
-- login: stefanondisponibile
- count: 10
- avatarUrl: https://avatars.githubusercontent.com/u/20441825?u=ee1e59446b98f8ec2363caeda4c17164d0d9cc7d&v=4
- url: https://github.com/stefanondisponibile
last_month_active:
-- login: Kludex
- count: 12
- avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=cf8455cb899806b774a3a71073f88583adec99f6&v=4
- url: https://github.com/Kludex
-- login: frankie567
- count: 9
- avatarUrl: https://avatars.githubusercontent.com/u/1144727?u=72adf1cb1d29787305c99700d669561952cea0af&v=4
- url: https://github.com/frankie567
-- login: Mause
- count: 6
- avatarUrl: https://avatars.githubusercontent.com/u/1405026?v=4
- url: https://github.com/Mause
-- login: ArcLightSlavik
- count: 6
- avatarUrl: https://avatars.githubusercontent.com/u/31127044?u=81a84af39c89b898b0fbc5a04e8834f60f23e55a&v=4
- url: https://github.com/ArcLightSlavik
-- login: gyKa
+- login: jgould22
+ count: 15
+ avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4
+ url: https://github.com/jgould22
+- login: accelleon
+ count: 5
+ avatarUrl: https://avatars.githubusercontent.com/u/5001614?v=4
+ url: https://github.com/accelleon
+- login: jonatasoli
count: 4
- avatarUrl: https://avatars.githubusercontent.com/u/1000842?v=4
- url: https://github.com/gyKa
-- login: ricky-sb
+ avatarUrl: https://avatars.githubusercontent.com/u/26334101?u=071c062d2861d3dd127f6b4a5258cd8ef55d4c50&v=4
+ url: https://github.com/jonatasoli
+- login: Kludex
+ count: 4
+ avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4
+ url: https://github.com/Kludex
+- login: yinziyan1206
count: 3
- avatarUrl: https://avatars.githubusercontent.com/u/10700079?v=4
- url: https://github.com/ricky-sb
-- login: captainCapitalism
- count: 3
- avatarUrl: https://avatars.githubusercontent.com/u/32553875?v=4
- url: https://github.com/captainCapitalism
-- login: acidjunk
- count: 3
- avatarUrl: https://avatars.githubusercontent.com/u/685002?u=b5094ab4527fc84b006c0ac9ff54367bdebb2267&v=4
- url: https://github.com/acidjunk
+ avatarUrl: https://avatars.githubusercontent.com/u/37829370?v=4
+ url: https://github.com/yinziyan1206
top_contributors:
- login: waynerv
count: 25
@@ -191,25 +231,37 @@ top_contributors:
count: 16
avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=58ed2a45798a4339700e2f62b2e12e6e54bf0396&v=4
url: https://github.com/dmontagu
+- login: jaystone776
+ count: 15
+ avatarUrl: https://avatars.githubusercontent.com/u/11191137?u=299205a95e9b6817a43144a48b643346a5aac5cc&v=4
+ url: https://github.com/jaystone776
- login: euri10
count: 13
avatarUrl: https://avatars.githubusercontent.com/u/1104190?u=321a2e953e6645a7d09b732786c7a8061e0f8a8b&v=4
url: https://github.com/euri10
- login: mariacamilagl
- count: 9
+ count: 12
avatarUrl: https://avatars.githubusercontent.com/u/11489395?u=4adb6986bf3debfc2b8216ae701f2bd47d73da7d&v=4
url: https://github.com/mariacamilagl
+- login: Smlep
+ count: 9
+ avatarUrl: https://avatars.githubusercontent.com/u/16785985?v=4
+ url: https://github.com/Smlep
+- login: Serrones
+ count: 8
+ avatarUrl: https://avatars.githubusercontent.com/u/22691749?u=4795b880e13ca33a73e52fc0ef7dc9c60c8fce47&v=4
+ url: https://github.com/Serrones
+- login: Kludex
+ count: 8
+ avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4
+ url: https://github.com/Kludex
- login: RunningIkkyu
count: 7
avatarUrl: https://avatars.githubusercontent.com/u/31848542?u=706e1ee3f248245f2d68b976d149d06fd5a2010d&v=4
url: https://github.com/RunningIkkyu
-- login: Serrones
- count: 7
- avatarUrl: https://avatars.githubusercontent.com/u/22691749?u=4795b880e13ca33a73e52fc0ef7dc9c60c8fce47&v=4
- url: https://github.com/Serrones
- login: hard-coders
- count: 6
- avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=f2d3d2038c55d86d7f9348f4e6c5e30191e4ee8b&v=4
+ count: 7
+ avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=95db33927bbff1ed1c07efddeb97ac2ff33068ed&v=4
url: https://github.com/hard-coders
- login: wshayes
count: 5
@@ -225,65 +277,109 @@ top_contributors:
url: https://github.com/Attsun1031
- login: jekirl
count: 4
- avatarUrl: https://avatars.githubusercontent.com/u/2546697?v=4
+ avatarUrl: https://avatars.githubusercontent.com/u/2546697?u=a027452387d85bd4a14834e19d716c99255fb3b7&v=4
url: https://github.com/jekirl
-- login: komtaki
+- login: jfunez
count: 4
- avatarUrl: https://avatars.githubusercontent.com/u/39375566?u=5a44657c0544111ee3c132d9bb9951c2804f7969&v=4
- url: https://github.com/komtaki
-- login: Smlep
- count: 4
- avatarUrl: https://avatars.githubusercontent.com/u/16785985?v=4
- url: https://github.com/Smlep
-top_reviewers:
-- login: Kludex
- count: 80
- avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=cf8455cb899806b774a3a71073f88583adec99f6&v=4
- url: https://github.com/Kludex
-- login: tokusumi
- count: 44
- avatarUrl: https://avatars.githubusercontent.com/u/41147016?u=55010621aece725aa702270b54fed829b6a1fe60&v=4
- url: https://github.com/tokusumi
-- login: Laineyzhang55
- count: 42
- avatarUrl: https://avatars.githubusercontent.com/u/59285379?v=4
- url: https://github.com/Laineyzhang55
+ avatarUrl: https://avatars.githubusercontent.com/u/805749?v=4
+ url: https://github.com/jfunez
- login: ycd
- count: 41
+ count: 4
avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=826f228edf0bab0d19ad1d5c4ba4df1047ccffef&v=4
url: https://github.com/ycd
+- login: komtaki
+ count: 4
+ avatarUrl: https://avatars.githubusercontent.com/u/39375566?u=260ad6b1a4b34c07dbfa728da5e586f16f6d1824&v=4
+ url: https://github.com/komtaki
+- login: NinaHwang
+ count: 4
+ avatarUrl: https://avatars.githubusercontent.com/u/79563565?u=1741703bd6c8f491503354b363a86e879b4c1cab&v=4
+ url: https://github.com/NinaHwang
+top_reviewers:
+- login: Kludex
+ count: 93
+ avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4
+ url: https://github.com/Kludex
- login: waynerv
- count: 38
+ count: 47
avatarUrl: https://avatars.githubusercontent.com/u/39515546?u=ec35139777597cdbbbddda29bf8b9d4396b429a9&v=4
url: https://github.com/waynerv
+- login: Laineyzhang55
+ count: 47
+ avatarUrl: https://avatars.githubusercontent.com/u/59285379?v=4
+ url: https://github.com/Laineyzhang55
+- login: tokusumi
+ count: 46
+ avatarUrl: https://avatars.githubusercontent.com/u/41147016?u=55010621aece725aa702270b54fed829b6a1fe60&v=4
+ url: https://github.com/tokusumi
+- login: ycd
+ count: 45
+ avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=826f228edf0bab0d19ad1d5c4ba4df1047ccffef&v=4
+ url: https://github.com/ycd
+- login: cikay
+ count: 41
+ avatarUrl: https://avatars.githubusercontent.com/u/24587499?u=e772190a051ab0eaa9c8542fcff1892471638f2b&v=4
+ url: https://github.com/cikay
+- login: BilalAlpaslan
+ count: 40
+ avatarUrl: https://avatars.githubusercontent.com/u/47563997?u=63ed66e304fe8d765762c70587d61d9196e5c82d&v=4
+ url: https://github.com/BilalAlpaslan
- login: AdrianDeAnda
- count: 28
+ count: 33
avatarUrl: https://avatars.githubusercontent.com/u/1024932?u=bb7f8a0d6c9de4e9d0320a9f271210206e202250&v=4
url: https://github.com/AdrianDeAnda
+- login: ArcLightSlavik
+ count: 31
+ avatarUrl: https://avatars.githubusercontent.com/u/31127044?u=b0f2c37142f4b762e41ad65dc49581813422bd71&v=4
+ url: https://github.com/ArcLightSlavik
+- login: cassiobotaro
+ count: 25
+ avatarUrl: https://avatars.githubusercontent.com/u/3127847?u=b0a652331da17efeb85cd6e3a4969182e5004804&v=4
+ url: https://github.com/cassiobotaro
- login: dmontagu
count: 23
avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=58ed2a45798a4339700e2f62b2e12e6e54bf0396&v=4
url: https://github.com/dmontagu
- login: komtaki
count: 21
- avatarUrl: https://avatars.githubusercontent.com/u/39375566?u=5a44657c0544111ee3c132d9bb9951c2804f7969&v=4
+ avatarUrl: https://avatars.githubusercontent.com/u/39375566?u=260ad6b1a4b34c07dbfa728da5e586f16f6d1824&v=4
url: https://github.com/komtaki
-- login: ArcLightSlavik
- count: 20
- avatarUrl: https://avatars.githubusercontent.com/u/31127044?u=81a84af39c89b898b0fbc5a04e8834f60f23e55a&v=4
- url: https://github.com/ArcLightSlavik
-- login: cassiobotaro
- count: 16
- avatarUrl: https://avatars.githubusercontent.com/u/3127847?u=b0a652331da17efeb85cd6e3a4969182e5004804&v=4
- url: https://github.com/cassiobotaro
+- login: yezz123
+ count: 19
+ avatarUrl: https://avatars.githubusercontent.com/u/52716203?u=636b4f79645176df4527dd45c12d5dbb5a4193cf&v=4
+ url: https://github.com/yezz123
+- login: hard-coders
+ count: 19
+ avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=95db33927bbff1ed1c07efddeb97ac2ff33068ed&v=4
+ url: https://github.com/hard-coders
+- login: 0417taehyun
+ count: 19
+ avatarUrl: https://avatars.githubusercontent.com/u/63915557?u=47debaa860fd52c9b98c97ef357ddcec3b3fb399&v=4
+ url: https://github.com/0417taehyun
+- login: zy7y
+ count: 17
+ avatarUrl: https://avatars.githubusercontent.com/u/67154681?u=5d634834cc514028ea3f9115f7030b99a1f4d5a4&v=4
+ url: https://github.com/zy7y
- login: yanever
count: 16
avatarUrl: https://avatars.githubusercontent.com/u/21978760?v=4
url: https://github.com/yanever
+- login: lsglucas
+ count: 16
+ avatarUrl: https://avatars.githubusercontent.com/u/61513630?u=320e43fe4dc7bc6efc64e9b8f325f8075634fd20&v=4
+ url: https://github.com/lsglucas
- login: SwftAlpc
count: 16
avatarUrl: https://avatars.githubusercontent.com/u/52768429?u=6a3aa15277406520ad37f6236e89466ed44bc5b8&v=4
url: https://github.com/SwftAlpc
+- login: Smlep
+ count: 16
+ avatarUrl: https://avatars.githubusercontent.com/u/16785985?v=4
+ url: https://github.com/Smlep
+- login: DevDae
+ count: 16
+ avatarUrl: https://avatars.githubusercontent.com/u/87962045?u=08e10fa516e844934f4b3fc7c38b33c61697e4a1&v=4
+ url: https://github.com/DevDae
- login: pedabraham
count: 15
avatarUrl: https://avatars.githubusercontent.com/u/16860088?u=abf922a7b920bf8fdb7867d8b43e091f1e796178&v=4
@@ -292,16 +388,16 @@ top_reviewers:
count: 15
avatarUrl: https://avatars.githubusercontent.com/u/63476957?u=6c86e59b48e0394d4db230f37fc9ad4d7e2c27c7&v=4
url: https://github.com/delhi09
+- login: rjNemo
+ count: 14
+ avatarUrl: https://avatars.githubusercontent.com/u/56785022?u=d5c3a02567c8649e146fcfc51b6060ccaf8adef8&v=4
+ url: https://github.com/rjNemo
- login: RunningIkkyu
count: 12
avatarUrl: https://avatars.githubusercontent.com/u/31848542?u=706e1ee3f248245f2d68b976d149d06fd5a2010d&v=4
url: https://github.com/RunningIkkyu
-- login: hard-coders
- count: 12
- avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=f2d3d2038c55d86d7f9348f4e6c5e30191e4ee8b&v=4
- url: https://github.com/hard-coders
- login: sh0nk
- count: 11
+ count: 12
avatarUrl: https://avatars.githubusercontent.com/u/6478810?u=af15d724875cec682ed8088a86d36b2798f981c0&v=4
url: https://github.com/sh0nk
- login: mariacamilagl
@@ -316,26 +412,46 @@ top_reviewers:
count: 10
avatarUrl: https://avatars.githubusercontent.com/u/7887703?v=4
url: https://github.com/maoyibo
+- login: solomein-sv
+ count: 10
+ avatarUrl: https://avatars.githubusercontent.com/u/46193920?u=46acfb4aeefb1d7b9fdc5a8cbd9eb8744683c47a&v=4
+ url: https://github.com/solomein-sv
+- login: graingert
+ count: 9
+ avatarUrl: https://avatars.githubusercontent.com/u/413772?v=4
+ url: https://github.com/graingert
- login: PandaHun
count: 9
avatarUrl: https://avatars.githubusercontent.com/u/13096845?u=646eba44db720e37d0dbe8e98e77ab534ea78a20&v=4
url: https://github.com/PandaHun
+- login: kty4119
+ count: 9
+ avatarUrl: https://avatars.githubusercontent.com/u/49435654?v=4
+ url: https://github.com/kty4119
- login: bezaca
count: 9
avatarUrl: https://avatars.githubusercontent.com/u/69092910?u=4ac58eab99bd37d663f3d23551df96d4fbdbf760&v=4
url: https://github.com/bezaca
-- login: lsglucas
- count: 8
- avatarUrl: https://avatars.githubusercontent.com/u/61513630?u=320e43fe4dc7bc6efc64e9b8f325f8075634fd20&v=4
- url: https://github.com/lsglucas
-- login: rjNemo
- count: 8
- avatarUrl: https://avatars.githubusercontent.com/u/56785022?u=d5c3a02567c8649e146fcfc51b6060ccaf8adef8&v=4
- url: https://github.com/rjNemo
- login: blt232018
count: 8
avatarUrl: https://avatars.githubusercontent.com/u/43393471?u=172b0e0391db1aa6c1706498d6dfcb003c8a4857&v=4
url: https://github.com/blt232018
+- login: rogerbrinkmann
+ count: 8
+ avatarUrl: https://avatars.githubusercontent.com/u/5690226?v=4
+ url: https://github.com/rogerbrinkmann
+- login: ComicShrimp
+ count: 8
+ avatarUrl: https://avatars.githubusercontent.com/u/43503750?u=b3e4d9a14d9a65d429ce62c566aef73178b7111d&v=4
+ url: https://github.com/ComicShrimp
+- login: NinaHwang
+ count: 8
+ avatarUrl: https://avatars.githubusercontent.com/u/79563565?u=1741703bd6c8f491503354b363a86e879b4c1cab&v=4
+ url: https://github.com/NinaHwang
+- login: dimaqq
+ count: 8
+ avatarUrl: https://avatars.githubusercontent.com/u/662249?v=4
+ url: https://github.com/dimaqq
- login: Serrones
count: 7
avatarUrl: https://avatars.githubusercontent.com/u/22691749?u=4795b880e13ca33a73e52fc0ef7dc9c60c8fce47&v=4
@@ -352,401 +468,27 @@ top_reviewers:
count: 7
avatarUrl: https://avatars.githubusercontent.com/u/8245071?u=b3afd005f9e4bf080c219ef61a592b3a8004b764&v=4
url: https://github.com/NastasiaSaby
-- login: Smlep
+- login: Mause
count: 7
- avatarUrl: https://avatars.githubusercontent.com/u/16785985?v=4
- url: https://github.com/Smlep
+ avatarUrl: https://avatars.githubusercontent.com/u/1405026?v=4
+ url: https://github.com/Mause
+- login: AlexandreBiguet
+ count: 7
+ avatarUrl: https://avatars.githubusercontent.com/u/1483079?u=ff926455cd4cab03c6c49441aa5dc2b21df3e266&v=4
+ url: https://github.com/AlexandreBiguet
+- login: krocdort
+ count: 7
+ avatarUrl: https://avatars.githubusercontent.com/u/34248814?v=4
+ url: https://github.com/krocdort
- login: jovicon
count: 6
avatarUrl: https://avatars.githubusercontent.com/u/21287303?u=b049eac3e51a4c0473c2efe66b4d28a7d8f2b572&v=4
url: https://github.com/jovicon
-- login: Mause
+- login: LorhanSohaky
count: 6
- avatarUrl: https://avatars.githubusercontent.com/u/1405026?v=4
- url: https://github.com/Mause
-- login: nimctl
- count: 5
- avatarUrl: https://avatars.githubusercontent.com/u/49960770?u=e39b11d47188744ee07b2a1c7ce1a1bdf3c80760&v=4
- url: https://github.com/nimctl
-- login: juntatalor
- count: 5
- avatarUrl: https://avatars.githubusercontent.com/u/8134632?v=4
- url: https://github.com/juntatalor
-- login: SnkSynthesis
- count: 5
- avatarUrl: https://avatars.githubusercontent.com/u/63564282?u=0078826509dbecb2fdb543f4e881c9cd06157893&v=4
- url: https://github.com/SnkSynthesis
-- login: anthonycepeda
- count: 5
- avatarUrl: https://avatars.githubusercontent.com/u/72019805?u=892f700c79f9732211bd5221bf16eec32356a732&v=4
- url: https://github.com/anthonycepeda
-- login: euri10
- count: 4
- avatarUrl: https://avatars.githubusercontent.com/u/1104190?u=321a2e953e6645a7d09b732786c7a8061e0f8a8b&v=4
- url: https://github.com/euri10
-- login: rkbeatss
- count: 4
- avatarUrl: https://avatars.githubusercontent.com/u/23391143?u=56ab6bff50be950fa8cae5cf736f2ae66e319ff3&v=4
- url: https://github.com/rkbeatss
-- login: aviramha
- count: 4
- avatarUrl: https://avatars.githubusercontent.com/u/41201924?u=6883cc4fc13a7b2e60d4deddd4be06f9c5287880&v=4
- url: https://github.com/aviramha
-- login: Zxilly
- count: 4
- avatarUrl: https://avatars.githubusercontent.com/u/31370133?v=4
- url: https://github.com/Zxilly
-- login: Bluenix2
- count: 4
- avatarUrl: https://avatars.githubusercontent.com/u/38372706?u=c9d28aff15958d6ebf1971148bfb3154ff943c4f&v=4
- url: https://github.com/Bluenix2
-sponsors_50:
-- login: johnadjei
- avatarUrl: https://avatars.githubusercontent.com/u/767860?v=4
- url: https://github.com/johnadjei
-- login: wdwinslow
- avatarUrl: https://avatars.githubusercontent.com/u/11562137?u=dc01daafb354135603a263729e3d26d939c0c452&v=4
- url: https://github.com/wdwinslow
-- login: bingwu-chime
- avatarUrl: https://avatars.githubusercontent.com/u/67026650?u=603a6b345f25c20c6706a8a6c7f71ae688d649a5&v=4
- url: https://github.com/bingwu-chime
-sponsors:
-- login: kamalgill
- avatarUrl: https://avatars.githubusercontent.com/u/133923?u=0df9181d97436ce330e9acf90ab8a54b7022efe7&v=4
- url: https://github.com/kamalgill
-- login: grillazz
- avatarUrl: https://avatars.githubusercontent.com/u/3415861?u=16d7d0ffa5dfb99f8834f8f76d90e138ba09b94a&v=4
- url: https://github.com/grillazz
-- login: tizz98
- avatarUrl: https://avatars.githubusercontent.com/u/5739698?u=f095a3659e3a8e7c69ccd822696990b521ea25f9&v=4
- url: https://github.com/tizz98
-- login: jmaralc
- avatarUrl: https://avatars.githubusercontent.com/u/21101214?u=b15a9f07b7cbf6c9dcdbcb6550bbd2c52f55aa50&v=4
- url: https://github.com/jmaralc
-- login: psgandalf
- avatarUrl: https://avatars.githubusercontent.com/u/8134158?v=4
- url: https://github.com/psgandalf
-- login: samuelcolvin
- avatarUrl: https://avatars.githubusercontent.com/u/4039449?u=807390ba9cfe23906c3bf8a0d56aaca3cf2bfa0d&v=4
- url: https://github.com/samuelcolvin
-- login: jokull
- avatarUrl: https://avatars.githubusercontent.com/u/701?u=0532b62166893d5160ef795c4c8b7512d971af05&v=4
- url: https://github.com/jokull
-- login: wshayes
- avatarUrl: https://avatars.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4
- url: https://github.com/wshayes
-- login: koxudaxi
- avatarUrl: https://avatars.githubusercontent.com/u/630670?u=507d8577b4b3670546b449c4c2ccbc5af40d72f7&v=4
- url: https://github.com/koxudaxi
-- login: falkben
- avatarUrl: https://avatars.githubusercontent.com/u/653031?u=0c8d8f33d87f1aa1a6488d3f02105e9abc838105&v=4
- url: https://github.com/falkben
-- login: jqueguiner
- avatarUrl: https://avatars.githubusercontent.com/u/690878?u=e4835b2a985a0f2d52018e4926cb5a58c26a62e8&v=4
- url: https://github.com/jqueguiner
-- login: Mazyod
- avatarUrl: https://avatars.githubusercontent.com/u/860511?v=4
- url: https://github.com/Mazyod
-- login: ltieman
- avatarUrl: https://avatars.githubusercontent.com/u/1084689?u=e69b17de17cb3ca141a17daa7ccbe173ceb1eb17&v=4
- url: https://github.com/ltieman
-- login: mrmattwright
- avatarUrl: https://avatars.githubusercontent.com/u/1277725?v=4
- url: https://github.com/mrmattwright
-- login: westonsteimel
- avatarUrl: https://avatars.githubusercontent.com/u/1593939?u=0f2c0e3647f916fe295d62fa70da7a4c177115e3&v=4
- url: https://github.com/westonsteimel
-- login: timdrijvers
- avatarUrl: https://avatars.githubusercontent.com/u/1694939?v=4
- url: https://github.com/timdrijvers
-- login: mrgnw
- avatarUrl: https://avatars.githubusercontent.com/u/2504532?u=7ec43837a6d0afa80f96f0788744ea6341b89f97&v=4
- url: https://github.com/mrgnw
-- login: madisonmay
- avatarUrl: https://avatars.githubusercontent.com/u/2645393?u=f22b93c6ea345a4d26a90a3834dfc7f0789fcb63&v=4
- url: https://github.com/madisonmay
-- login: saivarunk
- avatarUrl: https://avatars.githubusercontent.com/u/2976867?u=71f4385e781e9a9e871a52f2d4686f9a8d69ba2f&v=4
- url: https://github.com/saivarunk
-- login: andre1sk
- avatarUrl: https://avatars.githubusercontent.com/u/3148093?v=4
- url: https://github.com/andre1sk
-- login: Shark009
- avatarUrl: https://avatars.githubusercontent.com/u/3163309?v=4
- url: https://github.com/Shark009
-- login: peterHoburg
- avatarUrl: https://avatars.githubusercontent.com/u/3860655?u=f55f47eb2d6a9b495e806ac5a044e3ae01ccc1fa&v=4
- url: https://github.com/peterHoburg
-- login: dudil
- avatarUrl: https://avatars.githubusercontent.com/u/4785835?u=58b7ea39123e0507f3b2996448a27256b16fd697&v=4
- url: https://github.com/dudil
-- login: ennui93
- avatarUrl: https://avatars.githubusercontent.com/u/5300907?u=5b5452725ddb391b2caaebf34e05aba873591c3a&v=4
- url: https://github.com/ennui93
-- login: sco1
- avatarUrl: https://avatars.githubusercontent.com/u/5323929?u=2b8434060d0c9d93de80a2a945baed94a412c31e&v=4
- url: https://github.com/sco1
-- login: MacroPower
- avatarUrl: https://avatars.githubusercontent.com/u/5648814?u=b2730000c9f9a471282b9849d2cc85711d7973d4&v=4
- url: https://github.com/MacroPower
-- login: ginomempin
- avatarUrl: https://avatars.githubusercontent.com/u/6091865?v=4
- url: https://github.com/ginomempin
-- login: iwpnd
- avatarUrl: https://avatars.githubusercontent.com/u/6152183?u=b2286006daafff5f991557344fee20b5da59639a&v=4
- url: https://github.com/iwpnd
-- login: s3ich4n
- avatarUrl: https://avatars.githubusercontent.com/u/6926298?u=ba3025d698e1c986655e776ae383a3d60d9d578e&v=4
- url: https://github.com/s3ich4n
-- login: Rehket
- avatarUrl: https://avatars.githubusercontent.com/u/7015688?u=3afb0ba200feebbc7f958950e92db34df2a3c172&v=4
- url: https://github.com/Rehket
-- login: christippett
- avatarUrl: https://avatars.githubusercontent.com/u/7218120?u=434b9d29287d7de25772d94ddc74a9bd6d969284&v=4
- url: https://github.com/christippett
-- login: Kludex
- avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=cf8455cb899806b774a3a71073f88583adec99f6&v=4
- url: https://github.com/Kludex
-- login: Shackelford-Arden
- avatarUrl: https://avatars.githubusercontent.com/u/7362263?v=4
- url: https://github.com/Shackelford-Arden
-- login: macleodmac
- avatarUrl: https://avatars.githubusercontent.com/u/8996312?u=e39c68c3e0b1d264dcba4850134a291680f46355&v=4
- url: https://github.com/macleodmac
-- login: cristeaadrian
- avatarUrl: https://avatars.githubusercontent.com/u/9112724?v=4
- url: https://github.com/cristeaadrian
-- login: otivvormes
- avatarUrl: https://avatars.githubusercontent.com/u/11317418?u=6de1edefb6afd0108c0ad2816bd6efc4464a9c44&v=4
- url: https://github.com/otivvormes
-- login: iambobmae
- avatarUrl: https://avatars.githubusercontent.com/u/12390270?u=c9a35c2ee5092a9b4135ebb1f91b7f521c467031&v=4
- url: https://github.com/iambobmae
-- login: ronaldnwilliams
- avatarUrl: https://avatars.githubusercontent.com/u/13632749?u=ac41a086d0728bf66a9d2bee9e5e377041ff44a4&v=4
- url: https://github.com/ronaldnwilliams
-- login: uselessscat
- avatarUrl: https://avatars.githubusercontent.com/u/15332878?u=8485a1b7383c274b28f383370ee2d5f9a6cd423b&v=4
- url: https://github.com/uselessscat
-- login: natenka
- avatarUrl: https://avatars.githubusercontent.com/u/15850513?u=00d1083c980d0b4ce32835dc07eee7f43f34fd2f&v=4
- url: https://github.com/natenka
-- login: la-mar
- avatarUrl: https://avatars.githubusercontent.com/u/16618300?u=7755c0521d2bb0d704f35a51464b15c1e2e6c4da&v=4
- url: https://github.com/la-mar
-- login: robintully
- avatarUrl: https://avatars.githubusercontent.com/u/17059673?u=862b9bb01513f5acd30df97433cb97a24dbfb772&v=4
- url: https://github.com/robintully
-- login: ShaulAb
- avatarUrl: https://avatars.githubusercontent.com/u/18129076?u=2c8d48e47f2dbee15c3f89c3d17d4c356504386c&v=4
- url: https://github.com/ShaulAb
-- login: wedwardbeck
- avatarUrl: https://avatars.githubusercontent.com/u/19333237?u=1de4ae2bf8d59eb4c013f21d863cbe0f2010575f&v=4
- url: https://github.com/wedwardbeck
-- login: linusg
- avatarUrl: https://avatars.githubusercontent.com/u/19366641?u=125e390abef8fff3b3b0d370c369cba5d7fd4c67&v=4
- url: https://github.com/linusg
-- login: RedCarpetUp
- avatarUrl: https://avatars.githubusercontent.com/u/20360440?v=4
- url: https://github.com/RedCarpetUp
-- login: daddycocoaman
- avatarUrl: https://avatars.githubusercontent.com/u/21189155?u=756f6a17c71c538b11470f70839baacab43807ef&v=4
- url: https://github.com/daddycocoaman
-- login: Filimoa
- avatarUrl: https://avatars.githubusercontent.com/u/21352040?u=75e02d102d2ee3e3d793e555fa5c63045913ccb0&v=4
- url: https://github.com/Filimoa
-- login: raminsj13
- avatarUrl: https://avatars.githubusercontent.com/u/24259406?u=d51f2a526312ebba150a06936ed187ca0727d329&v=4
- url: https://github.com/raminsj13
-- login: comoelcometa
- avatarUrl: https://avatars.githubusercontent.com/u/25950317?u=c6751efa038561b9bc5fa56d1033d5174e10cd65&v=4
- url: https://github.com/comoelcometa
-- login: veprimk
- avatarUrl: https://avatars.githubusercontent.com/u/29689749?u=f8cb5a15a286e522e5b189bc572d5a1a90217fb2&v=4
- url: https://github.com/veprimk
-- login: orihomie
- avatarUrl: https://avatars.githubusercontent.com/u/29889683?u=6bc2135a52fcb3a49e69e7d50190796618185fda&v=4
- url: https://github.com/orihomie
-- login: SaltyCoco
- avatarUrl: https://avatars.githubusercontent.com/u/31451104?u=6ee4e17c07d21b7054f54a12fa9cc377a1b24ff9&v=4
- url: https://github.com/SaltyCoco
-- login: mauroalejandrojm
- avatarUrl: https://avatars.githubusercontent.com/u/31569442?u=cdada990a1527926a36e95f62c30a8b48bbc49a1&v=4
- url: https://github.com/mauroalejandrojm
-- login: public-daniel
- avatarUrl: https://avatars.githubusercontent.com/u/32238294?u=0377e38dd023395c9643d5388b4e9489a24b4d34&v=4
- url: https://github.com/public-daniel
-- login: ybressler
- avatarUrl: https://avatars.githubusercontent.com/u/40807730?u=6621dc9ab53b697912ab2a32211bb29ae90a9112&v=4
- url: https://github.com/ybressler
-- login: dbanty
- avatarUrl: https://avatars.githubusercontent.com/u/43723790?u=0cf33e4f40efc2ea206a1189fd63a11344eb88ed&v=4
- url: https://github.com/dbanty
-- login: dudikbender
- avatarUrl: https://avatars.githubusercontent.com/u/53487583?u=494f85229115076121b3639a3806bbac1c6ae7f6&v=4
- url: https://github.com/dudikbender
-- login: primer-io
- avatarUrl: https://avatars.githubusercontent.com/u/62146168?v=4
- url: https://github.com/primer-io
-- login: tkrestiankova
- avatarUrl: https://avatars.githubusercontent.com/u/67013045?v=4
- url: https://github.com/tkrestiankova
-- login: daverin
- avatarUrl: https://avatars.githubusercontent.com/u/70378377?u=6d1814195c0de7162820eaad95a25b423a3869c0&v=4
- url: https://github.com/daverin
-- login: anthonycepeda
- avatarUrl: https://avatars.githubusercontent.com/u/72019805?u=892f700c79f9732211bd5221bf16eec32356a732&v=4
- url: https://github.com/anthonycepeda
-- login: linux-china
- avatarUrl: https://avatars.githubusercontent.com/u/46711?v=4
- url: https://github.com/linux-china
-- login: jhb
- avatarUrl: https://avatars.githubusercontent.com/u/142217?v=4
- url: https://github.com/jhb
-- login: yourkin
- avatarUrl: https://avatars.githubusercontent.com/u/178984?v=4
- url: https://github.com/yourkin
-- login: jmagnusson
- avatarUrl: https://avatars.githubusercontent.com/u/190835?v=4
- url: https://github.com/jmagnusson
-- login: slafs
- avatarUrl: https://avatars.githubusercontent.com/u/210173?v=4
- url: https://github.com/slafs
-- login: adamghill
- avatarUrl: https://avatars.githubusercontent.com/u/317045?u=f1349d5ffe84a19f324e204777859fbf69ddf633&v=4
- url: https://github.com/adamghill
-- login: eteq
- avatarUrl: https://avatars.githubusercontent.com/u/346587?v=4
- url: https://github.com/eteq
-- login: dmig
- avatarUrl: https://avatars.githubusercontent.com/u/388564?v=4
- url: https://github.com/dmig
-- login: hongqn
- avatarUrl: https://avatars.githubusercontent.com/u/405587?u=470b4c04832e45141fd5264d3354845cc9fc6466&v=4
- url: https://github.com/hongqn
-- login: rinckd
- avatarUrl: https://avatars.githubusercontent.com/u/546002?u=1fcc7e664dc86524a0af6837a0c222829c3fd4e5&v=4
- url: https://github.com/rinckd
-- login: Pytlicek
- avatarUrl: https://avatars.githubusercontent.com/u/1430522?u=169dba3bfbc04ed214a914640ff435969f19ddb3&v=4
- url: https://github.com/Pytlicek
-- login: okken
- avatarUrl: https://avatars.githubusercontent.com/u/1568356?u=0a991a21bdc62e2bea9ad311652f2c45f453dc84&v=4
- url: https://github.com/okken
-- login: leogregianin
- avatarUrl: https://avatars.githubusercontent.com/u/1684053?u=94ddd387601bd1805034dbe83e6eba0491c15323&v=4
- url: https://github.com/leogregianin
-- login: cbonoz
- avatarUrl: https://avatars.githubusercontent.com/u/2351087?u=fd3e8030b2cc9fbfbb54a65e9890c548a016f58b&v=4
- url: https://github.com/cbonoz
-- login: rglsk
- avatarUrl: https://avatars.githubusercontent.com/u/2768101?u=e349c88673f2155fe021331377c656a9d74bcc25&v=4
- url: https://github.com/rglsk
-- login: Atem18
- avatarUrl: https://avatars.githubusercontent.com/u/2875254?v=4
- url: https://github.com/Atem18
-- login: paul121
- avatarUrl: https://avatars.githubusercontent.com/u/3116995?u=6e2d8691cc345e63ee02e4eb4d7cef82b1fcbedc&v=4
- url: https://github.com/paul121
-- login: igorcorrea
- avatarUrl: https://avatars.githubusercontent.com/u/3438238?u=c57605077c31a8f7b2341fc4912507f91b4a5621&v=4
- url: https://github.com/igorcorrea
-- login: zsinx6
- avatarUrl: https://avatars.githubusercontent.com/u/3532625?u=ba75a5dc744d1116ccfeaaf30d41cb2fe81fe8dd&v=4
- url: https://github.com/zsinx6
-- login: pawamoy
- avatarUrl: https://avatars.githubusercontent.com/u/3999221?u=b030e4c89df2f3a36bc4710b925bdeb6745c9856&v=4
- url: https://github.com/pawamoy
-- login: spyker77
- avatarUrl: https://avatars.githubusercontent.com/u/4953435?u=568baae6469628e020fe0bab16e395b7ae10c7d3&v=4
- url: https://github.com/spyker77
-- login: serfer2
- avatarUrl: https://avatars.githubusercontent.com/u/5196592?u=e8798d87120952ed41876778f0cc8a1ddb47f901&v=4
- url: https://github.com/serfer2
-- login: JonasKs
- avatarUrl: https://avatars.githubusercontent.com/u/5310116?u=98a049f3e1491bffb91e1feb7e93def6881a9389&v=4
- url: https://github.com/JonasKs
-- login: holec
- avatarUrl: https://avatars.githubusercontent.com/u/6438041?u=f5af71ec85b3a9d7b8139cb5af0512b02fa9ab1e&v=4
- url: https://github.com/holec
-- login: BartlomiejRasztabiga
- avatarUrl: https://avatars.githubusercontent.com/u/8852711?u=ed213d60f7a423df31ceb1004aa3ec60e612cb98&v=4
- url: https://github.com/BartlomiejRasztabiga
-- login: davanstrien
- avatarUrl: https://avatars.githubusercontent.com/u/8995957?u=fb2aad2b52bb4e7b56db6d7c8ecc9ae1eac1b984&v=4
- url: https://github.com/davanstrien
-- login: and-semakin
- avatarUrl: https://avatars.githubusercontent.com/u/9129071?u=ea77ddf7de4bc375d546bf2825ed420eaddb7666&v=4
- url: https://github.com/and-semakin
-- login: VivianSolide
- avatarUrl: https://avatars.githubusercontent.com/u/9358572?u=ffb2e2ec522a15dcd3f0af1f9fd1df4afe418afa&v=4
- url: https://github.com/VivianSolide
-- login: hard-coders
- avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=f2d3d2038c55d86d7f9348f4e6c5e30191e4ee8b&v=4
- url: https://github.com/hard-coders
-- login: satwikkansal
- avatarUrl: https://avatars.githubusercontent.com/u/10217535?u=b12d6ef74ea297de9e46da6933b1a5b7ba9e6a61&v=4
- url: https://github.com/satwikkansal
-- login: pheanex
- avatarUrl: https://avatars.githubusercontent.com/u/10408624?u=5b6bab6ee174aa6e991333e06eb29f628741013d&v=4
- url: https://github.com/pheanex
-- login: wotori
- avatarUrl: https://avatars.githubusercontent.com/u/10486621?u=0044c295b91694b8c9bccc0a805681f794250f7b&v=4
- url: https://github.com/wotori
-- login: JimFawkes
- avatarUrl: https://avatars.githubusercontent.com/u/12075115?u=dc58ecfd064d72887c34bf500ddfd52592509acd&v=4
- url: https://github.com/JimFawkes
-- login: logan-connolly
- avatarUrl: https://avatars.githubusercontent.com/u/16244943?u=8ae66dfbba936463cc8aa0dd7a6d2b4c0cc757eb&v=4
- url: https://github.com/logan-connolly
-- login: iPr0ger
- avatarUrl: https://avatars.githubusercontent.com/u/19322290?v=4
- url: https://github.com/iPr0ger
-- login: sadikkuzu
- avatarUrl: https://avatars.githubusercontent.com/u/23168063?u=765ed469c44c004560079210ccdad5b29938eaa9&v=4
- url: https://github.com/sadikkuzu
-- login: ghandic
- avatarUrl: https://avatars.githubusercontent.com/u/23500353?u=e2e1d736f924d9be81e8bfc565b6d8836ba99773&v=4
- url: https://github.com/ghandic
-- login: MoronVV
- avatarUrl: https://avatars.githubusercontent.com/u/24293616?v=4
- url: https://github.com/MoronVV
-- login: AngusWG
- avatarUrl: https://avatars.githubusercontent.com/u/26385612?u=f4d4c8bd2097cdd58eb9e385932b83c78777f3c0&v=4
- url: https://github.com/AngusWG
-- login: mertguvencli
- avatarUrl: https://avatars.githubusercontent.com/u/29762151?u=16a906d90df96c8cff9ea131a575c4bc171b1523&v=4
- url: https://github.com/mertguvencli
-- login: rgreen32
- avatarUrl: https://avatars.githubusercontent.com/u/35779241?u=c9d64ad1ab364b6a1ec8e3d859da9ca802d681d8&v=4
- url: https://github.com/rgreen32
-- login: askurihin
- avatarUrl: https://avatars.githubusercontent.com/u/37978981?v=4
- url: https://github.com/askurihin
-- login: berrysauce
- avatarUrl: https://avatars.githubusercontent.com/u/38889179?u=758ed15a5be8bbd03855f5a74f42c19f7946ee32&v=4
- url: https://github.com/berrysauce
-- login: JitPackJoyride
- avatarUrl: https://avatars.githubusercontent.com/u/40203625?u=9638bfeacfa5940358188f8205ce662bba022b53&v=4
- url: https://github.com/JitPackJoyride
-- login: es3n1n
- avatarUrl: https://avatars.githubusercontent.com/u/40367813?u=e881a3880f1e342d19a1ea7c8e1b6d76c52dc294&v=4
- url: https://github.com/es3n1n
-- login: ilias-ant
- avatarUrl: https://avatars.githubusercontent.com/u/42189572?u=a2d6121bac4d125d92ec207460fa3f1842d37e66&v=4
- url: https://github.com/ilias-ant
-- login: kbhatiya999
- avatarUrl: https://avatars.githubusercontent.com/u/47816034?v=4
- url: https://github.com/kbhatiya999
-- login: akanz1
- avatarUrl: https://avatars.githubusercontent.com/u/51492342?u=2280f57134118714645e16b535c1a37adf6b369b&v=4
- url: https://github.com/akanz1
-- login: rychardvale
- avatarUrl: https://avatars.githubusercontent.com/u/54805553?u=3d20ab05301d05f9ac3500fb79a2bfee3842b753&v=4
- url: https://github.com/rychardvale
-- login: athemeart
- avatarUrl: https://avatars.githubusercontent.com/u/61623624?v=4
- url: https://github.com/athemeart
-- login: Rhythmicc
- avatarUrl: https://avatars.githubusercontent.com/u/29839231?u=2100781089a259707c475c4547bd7995b0fc18ee&v=4
- url: https://github.com/Rhythmicc
+ avatarUrl: https://avatars.githubusercontent.com/u/16273730?u=095b66f243a2cd6a0aadba9a095009f8aaf18393&v=4
+ url: https://github.com/LorhanSohaky
+- login: peidrao
+ count: 6
+ avatarUrl: https://avatars.githubusercontent.com/u/32584628?u=88c2cb42a99e0f50cdeae3606992568184783ee5&v=4
+ url: https://github.com/peidrao
diff --git a/docs/en/data/sponsors.yml b/docs/en/data/sponsors.yml
index 49df39c9..ac825193 100644
--- a/docs/en/data/sponsors.yml
+++ b/docs/en/data/sponsors.yml
@@ -1,21 +1,42 @@
gold:
- - url: https://www.deta.sh/?ref=fastapi
- title: The launchpad for all your (team's) ideas
- img: https://fastapi.tiangolo.com/img/sponsors/deta.svg
- url: https://bit.ly/2QSouzH
title: "Jina: build neural search-as-a-service for any kind of data in just minutes."
img: https://fastapi.tiangolo.com/img/sponsors/jina.svg
+ - url: https://cryptapi.io/
+ title: "CryptAPI: Your easy to use, secure and privacy oriented payment gateway."
+ img: https://fastapi.tiangolo.com/img/sponsors/cryptapi.svg
+ - url: https://classiq.link/n4s
+ title: Join the team building a new SaaS platform that will change the computing world
+ img: https://fastapi.tiangolo.com/img/sponsors/classiq.png
+ - url: https://www.dropbase.io/careers
+ title: Dropbase - seamlessly collect, clean, and centralize data.
+ img: https://fastapi.tiangolo.com/img/sponsors/dropbase.svg
silver:
+ - url: https://www.deta.sh/?ref=fastapi
+ title: The launchpad for all your (team's) ideas
+ img: https://fastapi.tiangolo.com/img/sponsors/deta.svg
- url: https://www.investsuite.com/jobs
title: Wealthtech jobs with FastAPI
img: https://fastapi.tiangolo.com/img/sponsors/investsuite.svg
- - url: https://www.vim.so/?utm_source=FastAPI
- title: We help you master vim with interactive exercises
- img: https://fastapi.tiangolo.com/img/sponsors/vimso.png
- - url: https://talkpython.fm/fastapi-sponsor
+ - url: https://training.talkpython.fm/fastapi-courses
title: FastAPI video courses on demand from people you trust
img: https://fastapi.tiangolo.com/img/sponsors/talkpython.png
-bronze:
- url: https://testdriven.io/courses/tdd-fastapi/
title: Learn to build high-quality web apps with best practices
img: https://fastapi.tiangolo.com/img/sponsors/testdriven.svg
+ - url: https://github.com/deepset-ai/haystack/
+ title: Build powerful search from composable, open source building blocks
+ img: https://fastapi.tiangolo.com/img/sponsors/haystack-fastapi.svg
+ - url: https://www.udemy.com/course/fastapi-rest/
+ title: Learn FastAPI by building a complete project. Extend your knowledge on advanced web development-AWS, Payments, Emails.
+ img: https://fastapi.tiangolo.com/img/sponsors/ines-course.jpg
+ - url: https://careers.budget-insight.com/
+ title: Budget Insight is hiring!
+ img: https://fastapi.tiangolo.com/img/sponsors/budget-insight.svg
+bronze:
+ - url: https://www.exoflare.com/open-source/?utm_source=FastAPI&utm_campaign=open_source
+ title: Biosecurity risk assessments made easy.
+ img: https://fastapi.tiangolo.com/img/sponsors/exoflare.png
+ - url: https://striveworks.us/careers?utm_source=fastapi&utm_medium=sponsor_banner&utm_campaign=feb_march#openings
+ title: https://striveworks.us/careers
+ img: https://fastapi.tiangolo.com/img/sponsors/striveworks.png
diff --git a/docs/en/data/sponsors_badge.yml b/docs/en/data/sponsors_badge.yml
new file mode 100644
index 00000000..1c8b0cde
--- /dev/null
+++ b/docs/en/data/sponsors_badge.yml
@@ -0,0 +1,12 @@
+logins:
+ - jina-ai
+ - deta
+ - investsuite
+ - mikeckennedy
+ - deepset-ai
+ - cryptapi
+ - Striveworks
+ - xoflare
+ - InesIvanova
+ - DropbaseHQ
+ - VincentParedes
diff --git a/docs/en/docs/advanced/async-tests.md b/docs/en/docs/advanced/async-tests.md
index 921bdb70..d5116233 100644
--- a/docs/en/docs/advanced/async-tests.md
+++ b/docs/en/docs/advanced/async-tests.md
@@ -6,21 +6,9 @@ Being able to use asynchronous functions in your tests could be useful, for exam
Let's look at how we can make that work.
-## pytest-asyncio
+## pytest.mark.anyio
-If we want to call asynchronous functions in our tests, our test functions have to be asynchronous. Pytest provides a neat library for this, called `pytest-asyncio`, that allows us to specify that some test functions are to be called asynchronously.
-
-You can install it via:
-
-
+
+## Dataclasses in Nested Data Structures
+
+You can also combine `dataclasses` with other type annotations to make nested data structures.
+
+In some cases, you might still have to use Pydantic's version of `dataclasses`. For example, if you have errors with the automatically generated API documentation.
+
+In that case, you can simply swap the standard `dataclasses` with `pydantic.dataclasses`, which is a drop-in replacement:
+
+```{ .python .annotate hl_lines="1 5 8-11 14-17 23-25 28" }
+{!../../../docs_src/dataclasses/tutorial003.py!}
+```
+
+1. We still import `field` from standard `dataclasses`.
+
+2. `pydantic.dataclasses` is a drop-in replacement for `dataclasses`.
+
+3. The `Author` dataclass includes a list of `Item` dataclasses.
+
+4. The `Author` dataclass is used as the `response_model` parameter.
+
+5. You can use other standard type annotations with dataclasses as the request body.
+
+ In this case, it's a list of `Item` dataclasses.
+
+6. Here we are returning a dictionary that contains `items` which is a list of dataclasses.
+
+ FastAPI is still capable of serializing the data to JSON.
+
+7. Here the `response_model` is using a type annotation of a list of `Author` dataclasses.
+
+ Again, you can combine `dataclasses` with standard type annotations.
+
+8. Notice that this *path operation function* uses regular `def` instead of `async def`.
+
+ As always, in FastAPI you can combine `def` and `async def` as needed.
+
+ If you need a refresher about when to use which, check out the section _"In a hurry?"_ in the docs about `async` and `await`.
+
+9. This *path operation function* is not returning dataclasses (although it could), but a list of dictionaries with internal data.
+
+ FastAPI will use the `response_model` parameter (that includes dataclasses) to convert the response.
+
+You can combine `dataclasses` with other type annotations in many different combinations to form complex data structures.
+
+Check the in-code annotation tips above to see more specific details.
+
+## Learn More
+
+You can also combine `dataclasses` with other Pydantic models, inherit from them, include them in your own models, etc.
+
+To learn more, check the Pydantic docs about dataclasses.
+
+## Version
+
+This is available since FastAPI version `0.67.0`. 🔖
diff --git a/docs/en/docs/advanced/extending-openapi.md b/docs/en/docs/advanced/extending-openapi.md
index 9179126d..d1b14bc0 100644
--- a/docs/en/docs/advanced/extending-openapi.md
+++ b/docs/en/docs/advanced/extending-openapi.md
@@ -152,21 +152,6 @@ After that, your file structure could look like:
└── swagger-ui.css
```
-### Install `aiofiles`
-
-Now you need to install `aiofiles`:
-
-
-
+
+But you can disable it by setting `syntaxHighlight` to `False`:
+
+```Python hl_lines="3"
+{!../../../docs_src/extending_openapi/tutorial003.py!}
+```
+
+...and then Swagger UI won't show the syntax highlighting anymore:
+
+
+
+### Change the Theme
+
+The same way you could set the syntax highlighting theme with the key `"syntaxHighlight.theme"` (notice that it has a dot in the middle):
+
+```Python hl_lines="3"
+{!../../../docs_src/extending_openapi/tutorial004.py!}
+```
+
+That configuration would change the syntax highlighting color theme:
+
+
+
+### Change Default Swagger UI Parameters
+
+FastAPI includes some default configuration parameters appropriate for most of the use cases.
+
+It includes these default configurations:
+
+```Python
+{!../../../fastapi/openapi/docs.py[ln:7-13]!}
+```
+
+You can override any of them by setting a different value in the argument `swagger_ui_parameters`.
+
+For example, to disable `deepLinking` you could pass these settings to `swagger_ui_parameters`:
+
+```Python hl_lines="3"
+{!../../../docs_src/extending_openapi/tutorial005.py!}
+```
+
+### Other Swagger UI Parameters
+
+To see all the other possible configurations you can use, read the official docs for Swagger UI parameters.
+
+### JavaScript-only settings
+
+Swagger UI also allows other configurations to be **JavaScript-only** objects (for example, JavaScript functions).
+
+FastAPI also includes these JavaScript-only `presets` settings:
+
+```JavaScript
+presets: [
+ SwaggerUIBundle.presets.apis,
+ SwaggerUIBundle.SwaggerUIStandalonePreset
+]
+```
+
+These are **JavaScript** objects, not strings, so you can't pass them from Python code directly.
+
+If you need to use JavaScript-only configurations like those, you can use one of the methods above. Override all the Swagger UI *path operation* and manually write any JavaScript you need.
diff --git a/docs/en/docs/advanced/generate-clients.md b/docs/en/docs/advanced/generate-clients.md
new file mode 100644
index 00000000..f31a248d
--- /dev/null
+++ b/docs/en/docs/advanced/generate-clients.md
@@ -0,0 +1,267 @@
+# Generate Clients
+
+As **FastAPI** is based on the OpenAPI specification, you get automatic compatibility with many tools, including the automatic API docs (provided by Swagger UI).
+
+One particular advantage that is not necessarily obvious is that you can **generate clients** (sometimes called **SDKs** ) for your API, for many different **programming languages**.
+
+## OpenAPI Client Generators
+
+There are many tools to generate clients from **OpenAPI**.
+
+A common tool is OpenAPI Generator.
+
+If you are building a **frontend**, a very interesting alternative is openapi-typescript-codegen.
+
+## Generate a TypeScript Frontend Client
+
+Let's start with a simple FastAPI application:
+
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9-11 14-15 18 19 23"
+ {!> ../../../docs_src/generate_clients/tutorial001.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="7-9 12-13 16-17 21"
+ {!> ../../../docs_src/generate_clients/tutorial001_py39.py!}
+ ```
+
+Notice that the *path operations* define the models they use for request payload and response payload, using the models `Item` and `ResponseMessage`.
+
+### API Docs
+
+If you go to the API docs, you will see that it has the **schemas** for the data to be sent in requests and received in responses:
+
+
+
+You can see those schemas because they were declared with the models in the app.
+
+That information is available in the app's **OpenAPI schema**, and then shown in the API docs (by Swagger UI).
+
+And that same information from the models that is included in OpenAPI is what can be used to **generate the client code**.
+
+### Generate a TypeScript Client
+
+Now that we have the app with the models, we can generate the client code for the frontend.
+
+#### Install `openapi-typescript-codegen`
+
+You can install `openapi-typescript-codegen` in your frontend code with:
+
+
+
+You will also get autocompletion for the payload to send:
+
+
+
+!!! tip
+ Notice the autocompletion for `name` and `price`, that was defined in the FastAPI application, in the `Item` model.
+
+You will have inline errors for the data that you send:
+
+
+
+The response object will also have autocompletion:
+
+
+
+## FastAPI App with Tags
+
+In many cases your FastAPI app will be bigger, and you will probably use tags to separate different groups of *path operations*.
+
+For example, you could have a section for **items** and another section for **users**, and they could be separated by tags:
+
+
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="23 28 36"
+ {!> ../../../docs_src/generate_clients/tutorial002.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="21 26 34"
+ {!> ../../../docs_src/generate_clients/tutorial002_py39.py!}
+ ```
+
+### Generate a TypeScript Client with Tags
+
+If you generate a client for a FastAPI app using tags, it will normally also separate the client code based on the tags.
+
+This way you will be able to have things ordered and grouped correctly for the client code:
+
+
+
+In this case you have:
+
+* `ItemsService`
+* `UsersService`
+
+### Client Method Names
+
+Right now the generated method names like `createItemItemsPost` don't look very clean:
+
+```TypeScript
+ItemsService.createItemItemsPost({name: "Plumbus", price: 5})
+```
+
+...that's because the client generator uses the OpenAPI internal **operation ID** for each *path operation*.
+
+OpenAPI requires that each operation ID is unique across all the *path operations*, so FastAPI uses the **function name**, the **path**, and the **HTTP method/operation** to generate that operation ID, because that way it can make sure that the operation IDs are unique.
+
+But I'll show you how to improve that next. 🤓
+
+## Custom Operation IDs and Better Method Names
+
+You can **modify** the way these operation IDs are **generated** to make them simpler and have **simpler method names** in the clients.
+
+In this case you will have to ensure that each operation ID is **unique** in some other way.
+
+For example, you could make sure that each *path operation* has a tag, and then generate the operation ID based on the **tag** and the *path operation* **name** (the function name).
+
+### Custom Generate Unique ID Function
+
+FastAPI uses a **unique ID** for each *path operation*, it is used for the **operation ID** and also for the names of any needed custom models, for requests or responses.
+
+You can customize that function. It takes an `APIRoute` and outputs a string.
+
+For example, here it is using the first tag (you will probably have only one tag) and the *path operation* name (the function name).
+
+You can then pass that custom function to **FastAPI** as the `generate_unique_id_function` parameter:
+
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="8-9 12"
+ {!> ../../../docs_src/generate_clients/tutorial003.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="6-7 10"
+ {!> ../../../docs_src/generate_clients/tutorial003_py39.py!}
+ ```
+
+### Generate a TypeScript Client with Custom Operation IDs
+
+Now if you generate the client again, you will see that it has the improved method names:
+
+
+
+As you see, the method names now have the tag and then the function name, now they don't include information from the URL path and the HTTP operation.
+
+### Preprocess the OpenAPI Specification for the Client Generator
+
+The generated code still has some **duplicated information**.
+
+We already know that this method is related to the **items** because that word is in the `ItemsService` (taken from the tag), but we still have the tag name prefixed in the method name too. 😕
+
+We will probably still want to keep it for OpenAPI in general, as that will ensure that the operation IDs are **unique**.
+
+But for the generated client we could **modify** the OpenAPI operation IDs right before generating the clients, just to make those method names nicer and **cleaner**.
+
+We could download the OpenAPI JSON to a file `openapi.json` and then we could **remove that prefixed tag** with a script like this:
+
+```Python
+{!../../../docs_src/generate_clients/tutorial004.py!}
+```
+
+With that, the operation IDs would be renamed from things like `items-get_items` to just `get_items`, that way the client generator can generate simpler method names.
+
+### Generate a TypeScript Client with the Preprocessed OpenAPI
+
+Now as the end result is in a file `openapi.json`, you would modify the `package.json` to use that local file, for example:
+
+```JSON hl_lines="7"
+{
+ "name": "frontend-app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "generate-client": "openapi --input ./openapi.json --output ./src/client --client axios"
+ },
+ "author": "",
+ "license": "",
+ "devDependencies": {
+ "openapi-typescript-codegen": "^0.20.1",
+ "typescript": "^4.6.2"
+ }
+}
+```
+
+After generating the new client, you would now have **clean method names**, with all the **autocompletion**, **inline errors**, etc:
+
+
+
+## Benefits
+
+When using the automatically generated clients you would **autocompletion** for:
+
+* Methods.
+* Request payloads in the body, query parameters, etc.
+* Response payloads.
+
+You would also have **inline errors** for everything.
+
+And whenever you update the backend code, and **regenerate** the frontend, it would have any new *path operations* available as methods, the old ones removed, and any other change would be reflected on the generated code. 🤓
+
+This also means that if something changed it will be **reflected** on the client code automatically. And if you **build** the client it will error out if you have any **mismatch** in the data used.
+
+So, you would **detect many errors** very early in the development cycle instead of having to wait for the errors to show up to your final users in production and then trying to debug where the problem is. ✨
diff --git a/docs/en/docs/advanced/graphql.md b/docs/en/docs/advanced/graphql.md
index acc1a39f..15460640 100644
--- a/docs/en/docs/advanced/graphql.md
+++ b/docs/en/docs/advanced/graphql.md
@@ -1,44 +1,56 @@
# GraphQL
-**FastAPI** has optional support for GraphQL (provided by Starlette directly), using the `graphene` library.
+As **FastAPI** is based on the **ASGI** standard, it's very easy to integrate any **GraphQL** library also compatible with ASGI.
You can combine normal FastAPI *path operations* with GraphQL on the same application.
-## Import and use `graphene`
+!!! tip
+ **GraphQL** solves some very specific use cases.
-GraphQL is implemented with Graphene, you can check Graphene's docs for more details.
+ It has **advantages** and **disadvantages** when compared to common **web APIs**.
-Import `graphene` and define your GraphQL data:
+ Make sure you evaluate if the **benefits** for your use case compensate the **drawbacks**. 🤓
-```Python hl_lines="1 6-10"
+## GraphQL Libraries
+
+Here are some of the **GraphQL** libraries that have **ASGI** support. You could use them with **FastAPI**:
+
+* Strawberry 🍓
+ * With docs for FastAPI
+* Ariadne
+ * With docs for Starlette (that also apply to FastAPI)
+* Tartiflette
+ * With Tartiflette ASGI to provide ASGI integration
+* Graphene
+ * With starlette-graphene3
+
+## GraphQL with Strawberry
+
+If you need or want to work with **GraphQL**, **Strawberry** is the **recommended** library as it has the design closest to **FastAPI's** design, it's all based on **type annotations**.
+
+Depending on your use case, you might prefer to use a different library, but if you asked me, I would probably suggest you try **Strawberry**.
+
+Here's a small preview of how you could integrate Strawberry with FastAPI:
+
+```Python hl_lines="3 22 25-26"
{!../../../docs_src/graphql/tutorial001.py!}
```
-## Add Starlette's `GraphQLApp`
+You can learn more about Strawberry in the Strawberry documentation.
-Then import and add Starlette's `GraphQLApp`:
+And also the docs about Strawberry with FastAPI.
-```Python hl_lines="3 14"
-{!../../../docs_src/graphql/tutorial001.py!}
-```
+## Older `GraphQLApp` from Starlette
-!!! info
- Here we are using `.add_route`, that is the way to add a route in Starlette (inherited by FastAPI) without declaring the specific operation (as would be with `.get()`, `.post()`, etc).
+Previous versions of Starlette included a `GraphQLApp` class to integrate with Graphene.
-## Check it
+It was deprecated from Starlette, but if you have code that used it, you can easily **migrate** to starlette-graphene3, that covers the same use case and has an **almost identical interface**.
-Run it with Uvicorn and open your browser at http://127.0.0.1:8000.
+!!! tip
+ If you need GraphQL, I still would recommend you check out Strawberry, as it's based on type annotations instead of custom classes and types.
-You will see GraphiQL web user interface:
+## Learn More
-
+You can learn more about **GraphQL** in the official GraphQL documentation.
-## More details
-
-For more details, including:
-
-* Accessing request information
-* Adding background tasks
-* Using normal or async functions
-
-check the official Starlette GraphQL docs.
+You can also read more about each those libraries described above in their links.
diff --git a/docs/en/docs/advanced/path-operation-advanced-configuration.md b/docs/en/docs/advanced/path-operation-advanced-configuration.md
index 8d085d75..a1c902ef 100644
--- a/docs/en/docs/advanced/path-operation-advanced-configuration.md
+++ b/docs/en/docs/advanced/path-operation-advanced-configuration.md
@@ -33,7 +33,7 @@ You should do it after adding all your *path operations*.
## Exclude from OpenAPI
-To exclude a *path operation* from the generated OpenAPI schema (and thus, from the automatic documentation systems), use the parameter `include_in_schema` and set it to `False`;
+To exclude a *path operation* from the generated OpenAPI schema (and thus, from the automatic documentation systems), use the parameter `include_in_schema` and set it to `False`:
```Python hl_lines="6"
{!../../../docs_src/path_operation_advanced_configuration/tutorial003.py!}
@@ -50,3 +50,121 @@ It won't show up in the documentation, but other tools (such as Sphinx) will be
```Python hl_lines="19-29"
{!../../../docs_src/path_operation_advanced_configuration/tutorial004.py!}
```
+
+## Additional Responses
+
+You probably have seen how to declare the `response_model` and `status_code` for a *path operation*.
+
+That defines the metadata about the main response of a *path operation*.
+
+You can also declare additional responses with their models, status codes, etc.
+
+There's a whole chapter here in the documentation about it, you can read it at [Additional Responses in OpenAPI](./additional-responses.md){.internal-link target=_blank}.
+
+## OpenAPI Extra
+
+When you declare a *path operation* in your application, **FastAPI** automatically generates the relevant metadata about that *path operation* to be included in the OpenAPI schema.
+
+!!! note "Technical details"
+ In the OpenAPI specification it is called the Operation Object.
+
+It has all the information about the *path operation* and is used to generate the automatic documentation.
+
+It includes the `tags`, `parameters`, `requestBody`, `responses`, etc.
+
+This *path operation*-specific OpenAPI schema is normally generated automatically by **FastAPI**, but you can also extend it.
+
+!!! tip
+ This is a low level extension point.
+
+ If you only need to declare additional responses, a more convenient way to do it is with [Additional Responses in OpenAPI](./additional-responses.md){.internal-link target=_blank}.
+
+You can extend the OpenAPI schema for a *path operation* using the parameter `openapi_extra`.
+
+### OpenAPI Extensions
+
+This `openapi_extra` can be helpful, for example, to declare [OpenAPI Extensions](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions):
+
+```Python hl_lines="6"
+{!../../../docs_src/path_operation_advanced_configuration/tutorial005.py!}
+```
+
+If you open the automatic API docs, your extension will show up at the bottom of the specific *path operation*.
+
+
+
+And if you see the resulting OpenAPI (at `/openapi.json` in your API), you will see your extension as part of the specific *path operation* too:
+
+```JSON hl_lines="22"
+{
+ "openapi": "3.0.2",
+ "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": {}
+ }
+ }
+ }
+ },
+ "x-aperture-labs-portal": "blue"
+ }
+ }
+ }
+}
+```
+
+### Custom OpenAPI *path operation* schema
+
+The dictionary in `openapi_extra` will be deeply merged with the automatically generated OpenAPI schema for the *path operation*.
+
+So, you could add additional data to the automatically generated schema.
+
+For example, you could decide to read and validate the request with your own code, without using the automatic features of FastAPI with Pydantic, but you could still want to define the request in the OpenAPI schema.
+
+You could do that with `openapi_extra`:
+
+```Python hl_lines="20-37 39-40"
+{!../../../docs_src/path_operation_advanced_configuration/tutorial006.py!}
+```
+
+In this example, we didn't declare any Pydantic model. In fact, the request body is not even parsed as JSON, it is read directly as `bytes`, and the function `magic_data_reader()` would be in charge of parsing it in some way.
+
+Nevertheless, we can declare the expected schema for the request body.
+
+### Custom OpenAPI content type
+
+Using this same trick, you could use a Pydantic model to define the JSON Schema that is then included in the custom OpenAPI schema section for the *path operation*.
+
+And you could do this even if the data type in the request is not JSON.
+
+For example, in this application we don't use FastAPI's integrated functionality to extract the JSON Schema from Pydantic models nor the automatic validation for JSON. In fact, we are declaring the request content type as YAML, not JSON:
+
+```Python hl_lines="17-22 24"
+{!../../../docs_src/path_operation_advanced_configuration/tutorial007.py!}
+```
+
+Nevertheless, although we are not using the default integrated functionality, we are still using a Pydantic model to manually generate the JSON Schema for the data that we want to receive in YAML.
+
+Then we use the request directly, and extract the body as `bytes`. This means that FastAPI won't even try to parse the request payload as JSON.
+
+And then in our code, we parse that YAML content directly, and then we are again using the same Pydantic model to validate the YAML content:
+
+```Python hl_lines="26-33"
+{!../../../docs_src/path_operation_advanced_configuration/tutorial007.py!}
+```
+
+!!! tip
+ Here we re-use the same Pydantic model.
+
+ But the same way, we could have validated it in some other way.
diff --git a/docs/en/docs/advanced/settings.md b/docs/en/docs/advanced/settings.md
index 706a7117..d5151b58 100644
--- a/docs/en/docs/advanced/settings.md
+++ b/docs/en/docs/advanced/settings.md
@@ -235,7 +235,7 @@ And then we can require it from the *path operation function* as a dependency an
Then it would be very easy to provide a different settings object during testing by creating a dependency override for `get_settings`:
-```Python hl_lines="8-9 12 21"
+```Python hl_lines="9-10 13 21"
{!../../../docs_src/settings/app02/test_main.py!}
```
@@ -288,7 +288,7 @@ Reading a file from disk is normally a costly (slow) operation, so you probably
But every time we do:
```Python
-config.Settings()
+Settings()
```
a new `Settings` object would be created, and at creation it would read the `.env` file again.
@@ -297,7 +297,7 @@ If the dependency function was just like:
```Python
def get_settings():
- return config.Settings()
+ return Settings()
```
we would create that object for each request, and we would be reading the `.env` file for each request. ⚠️
diff --git a/docs/en/docs/advanced/templates.md b/docs/en/docs/advanced/templates.md
index a8e2575c..38618aee 100644
--- a/docs/en/docs/advanced/templates.md
+++ b/docs/en/docs/advanced/templates.md
@@ -2,7 +2,7 @@
You can use any template engine you want with **FastAPI**.
-A common election is Jinja2, the same one used by Flask and other tools.
+A common choice is Jinja2, the same one used by Flask and other tools.
There are utilities to configure it easily that you can use directly in your **FastAPI** application (provided by Starlette).
@@ -20,18 +20,6 @@ $ pip install jinja2
-If you need to also serve static files (as in this example), install `aiofiles`:
-
-
+
+---
+
+Now that we know the difference between the terms **process** and **program**, let's continue talking about deployments.
+
+## Running on Startup
+
+In most cases, when you create a web API, you want it to be **always running**, uninterrupted, so that your clients can always access it. This is of course, unless you have a specific reason why you want it to run only in certain situations, but most of the time you want it constantly running and **available**.
+
+### In a Remote Server
+
+When you set up a remote server (a cloud server, a virtual machine, etc.) the simplest thing you can do is to run Uvicorn (or similar) manually, the same way you do when developing locally.
+
+And it will work and will be useful **during development**.
+
+But if your connection to the server is lost, the **running process** will probably die.
+
+And if the server is restarted (for example after updates, or migrations from the cloud provider) you probably **won't notice it**. And because of that, you won't even know that you have to restart the process manually. So, your API will just stay dead. 😱
+
+### Run Automatically on Startup
+
+In general, you will probably want the server program (e.g. Uvicorn) to be started automatically on server startup, and without needing any **human intervention**, to have a process always running with your API (e.g. Uvicorn running your FastAPI app).
+
+### Separate Program
+
+To achieve this, you will normally have a **separate program** that would make sure your application is run on startup. And in many cases, it would also make sure other components or applications are also run, for example, a database.
+
+### Example Tools to Run at Startup
+
+Some examples of the tools that can do this job are:
+
+* Docker
+* Kubernetes
+* Docker Compose
+* Docker in Swarm Mode
+* Systemd
+* Supervisor
+* Handled internally by a cloud provider as part of their services
+* Others...
+
+I'll give you more concrete examples in the next chapters.
+
+## Restarts
+
+Similar to making sure your application is run on startup, you probably also want to make sure it is **restarted** after failures.
+
+### We Make Mistakes
+
+We, as humans, make **mistakes**, all the time. Software almost *always* has **bugs** hidden in different places. 🐛
+
+And we as developers keep improving the code as we find those bugs and as we implement new features (possibly adding new bugs too 😅).
+
+### Small Errors Automatically Handled
+
+When building web APIs with FastAPI, if there's an error in our code, FastAPI will normally contain it to the single request that triggered the error. 🛡
+
+The client will get a **500 Internal Server Error** for that request, but the application will continue working for the next requests instead of just crashing completely.
+
+### Bigger Errors - Crashes
+
+Nevertheless, there might be cases where we write some code that **crashes the entire application** making Uvicorn and Python crash. 💥
+
+And still, you would probably not want the application to stay dead because there was an error in one place, you probably want it to **continue running** at least for the *path operations* that are not broken.
+
+### Restart After Crash
+
+But in those cases with really bad errors that crash the running **process**, you would want an external component that is in charge of **restarting** the process, at least a couple of times...
+
+!!! tip
+ ...Although if the whole application is just **crashing immediately** it probably doesn't make sense to keep restarting it forever. But in those cases, you will probably notice it during development, or at least right after deployment.
+
+ So let's focus on the main cases, where it could crash entirely in some particular cases **in the future**, and it still makes sense to restart it.
+
+You would probably want to have the thing in charge of restarting your application as an **external component**, because by that point, the same application with Uvicorn and Python already crashed, so there's nothing in the same code of the same app that could do anything about it.
+
+### Example Tools to Restart Automatically
+
+In most cases, the same tool that is used to **run the program on startup** is also used to handle automatic **restarts**.
+
+For example, this could be handled by:
+
+* Docker
+* Kubernetes
+* Docker Compose
+* Docker in Swarm Mode
+* Systemd
+* Supervisor
+* Handled internally by a cloud provider as part of their services
+* Others...
+
+## Replication - Processes and Memory
+
+With a FastAPI application, using a server program like Uvicorn, running it once in **one process** can serve multiple clients concurrently.
+
+But in many cases, you will want to run several worker processes at the same time.
+
+### Multiple Processes - Workers
+
+If you have more clients than what a single process can handle (for example if the virtual machine is not too big) and you have **multiple cores** in the server's CPU, then you could have **multiple processes** running with the same application at the same time, and distribute all the requests among them.
+
+When you run **multiple processes** of the same API program, they are commonly called **workers**.
+
+### Worker Processes and Ports
+
+Remember from the docs [About HTTPS](./https.md){.internal-link target=_blank} that only one process can be listening on one combination of port and IP address in a server?
+
+This is still true.
+
+So, to be able to have **multiple processes** at the same time, there has to be a **single process listening on a port** that then transmits the communication to each worker process in some way.
+
+### Memory per Process
+
+Now, when the program loads things in memory, for example, a machine learning model in a variable, or the contents of a large file in a variable, all that **consumes a bit of the memory (RAM)** of the server.
+
+And multiple processes normally **don't share any memory**. This means that each running process has its own things, variables, and memory. And if you are consuming a large amount of memory in your code, **each process** will consume an equivalent amount of memory.
+
+### Server Memory
+
+For example, if your code loads a Machine Learning model with **1 GB in size**, when you run one process with your API, it will consume at least 1 GB of RAM. And if you start **4 processes** (4 workers), each will consume 1 GB of RAM. So in total, your API will consume **4 GB of RAM**.
+
+And if your remote server or virtual machine only has 3 GB of RAM, trying to load more than 4 GB of RAM will cause problems. 🚨
+
+### Multiple Processes - An Example
+
+In this example, there's a **Manager Process** that starts and controls two **Worker Processes**.
+
+This Manager Process would probably be the one listening on the **port** in the IP. And it would transmit all the communication to the worker processes.
+
+Those worker processes would be the ones running your application, they would perform the main computations to receive a **request** and return a **response**, and they would load anything you put in variables in RAM.
+
+
-
-
+
+
@@ -14,6 +14,9 @@
+
+
+
requests - Required if you want to use the `TestClient`.
-* aiofiles - Required if you want to use `FileResponse` or `StaticFiles`.
* jinja2 - Required if you want to use the default template configuration.
* python-multipart - Required if you want to support form "parsing", with `request.form()`.
* itsdangerous - Required for `SessionMiddleware` support.
* pyyaml - Required for Starlette's `SchemaGenerator` support (you probably don't need it with FastAPI).
-* graphene - Required for `GraphQLApp` support.
* ujson - Required if you want to use `UJSONResponse`.
Used by FastAPI / Starlette:
@@ -456,7 +457,7 @@ Used by FastAPI / Starlette:
* uvicorn - for the server that loads and serves your application.
* orjson - Required if you want to use `ORJSONResponse`.
-You can install all of these with `pip install fastapi[all]`.
+You can install all of these with `pip install "fastapi[all]"`.
## License
diff --git a/docs/en/docs/python-types.md b/docs/en/docs/python-types.md
index 9bbf955b..fe56dade 100644
--- a/docs/en/docs/python-types.md
+++ b/docs/en/docs/python-types.md
@@ -148,37 +148,62 @@ You can use, for example:
There are some data structures that can contain other values, like `dict`, `list`, `set` and `tuple`. And the internal values can have their own type too.
-To declare those types and the internal types, you can use the standard Python module `typing`.
+These types that have internal types are called "**generic**" types. And it's possible to declare them, even with their internal types.
-It exists specifically to support these type hints.
+To declare those types and the internal types, you can use the standard Python module `typing`. It exists specifically to support these type hints.
-#### `List`
+#### Newer versions of Python
+
+The syntax using `typing` is **compatible** with all versions, from Python 3.6 to the latest ones, including Python 3.9, Python 3.10, etc.
+
+As Python advances, **newer versions** come with improved support for these type annotations and in many cases you won't even need to import and use the `typing` module to declare the type annotations.
+
+If you can chose a more recent version of Python for your project, you will be able to take advantage of that extra simplicity. See some examples below.
+
+#### List
For example, let's define a variable to be a `list` of `str`.
-From `typing`, import `List` (with a capital `L`):
+=== "Python 3.6 and above"
-```Python hl_lines="1"
-{!../../../docs_src/python_types/tutorial006.py!}
-```
+ From `typing`, import `List` (with a capital `L`):
-Declare the variable, with the same colon (`:`) syntax.
+ ``` Python hl_lines="1"
+ {!> ../../../docs_src/python_types/tutorial006.py!}
+ ```
-As the type, put the `List`.
+ Declare the variable, with the same colon (`:`) syntax.
-As the list is a type that contains some internal types, you put them in square brackets:
+ As the type, put the `List` that you imported from `typing`.
-```Python hl_lines="4"
-{!../../../docs_src/python_types/tutorial006.py!}
-```
+ As the list is a type that contains some internal types, you put them in square brackets:
-!!! tip
+ ```Python hl_lines="4"
+ {!> ../../../docs_src/python_types/tutorial006.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ Declare the variable, with the same colon (`:`) syntax.
+
+ As the type, put `list`.
+
+ As the list is a type that contains some internal types, you put them in square brackets:
+
+ ```Python hl_lines="1"
+ {!> ../../../docs_src/python_types/tutorial006_py39.py!}
+ ```
+
+!!! info
Those internal types in the square brackets are called "type parameters".
- In this case, `str` is the type parameter passed to `List`.
+ In this case, `str` is the type parameter passed to `List` (or `list` in Python 3.9 and above).
That means: "the variable `items` is a `list`, and each of the items in this list is a `str`".
+!!! tip
+ If you use Python 3.9 or above, you don't have to import `List` from `typing`, you can use the same regular `list` type instead.
+
By doing that, your editor can provide support even while processing items from the list:
@@ -189,20 +214,28 @@ Notice that the variable `item` is one of the elements in the list `items`.
And still, the editor knows it is a `str`, and provides support for that.
-#### `Tuple` and `Set`
+#### Tuple and Set
You would do the same to declare `tuple`s and `set`s:
-```Python hl_lines="1 4"
-{!../../../docs_src/python_types/tutorial007.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="1 4"
+ {!> ../../../docs_src/python_types/tutorial007.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="1"
+ {!> ../../../docs_src/python_types/tutorial007_py39.py!}
+ ```
This means:
* The variable `items_t` is a `tuple` with 3 items, an `int`, another `int`, and a `str`.
* The variable `items_s` is a `set`, and each of its items is of type `bytes`.
-#### `Dict`
+#### Dict
To define a `dict`, you pass 2 type parameters, separated by commas.
@@ -210,9 +243,17 @@ The first type parameter is for the keys of the `dict`.
The second type parameter is for the values of the `dict`:
-```Python hl_lines="1 4"
-{!../../../docs_src/python_types/tutorial008.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="1 4"
+ {!> ../../../docs_src/python_types/tutorial008.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="1"
+ {!> ../../../docs_src/python_types/tutorial008_py39.py!}
+ ```
This means:
@@ -220,9 +261,33 @@ This means:
* The keys of this `dict` are of type `str` (let's say, the name of each item).
* The values of this `dict` are of type `float` (let's say, the price of each item).
-#### `Optional`
+#### Union
-You can also use `Optional` to declare that a variable has a type, like `str`, but that it is "optional", which means that it could also be `None`:
+You can declare that a variable can be any of **several types**, for example, an `int` or a `str`.
+
+In Python 3.6 and above (including Python 3.10) you can use the `Union` type from `typing` and put inside the square brackets the possible types to accept.
+
+In Python 3.10 there's also an **alternative syntax** were you can put the possible types separated by a vertical bar (`|`).
+
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="1 4"
+ {!> ../../../docs_src/python_types/tutorial008b.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="1"
+ {!> ../../../docs_src/python_types/tutorial008b_py310.py!}
+ ```
+
+In both cases this means that `item` could be an `int` or a `str`.
+
+#### Possibly `None`
+
+You can declare that a value could have a type, like `str`, but that it could also be `None`.
+
+In Python 3.6 and above (including Python 3.10) you can declare it by importing and using `Optional` from the `typing` module.
```Python hl_lines="1 4"
{!../../../docs_src/python_types/tutorial009.py!}
@@ -230,18 +295,73 @@ You can also use `Optional` to declare that a variable has a type, like `str`, b
Using `Optional[str]` instead of just `str` will let the editor help you detecting errors where you could be assuming that a value is always a `str`, when it could actually be `None` too.
+`Optional[Something]` is actually a shortcut for `Union[Something, None]`, they are equivalent.
+
+This also means that in Python 3.10, you can use `Something | None`:
+
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="1 4"
+ {!> ../../../docs_src/python_types/tutorial009.py!}
+ ```
+
+=== "Python 3.6 and above - alternative"
+
+ ```Python hl_lines="1 4"
+ {!> ../../../docs_src/python_types/tutorial009b.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="1"
+ {!> ../../../docs_src/python_types/tutorial009_py310.py!}
+ ```
+
#### Generic types
-These types that take type parameters in square brackets, like:
+These types that take type parameters in square brackets are called **Generic types** or **Generics**, for example:
-* `List`
-* `Tuple`
-* `Set`
-* `Dict`
-* `Optional`
-* ...and others.
+=== "Python 3.6 and above"
-are called **Generic types** or **Generics**.
+ * `List`
+ * `Tuple`
+ * `Set`
+ * `Dict`
+ * `Union`
+ * `Optional`
+ * ...and others.
+
+=== "Python 3.9 and above"
+
+ You can use the same builtin types as generics (with square brakets and types inside):
+
+ * `list`
+ * `tuple`
+ * `set`
+ * `dict`
+
+ And the same as with Python 3.6, from the `typing` module:
+
+ * `Union`
+ * `Optional`
+ * ...and others.
+
+=== "Python 3.10 and above"
+
+ You can use the same builtin types as generics (with square brakets and types inside):
+
+ * `list`
+ * `tuple`
+ * `set`
+ * `dict`
+
+ And the same as with Python 3.6, from the `typing` module:
+
+ * `Union`
+ * `Optional` (the same as with Python 3.6)
+ * ...and others.
+
+ In Python 3.10, as an alternative to using the generics `Union` and `Optional`, you can use the vertical bar (`|`) to declare unions of types.
### Classes as types
@@ -275,11 +395,25 @@ Then you create an instance of that class with some values and it will validate
And you get all the editor support with that resulting object.
-Taken from the official Pydantic docs:
+An example from the official Pydantic docs:
-```Python
-{!../../../docs_src/python_types/tutorial011.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python
+ {!> ../../../docs_src/python_types/tutorial011.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python
+ {!> ../../../docs_src/python_types/tutorial011_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python
+ {!> ../../../docs_src/python_types/tutorial011_py310.py!}
+ ```
!!! info
To learn more about Pydantic, check its docs.
diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md
index 32df4ed0..4d8bacf4 100644
--- a/docs/en/docs/release-notes.md
+++ b/docs/en/docs/release-notes.md
@@ -2,12 +2,527 @@
## Latest Changes
-* 👥 Update FastAPI People. PR [#3450](https://github.com/tiangolo/fastapi/pull/3450) by [@github-actions[bot]](https://github.com/apps/github-actions).
+
+## 0.77.1
+
+### Upgrades
+
+* ⬆ Upgrade Starlette from 0.19.0 to 0.19.1. PR [#4819](https://github.com/tiangolo/fastapi/pull/4819) by [@Kludex](https://github.com/Kludex).
+
+### Docs
+
+* 📝 Add link to german article: REST-API Programmieren mittels Python und dem FastAPI Modul. PR [#4624](https://github.com/tiangolo/fastapi/pull/4624) by [@fschuermeyer](https://github.com/fschuermeyer).
+* 📝 Add external link: PyCharm Guide to FastAPI. PR [#4512](https://github.com/tiangolo/fastapi/pull/4512) by [@mukulmantosh](https://github.com/mukulmantosh).
+* 📝 Add external link to article: Building an API with FastAPI and Supabase and Deploying on Deta. PR [#4440](https://github.com/tiangolo/fastapi/pull/4440) by [@aUnicornDev](https://github.com/aUnicornDev).
+* ✏ Fix small typo in `docs/en/docs/tutorial/security/first-steps.md`. PR [#4515](https://github.com/tiangolo/fastapi/pull/4515) by [@KikoIlievski](https://github.com/KikoIlievski).
+
+### Translations
+
+* 🌐 Add Polish translation for `docs/pl/docs/tutorial/index.md`. PR [#4516](https://github.com/tiangolo/fastapi/pull/4516) by [@MKaczkow](https://github.com/MKaczkow).
+* ✏ Fix typo in deployment. PR [#4629](https://github.com/tiangolo/fastapi/pull/4629) by [@raisulislam541](https://github.com/raisulislam541).
+* 🌐 Add Portuguese translation for `docs/pt/docs/help-fastapi.md`. PR [#4583](https://github.com/tiangolo/fastapi/pull/4583) by [@mateusjs](https://github.com/mateusjs).
+
+### Internal
+
+* 🔧 Add notifications in issue for Uzbek translations. PR [#4884](https://github.com/tiangolo/fastapi/pull/4884) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.77.0
+
+### Upgrades
+
+* ⬆ Upgrade Starlette from 0.18.0 to 0.19.0. PR [#4488](https://github.com/tiangolo/fastapi/pull/4488) by [@Kludex](https://github.com/Kludex).
+ * When creating an explicit `JSONResponse` the `content` argument is now required.
+
+### Docs
+
+* 📝 Add external link to article: Seamless FastAPI Configuration with ConfZ. PR [#4414](https://github.com/tiangolo/fastapi/pull/4414) by [@silvanmelchior](https://github.com/silvanmelchior).
+* 📝 Add external link to article: 5 Advanced Features of FastAPI You Should Try. PR [#4436](https://github.com/tiangolo/fastapi/pull/4436) by [@kaustubhgupta](https://github.com/kaustubhgupta).
+* ✏ Reword to improve legibility of docs about `TestClient`. PR [#4389](https://github.com/tiangolo/fastapi/pull/4389) by [@rgilton](https://github.com/rgilton).
+* 📝 Add external link to blog post about Kafka, FastAPI, and Ably. PR [#4044](https://github.com/tiangolo/fastapi/pull/4044) by [@Ugbot](https://github.com/Ugbot).
+* ✏ Fix typo in `docs/en/docs/tutorial/sql-databases.md`. PR [#4875](https://github.com/tiangolo/fastapi/pull/4875) by [@wpyoga](https://github.com/wpyoga).
+* ✏ Fix typo in `docs/en/docs/async.md`. PR [#4726](https://github.com/tiangolo/fastapi/pull/4726) by [@Prezu](https://github.com/Prezu).
+
+### Translations
+
+* 🌐 Update source example highlights for `docs/zh/docs/tutorial/query-params-str-validations.md`. PR [#4237](https://github.com/tiangolo/fastapi/pull/4237) by [@caimaoy](https://github.com/caimaoy).
+* 🌐 Remove translation docs references to aiofiles as it's no longer needed since AnyIO. PR [#3594](https://github.com/tiangolo/fastapi/pull/3594) by [@alonme](https://github.com/alonme).
+* ✏ 🌐 Fix typo in Portuguese translation for `docs/pt/docs/tutorial/path-params.md`. PR [#4722](https://github.com/tiangolo/fastapi/pull/4722) by [@CleoMenezesJr](https://github.com/CleoMenezesJr).
+* 🌐 Fix live docs server for translations for some languages. PR [#4729](https://github.com/tiangolo/fastapi/pull/4729) by [@wakabame](https://github.com/wakabame).
+* 🌐 Add Portuguese translation for `docs/pt/docs/tutorial/cookie-params.md`. PR [#4112](https://github.com/tiangolo/fastapi/pull/4112) by [@lbmendes](https://github.com/lbmendes).
+* 🌐 Fix French translation for `docs/tutorial/body.md`. PR [#4332](https://github.com/tiangolo/fastapi/pull/4332) by [@Smlep](https://github.com/Smlep).
+* 🌐 Add Japanese translation for `docs/ja/docs/advanced/conditional-openapi.md`. PR [#2631](https://github.com/tiangolo/fastapi/pull/2631) by [@sh0nk](https://github.com/sh0nk).
+* 🌐 Fix Japanese translation of `docs/ja/docs/tutorial/body.md`. PR [#3062](https://github.com/tiangolo/fastapi/pull/3062) by [@a-takahashi223](https://github.com/a-takahashi223).
+* 🌐 Add Portuguese translation for `docs/pt/docs/tutorial/background-tasks.md`. PR [#2170](https://github.com/tiangolo/fastapi/pull/2170) by [@izaguerreiro](https://github.com/izaguerreiro).
+* 🌐 Add Portuguese translation for `docs/deployment/deta.md`. PR [#4442](https://github.com/tiangolo/fastapi/pull/4442) by [@lsglucas](https://github.com/lsglucas).
+* 🌐 Add Russian translation for `docs/async.md`. PR [#4036](https://github.com/tiangolo/fastapi/pull/4036) by [@Winand](https://github.com/Winand).
+* 🌐 Add Portuguese translation for `docs/tutorial/body.md`. PR [#3960](https://github.com/tiangolo/fastapi/pull/3960) by [@leandrodesouzadev](https://github.com/leandrodesouzadev).
+* 🌐 Add Portuguese translation of `tutorial/extra-data-types.md`. PR [#4077](https://github.com/tiangolo/fastapi/pull/4077) by [@luccasmmg](https://github.com/luccasmmg).
+* 🌐 Update German translation for `docs/features.md`. PR [#3905](https://github.com/tiangolo/fastapi/pull/3905) by [@jomue](https://github.com/jomue).
+
+## 0.76.0
+
+### Upgrades
+
+* ⬆ Upgrade Starlette from 0.17.1 to 0.18.0. PR [#4483](https://github.com/tiangolo/fastapi/pull/4483) by [@Kludex](https://github.com/Kludex).
+
+### Internal
+
+* 👥 Update FastAPI People. PR [#4847](https://github.com/tiangolo/fastapi/pull/4847) by [@github-actions[bot]](https://github.com/apps/github-actions).
+* 🔧 Add Budget Insight sponsor. PR [#4824](https://github.com/tiangolo/fastapi/pull/4824) by [@tiangolo](https://github.com/tiangolo).
+* 🍱 Update sponsor, ExoFlare badge. PR [#4822](https://github.com/tiangolo/fastapi/pull/4822) by [@tiangolo](https://github.com/tiangolo).
+* 🔧 Update sponsors, enable Dropbase again, update TalkPython link. PR [#4821](https://github.com/tiangolo/fastapi/pull/4821) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.75.2
+
+This release includes upgrades to third-party packages that handle security issues. Although there's a chance these issues don't affect you in particular, please upgrade as soon as possible.
+
+### Fixes
+
+* ✅ Fix new/recent tests with new fixed `ValidationError` JSON Schema. PR [#4806](https://github.com/tiangolo/fastapi/pull/4806) by [@tiangolo](https://github.com/tiangolo).
+* 🐛 Fix JSON Schema for `ValidationError` at field `loc`. PR [#3810](https://github.com/tiangolo/fastapi/pull/3810) by [@dconathan](https://github.com/dconathan).
+* 🐛 Fix support for prefix on APIRouter WebSockets. PR [#2640](https://github.com/tiangolo/fastapi/pull/2640) by [@Kludex](https://github.com/Kludex).
+
+### Upgrades
+
+* ⬆️ Update ujson ranges for CVE-2021-45958. PR [#4804](https://github.com/tiangolo/fastapi/pull/4804) by [@tiangolo](https://github.com/tiangolo).
+* ⬆️ Upgrade dependencies upper range for extras "all". PR [#4803](https://github.com/tiangolo/fastapi/pull/4803) by [@tiangolo](https://github.com/tiangolo).
+* ⬆ Upgrade Swagger UI - swagger-ui-dist@4. This handles a security issue in Swagger UI itself where it could be possible to inject HTML into Swagger UI. Please upgrade as soon as you can, in particular if you expose your Swagger UI (`/docs`) publicly to non-expert users. PR [#4347](https://github.com/tiangolo/fastapi/pull/4347) by [@RAlanWright](https://github.com/RAlanWright).
+
+### Internal
+
+* 🔧 Update sponsors, add: ExoFlare, Ines Course; remove: Dropbase, Vim.so, Calmcode; update: Striveworks, TalkPython and TestDriven.io. PR [#4805](https://github.com/tiangolo/fastapi/pull/4805) by [@tiangolo](https://github.com/tiangolo).
+* ⬆️ Upgrade Codecov GitHub Action. PR [#4801](https://github.com/tiangolo/fastapi/pull/4801) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.75.1
+
+### Translations
+
+* 🌐 Start Dutch translations. PR [#4703](https://github.com/tiangolo/fastapi/pull/4703) by [@tiangolo](https://github.com/tiangolo).
+* 🌐 Start Persian/Farsi translations. PR [#4243](https://github.com/tiangolo/fastapi/pull/4243) by [@aminalaee](https://github.com/aminalaee).
+* ✏ Reword sentence about handling errors. PR [#1993](https://github.com/tiangolo/fastapi/pull/1993) by [@khuhroproeza](https://github.com/khuhroproeza).
+
+### Internal
+
+* 👥 Update FastAPI People. PR [#4752](https://github.com/tiangolo/fastapi/pull/4752) by [@github-actions[bot]](https://github.com/apps/github-actions).
+* ➖ Temporarily remove typer-cli from dependencies and upgrade Black to unblock Pydantic CI. PR [#4754](https://github.com/tiangolo/fastapi/pull/4754) by [@tiangolo](https://github.com/tiangolo).
+* 🔧 Add configuration to notify Dutch translations. PR [#4702](https://github.com/tiangolo/fastapi/pull/4702) by [@tiangolo](https://github.com/tiangolo).
+* 👥 Update FastAPI People. PR [#4699](https://github.com/tiangolo/fastapi/pull/4699) by [@github-actions[bot]](https://github.com/apps/github-actions).
+* 🐛 Fix FastAPI People generation to include missing file in commit. PR [#4695](https://github.com/tiangolo/fastapi/pull/4695) by [@tiangolo](https://github.com/tiangolo).
+* 🔧 Update Classiq sponsor links. PR [#4688](https://github.com/tiangolo/fastapi/pull/4688) by [@tiangolo](https://github.com/tiangolo).
+* 🔧 Add Classiq sponsor. PR [#4671](https://github.com/tiangolo/fastapi/pull/4671) by [@tiangolo](https://github.com/tiangolo).
+* 📝 Add Jina's QA Bot to the docs to help people that want to ask quick questions. PR [#4655](https://github.com/tiangolo/fastapi/pull/4655) by [@tiangolo](https://github.com/tiangolo) based on original PR [#4626](https://github.com/tiangolo/fastapi/pull/4626) by [@hanxiao](https://github.com/hanxiao).
+
+## 0.75.0
+
+### Features
+
+* ✨ Add support for custom `generate_unique_id_function` and docs for generating clients. New docs: [Advanced - Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/). PR [#4650](https://github.com/tiangolo/fastapi/pull/4650) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.74.1
+
+### Features
+
+* ✨ Include route in scope to allow middleware and other tools to extract its information. PR [#4603](https://github.com/tiangolo/fastapi/pull/4603) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.74.0
+
+### Breaking Changes
+
+* ✨ Update internal `AsyncExitStack` to fix context for dependencies with `yield`. PR [#4575](https://github.com/tiangolo/fastapi/pull/4575) by [@tiangolo](https://github.com/tiangolo).
+
+Dependencies with `yield` can now catch `HTTPException` and custom exceptions. For example:
+
+```Python
+async def get_database():
+ with Session() as session:
+ try:
+ yield session
+ except HTTPException:
+ session.rollback()
+ raise
+ finally:
+ session.close()
+```
+
+After the dependency with `yield` handles the exception (or not) the exception is raised again. So that any exception handlers can catch it, or ultimately the default internal `ServerErrorMiddleware`.
+
+If you depended on exceptions not being received by dependencies with `yield`, and receiving an exception breaks the code after `yield`, you can use a block with `try` and `finally`:
+
+```Python
+async def do_something():
+ try:
+ yield something
+ finally:
+ some_cleanup()
+```
+
+...that way the `finally` block is run regardless of any exception that might happen.
+
+### Features
+
+* The same PR [#4575](https://github.com/tiangolo/fastapi/pull/4575) from above also fixes the `contextvars` context for the code before and after `yield`. This was the main objective of that PR.
+
+This means that now, if you set a value in a context variable before `yield`, the value would still be available after `yield` (as you would intuitively expect). And it also means that you can reset the context variable with a token afterwards.
+
+For example, this works correctly now:
+
+```Python
+from contextvars import ContextVar
+from typing import Any, Dict, Optional
+
+
+legacy_request_state_context_var: ContextVar[Optional[Dict[str, Any]]] = ContextVar(
+ "legacy_request_state_context_var", default=None
+)
+
+async def set_up_request_state_dependency():
+ request_state = {"user": "deadpond"}
+ contextvar_token = legacy_request_state_context_var.set(request_state)
+ yield request_state
+ legacy_request_state_context_var.reset(contextvar_token)
+```
+
+...before this change it would raise an error when resetting the context variable, because the `contextvars` context was different, because of the way it was implemented.
+
+**Note**: You probably don't need `contextvars`, and you should probably avoid using them. But they are powerful and useful in some advanced scenarios, for example, migrating from code that used Flask's `g` semi-global variable.
+
+**Technical Details**: If you want to know more of the technical details you can check out the PR description [#4575](https://github.com/tiangolo/fastapi/pull/4575).
+
+### Internal
+
+* 🔧 Add Striveworks sponsor. PR [#4596](https://github.com/tiangolo/fastapi/pull/4596) by [@tiangolo](https://github.com/tiangolo).
+* 💚 Only build docs on push when on master to avoid duplicate runs from PRs. PR [#4564](https://github.com/tiangolo/fastapi/pull/4564) by [@tiangolo](https://github.com/tiangolo).
+* 👥 Update FastAPI People. PR [#4502](https://github.com/tiangolo/fastapi/pull/4502) by [@github-actions[bot]](https://github.com/apps/github-actions).
+
+## 0.73.0
+
+### Features
+
+* ✨ Add support for declaring `UploadFile` parameters without explicit `File()`. PR [#4469](https://github.com/tiangolo/fastapi/pull/4469) by [@tiangolo](https://github.com/tiangolo). New docs: [Request Files - File Parameters with UploadFile](https://fastapi.tiangolo.com/tutorial/request-files/#file-parameters-with-uploadfile).
+* ✨ Add support for tags with Enums. PR [#4468](https://github.com/tiangolo/fastapi/pull/4468) by [@tiangolo](https://github.com/tiangolo). New docs: [Path Operation Configuration - Tags with Enums](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags-with-enums).
+* ✨ Allow hiding from OpenAPI (and Swagger UI) `Query`, `Cookie`, `Header`, and `Path` parameters. PR [#3144](https://github.com/tiangolo/fastapi/pull/3144) by [@astraldawn](https://github.com/astraldawn). New docs: [Query Parameters and String Validations - Exclude from OpenAPI](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi).
+
+### Docs
+
+* 📝 Tweak and improve docs for Request Files. PR [#4470](https://github.com/tiangolo/fastapi/pull/4470) by [@tiangolo](https://github.com/tiangolo).
+
+### Fixes
+
+* 🐛 Fix bug preventing to use OpenAPI when using tuples. PR [#3874](https://github.com/tiangolo/fastapi/pull/3874) by [@victorbenichoux](https://github.com/victorbenichoux).
+* 🐛 Prefer custom encoder over defaults if specified in `jsonable_encoder`. PR [#2061](https://github.com/tiangolo/fastapi/pull/2061) by [@viveksunder](https://github.com/viveksunder).
+ * 💚 Duplicate PR to trigger CI. PR [#4467](https://github.com/tiangolo/fastapi/pull/4467) by [@tiangolo](https://github.com/tiangolo).
+
+### Internal
+
+* 🐛 Fix docs dependencies cache, to get the latest Material for MkDocs. PR [#4466](https://github.com/tiangolo/fastapi/pull/4466) by [@tiangolo](https://github.com/tiangolo).
+* 🔧 Add sponsor Dropbase. PR [#4465](https://github.com/tiangolo/fastapi/pull/4465) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.72.0
+
+### Features
+
+* ✨ Enable configuring Swagger UI parameters. Original PR [#2568](https://github.com/tiangolo/fastapi/pull/2568) by [@jmriebold](https://github.com/jmriebold). Here are the new docs: [Configuring Swagger UI](https://fastapi.tiangolo.com/advanced/extending-openapi/#configuring-swagger-ui).
+
+### Docs
+
+* 📝 Update Python Types docs, add missing 3.6 / 3.9 example. PR [#4434](https://github.com/tiangolo/fastapi/pull/4434) by [@tiangolo](https://github.com/tiangolo).
+
+### Translations
+
+* 🌐 Update Chinese translation for `docs/help-fastapi.md`. PR [#3847](https://github.com/tiangolo/fastapi/pull/3847) by [@jaystone776](https://github.com/jaystone776).
+* 🌐 Fix Korean translation for `docs/ko/docs/index.md`. PR [#4195](https://github.com/tiangolo/fastapi/pull/4195) by [@kty4119](https://github.com/kty4119).
+* 🌐 Add Polish translation for `docs/pl/docs/index.md`. PR [#4245](https://github.com/tiangolo/fastapi/pull/4245) by [@MicroPanda123](https://github.com/MicroPanda123).
+* 🌐 Add Chinese translation for `docs\tutorial\path-operation-configuration.md`. PR [#3312](https://github.com/tiangolo/fastapi/pull/3312) by [@jaystone776](https://github.com/jaystone776).
+
+### Internal
+
+* 🔧 Enable MkDocs Material Insiders' `content.tabs.link`. PR [#4399](https://github.com/tiangolo/fastapi/pull/4399) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.71.0
+
+### Features
+
+* ✨ Add docs and tests for Python 3.9 and Python 3.10. PR [#3712](https://github.com/tiangolo/fastapi/pull/3712) by [@tiangolo](https://github.com/tiangolo).
+ * You can start with [Python Types Intro](https://fastapi.tiangolo.com/python-types/), it explains what changes between different Python versions, in Python 3.9 and in Python 3.10.
+ * All the FastAPI docs are updated. Each code example in the docs that could use different syntax in Python 3.9 or Python 3.10 now has all the alternatives in tabs.
+* ⬆️ Upgrade Starlette to 0.17.1. PR [#4145](https://github.com/tiangolo/fastapi/pull/4145) by [@simondale00](https://github.com/simondale00).
+
+### Internal
+
+* 👥 Update FastAPI People. PR [#4354](https://github.com/tiangolo/fastapi/pull/4354) by [@github-actions[bot]](https://github.com/apps/github-actions).
+* 🔧 Add FastAPI Trove Classifier for PyPI as now there's one 🤷😁. PR [#4386](https://github.com/tiangolo/fastapi/pull/4386) by [@tiangolo](https://github.com/tiangolo).
+* ⬆ Upgrade MkDocs Material and configs. PR [#4385](https://github.com/tiangolo/fastapi/pull/4385) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.70.1
+
+There's nothing interesting in this particular FastAPI release. It is mainly to enable/unblock the release of the next version of Pydantic that comes packed with features and improvements. 🤩
+
+### Fixes
+
+* 🐛 Fix JSON Schema for dataclasses, supporting the fixes in Pydantic 1.9. PR [#4272](https://github.com/tiangolo/fastapi/pull/4272) by [@PrettyWood](https://github.com/PrettyWood).
+
+### Translations
+
+* 🌐 Add Korean translation for `docs/tutorial/request-forms-and-files.md`. PR [#3744](https://github.com/tiangolo/fastapi/pull/3744) by [@NinaHwang](https://github.com/NinaHwang).
+* 🌐 Add Korean translation for `docs/tutorial/request-files.md`. PR [#3743](https://github.com/tiangolo/fastapi/pull/3743) by [@NinaHwang](https://github.com/NinaHwang).
+* 🌐 Add portuguese translation for `docs/tutorial/query-params-str-validations.md`. PR [#3965](https://github.com/tiangolo/fastapi/pull/3965) by [@leandrodesouzadev](https://github.com/leandrodesouzadev).
+* 🌐 Add Korean translation for `docs/tutorial/response-status-code.md`. PR [#3742](https://github.com/tiangolo/fastapi/pull/3742) by [@NinaHwang](https://github.com/NinaHwang).
+* 🌐 Add Korean translation for Tutorial - JSON Compatible Encoder. PR [#3152](https://github.com/tiangolo/fastapi/pull/3152) by [@NEONKID](https://github.com/NEONKID).
+* 🌐 Add Korean translation for Tutorial - Path Parameters and Numeric Validations. PR [#2432](https://github.com/tiangolo/fastapi/pull/2432) by [@hard-coders](https://github.com/hard-coders).
+* 🌐 Add Korean translation for `docs/ko/docs/deployment/versions.md`. PR [#4121](https://github.com/tiangolo/fastapi/pull/4121) by [@DevDae](https://github.com/DevDae).
+* 🌐 Fix Korean translation for `docs/ko/docs/tutorial/index.md`. PR [#4193](https://github.com/tiangolo/fastapi/pull/4193) by [@kimjaeyoonn](https://github.com/kimjaeyoonn).
+* 🔧 Add CryptAPI sponsor. PR [#4264](https://github.com/tiangolo/fastapi/pull/4264) by [@tiangolo](https://github.com/tiangolo).
+* 📝 Update `docs/tutorial/dependencies/classes-as-dependencies`: Add type of query parameters in a description of `Classes as dependencies`. PR [#4015](https://github.com/tiangolo/fastapi/pull/4015) by [@0417taehyun](https://github.com/0417taehyun).
+* 🌐 Add French translation for Tutorial - First steps. PR [#3455](https://github.com/tiangolo/fastapi/pull/3455) by [@Smlep](https://github.com/Smlep).
+* 🌐 Add French translation for `docs/tutorial/path-params.md`. PR [#3548](https://github.com/tiangolo/fastapi/pull/3548) by [@Smlep](https://github.com/Smlep).
+* 🌐 Add French translation for `docs/tutorial/query-params.md`. PR [#3556](https://github.com/tiangolo/fastapi/pull/3556) by [@Smlep](https://github.com/Smlep).
+* 🌐 Add Turkish translation for `docs/python-types.md`. PR [#3926](https://github.com/tiangolo/fastapi/pull/3926) by [@BilalAlpaslan](https://github.com/BilalAlpaslan).
+
+### Internal
+
+* 👥 Update FastAPI People. PR [#4274](https://github.com/tiangolo/fastapi/pull/4274) by [@github-actions[bot]](https://github.com/apps/github-actions).
+
+## 0.70.0
+
+This release just upgrades Starlette to the latest version, `0.16.0`, which includes several bug fixes and some small breaking changes.
+
+These last **three consecutive releases** are independent so that you can **migrate gradually**:
+
+* First to FastAPI `0.68.2`, with no breaking changes, but upgrading all the sub-dependencies.
+* Next to FastAPI `0.69.0`, which upgrades Starlette to `0.15.0`, with AnyIO support, and a higher chance of having breaking changes in your code.
+* Finally to FastAPI `0.70.0`, just upgrading Starlette to the latest version `0.16.0` with additional bug fixes.
+
+This way, in case there was a breaking change for your code in one of the releases, you can still benefit from the previous upgrades. ✨
+
+### Breaking Changes - Upgrade
+
+* ⬆️ Upgrade Starlette to 0.16.0. PR [#4016](https://github.com/tiangolo/fastapi/pull/4016) by [@tiangolo](https://github.com/tiangolo).
+
+Also upgrades the ranges of optional dependencies:
+
+* `"jinja2 >=2.11.2,<4.0.0"`
+* `"itsdangerous >=1.1.0,<3.0.0"`
+
+## 0.69.0
+
+### Breaking Changes - Upgrade
+
+This release adds support for [Trio](https://trio.readthedocs.io/en/stable/). ✨
+
+It upgrades the version of Starlette to `0.15.0`, now based on [AnyIO](https://anyio.readthedocs.io/en/stable/), and the internal async components in **FastAPI** are now based on AnyIO as well, making it compatible with both **asyncio** and **Trio**.
+
+You can read the docs about running [FastAPI with Trio using Hypercorn](https://fastapi.tiangolo.com/deployment/manually/#hypercorn-with-trio).
+
+This release also removes `graphene` as an optional dependency for GraphQL. If you need to work with GraphQL, the recommended library now is [Strawberry](https://strawberry.rocks/). You can read the new [FastAPI with GraphQL docs](https://fastapi.tiangolo.com/advanced/graphql/).
+
+### Features
+
+* ✨ Add support for Trio via AnyIO, upgrading Starlette to `0.15.0`. PR [#3372](https://github.com/tiangolo/fastapi/pull/3372) by [@graingert](https://github.com/graingert).
+* ➖ Remove `graphene` as an optional dependency. PR [#4007](https://github.com/tiangolo/fastapi/pull/4007) by [@tiangolo](https://github.com/tiangolo).
+
+### Docs
+
+* 📝 Add docs for using Trio with Hypercorn. PR [#4014](https://github.com/tiangolo/fastapi/pull/4014) by [@tiangolo](https://github.com/tiangolo).
+* ✏ Fix typos in Deployment Guide. PR [#3975](https://github.com/tiangolo/fastapi/pull/3975) by [@ghandic](https://github.com/ghandic).
+* 📝 Update docs with pip install calls when using extras with brackets, use quotes for compatibility with Zsh. PR [#3131](https://github.com/tiangolo/fastapi/pull/3131) by [@tomwei7](https://github.com/tomwei7).
+* 📝 Add external link to article: Deploying ML Models as API Using FastAPI and Heroku. PR [#3904](https://github.com/tiangolo/fastapi/pull/3904) by [@kaustubhgupta](https://github.com/kaustubhgupta).
+* ✏ Fix typo in file paths in `docs/en/docs/contributing.md`. PR [#3752](https://github.com/tiangolo/fastapi/pull/3752) by [@NinaHwang](https://github.com/NinaHwang).
+* ✏ Fix a typo in `docs/en/docs/advanced/path-operation-advanced-configuration.md` and `docs/en/docs/release-notes.md`. PR [#3750](https://github.com/tiangolo/fastapi/pull/3750) by [@saintmalik](https://github.com/saintmalik).
+* ✏️ Add a missing comma in the security tutorial. PR [#3564](https://github.com/tiangolo/fastapi/pull/3564) by [@jalvaradosegura](https://github.com/jalvaradosegura).
+* ✏ Fix typo in `docs/en/docs/help-fastapi.md`. PR [#3760](https://github.com/tiangolo/fastapi/pull/3760) by [@jaystone776](https://github.com/jaystone776).
+* ✏ Fix typo about file path in `docs/en/docs/tutorial/bigger-applications.md`. PR [#3285](https://github.com/tiangolo/fastapi/pull/3285) by [@HolyDorus](https://github.com/HolyDorus).
+* ✏ Re-word to clarify test client in `docs/en/docs/tutorial/testing.md`. PR [#3382](https://github.com/tiangolo/fastapi/pull/3382) by [@Bharat123rox](https://github.com/Bharat123rox).
+* 📝 Fix incorrect highlighted code. PR [#3325](https://github.com/tiangolo/fastapi/pull/3325) by [@paxcodes](https://github.com/paxcodes).
+* 📝 Add external link to article: How-to deploy FastAPI app to Heroku. PR [#3241](https://github.com/tiangolo/fastapi/pull/3241) by [@Jarmos-san](https://github.com/Jarmos-san).
+* ✏ Fix typo (mistranslation) in `docs/en/docs/advanced/templates.md`. PR [#3211](https://github.com/tiangolo/fastapi/pull/3211) by [@oerpli](https://github.com/oerpli).
+* 📝 Remove note about (now supported) feature from Swagger UI in `docs/en/docs/tutorial/request-files.md`. PR [#2803](https://github.com/tiangolo/fastapi/pull/2803) by [@gsganden](https://github.com/gsganden).
+* ✏ Fix typo re-word in `docs/tutorial/handling-errors.md`. PR [#2700](https://github.com/tiangolo/fastapi/pull/2700) by [@graue70](https://github.com/graue70).
+
+### Translations
+
+* 🌐 Initialize Azerbaijani translations. PR [#3941](https://github.com/tiangolo/fastapi/pull/3941) by [@madatbay](https://github.com/madatbay).
+* 🌐 Add Turkish translation for `docs/fastapi-people.md`. PR [#3848](https://github.com/tiangolo/fastapi/pull/3848) by [@BilalAlpaslan](https://github.com/BilalAlpaslan).
+
+### Internal
+
+* 📝 Add supported Python versions badge. PR [#2794](https://github.com/tiangolo/fastapi/pull/2794) by [@hramezani](https://github.com/hramezani).
+* ✏ Fix link in Japanese docs for `docs/ja/docs/deployment/docker.md`. PR [#3245](https://github.com/tiangolo/fastapi/pull/3245) by [@utamori](https://github.com/utamori).
+* 🔧 Correct DeprecationWarning config and comment in pytest settings. PR [#4008](https://github.com/tiangolo/fastapi/pull/4008) by [@graingert](https://github.com/graingert).
+* 🔧 Swap light/dark theme button icon. PR [#3246](https://github.com/tiangolo/fastapi/pull/3246) by [@eddsalkield](https://github.com/eddsalkield).
+* 🔧 Lint only in Python 3.7 and above. PR [#4006](https://github.com/tiangolo/fastapi/pull/4006) by [@tiangolo](https://github.com/tiangolo).
+* 🔧 Add GitHub Action notify-translations config for Azerbaijani. PR [#3995](https://github.com/tiangolo/fastapi/pull/3995) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.68.2
+
+This release has **no breaking changes**. 🎉
+
+It upgrades the version ranges of sub-dependencies to allow applications using FastAPI to easily upgrade them.
+
+Soon there will be a new FastAPI release upgrading Starlette to take advantage of recent improvements, but as that has a higher chance of having breaking changes, it will be in a separate release.
+
+### Features
+
+* ⬆Increase supported version of aiofiles to suppress warnings. PR [#2899](https://github.com/tiangolo/fastapi/pull/2899) by [@SnkSynthesis](https://github.com/SnkSynthesis).
+* ➖ Do not require backports in Python >= 3.7. PR [#1880](https://github.com/tiangolo/fastapi/pull/1880) by [@FFY00](https://github.com/FFY00).
+* ⬆ Upgrade required Python version to >= 3.6.1, needed by typing.Deque, used by Pydantic. PR [#2733](https://github.com/tiangolo/fastapi/pull/2733) by [@hukkin](https://github.com/hukkin).
+* ⬆️ Bump Uvicorn max range to 0.15.0. PR [#3345](https://github.com/tiangolo/fastapi/pull/3345) by [@Kludex](https://github.com/Kludex).
+
+### Docs
+
+* 📝 Update GraphQL docs, recommend Strawberry. PR [#3981](https://github.com/tiangolo/fastapi/pull/3981) by [@tiangolo](https://github.com/tiangolo).
+* 📝 Re-write and extend Deployment guide: Concepts, Uvicorn, Gunicorn, Docker, Containers, Kubernetes. PR [#3974](https://github.com/tiangolo/fastapi/pull/3974) by [@tiangolo](https://github.com/tiangolo).
+* 📝 Upgrade HTTPS guide with more explanations and diagrams. PR [#3950](https://github.com/tiangolo/fastapi/pull/3950) by [@tiangolo](https://github.com/tiangolo).
+
+### Translations
+
+* 🌐 Add Turkish translation for `docs/features.md`. PR [#1950](https://github.com/tiangolo/fastapi/pull/1950) by [@ycd](https://github.com/ycd).
+* 🌐 Add Turkish translation for `docs/benchmarks.md`. PR [#2729](https://github.com/tiangolo/fastapi/pull/2729) by [@Telomeraz](https://github.com/Telomeraz).
+* 🌐 Add Turkish translation for `docs/index.md`. PR [#1908](https://github.com/tiangolo/fastapi/pull/1908) by [@ycd](https://github.com/ycd).
+* 🌐 Add French translation for `docs/tutorial/body.md`. PR [#3671](https://github.com/tiangolo/fastapi/pull/3671) by [@Smlep](https://github.com/Smlep).
+* 🌐 Add French translation for `deployment/docker.md`. PR [#3694](https://github.com/tiangolo/fastapi/pull/3694) by [@rjNemo](https://github.com/rjNemo).
+* 🌐 Add Portuguese translation for `docs/tutorial/path-params.md`. PR [#3664](https://github.com/tiangolo/fastapi/pull/3664) by [@FelipeSilva93](https://github.com/FelipeSilva93).
+* 🌐 Add Portuguese translation for `docs/deployment/https.md`. PR [#3754](https://github.com/tiangolo/fastapi/pull/3754) by [@lsglucas](https://github.com/lsglucas).
+* 🌐 Add German translation for `docs/features.md`. PR [#3699](https://github.com/tiangolo/fastapi/pull/3699) by [@mawassk](https://github.com/mawassk).
+
+### Internal
+
+* ✨ Update GitHub Action: notify-translations, to avoid a race conditions. PR [#3989](https://github.com/tiangolo/fastapi/pull/3989) by [@tiangolo](https://github.com/tiangolo).
+* ⬆️ Upgrade development `autoflake`, supporting multi-line imports. PR [#3988](https://github.com/tiangolo/fastapi/pull/3988) by [@tiangolo](https://github.com/tiangolo).
+* ⬆️ Increase dependency ranges for tests and docs: pytest-cov, pytest-asyncio, black, httpx, sqlalchemy, databases, mkdocs-markdownextradata-plugin. PR [#3987](https://github.com/tiangolo/fastapi/pull/3987) by [@tiangolo](https://github.com/tiangolo).
+* 👥 Update FastAPI People. PR [#3986](https://github.com/tiangolo/fastapi/pull/3986) by [@github-actions[bot]](https://github.com/apps/github-actions).
+* 💚 Fix badges in README and main page. PR [#3979](https://github.com/tiangolo/fastapi/pull/3979) by [@ghandic](https://github.com/ghandic).
+* ⬆ Upgrade internal testing dependencies: mypy to version 0.910, add newly needed type packages. PR [#3350](https://github.com/tiangolo/fastapi/pull/3350) by [@ArcLightSlavik](https://github.com/ArcLightSlavik).
+* ✨ Add Deepset Sponsorship. PR [#3976](https://github.com/tiangolo/fastapi/pull/3976) by [@tiangolo](https://github.com/tiangolo).
+* 🎨 Tweak CSS styles for shell animations. PR [#3888](https://github.com/tiangolo/fastapi/pull/3888) by [@tiangolo](https://github.com/tiangolo).
+* 🔧 Add new Sponsor Calmcode.io. PR [#3777](https://github.com/tiangolo/fastapi/pull/3777) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.68.1
+
+* ✨ Add support for `read_with_orm_mode`, to support [SQLModel](https://sqlmodel.tiangolo.com/) relationship attributes. PR [#3757](https://github.com/tiangolo/fastapi/pull/3757) by [@tiangolo](https://github.com/tiangolo).
+
+### Translations
+
+* 🌐 Add Portuguese translation of `docs/fastapi-people.md`. PR [#3461](https://github.com/tiangolo/fastapi/pull/3461) by [@ComicShrimp](https://github.com/ComicShrimp).
+* 🌐 Add Chinese translation for `docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md`. PR [#3492](https://github.com/tiangolo/fastapi/pull/3492) by [@jaystone776](https://github.com/jaystone776).
+* 🔧 Add new Translation tracking issues for German and Indonesian. PR [#3718](https://github.com/tiangolo/fastapi/pull/3718) by [@tiangolo](https://github.com/tiangolo).
+* 🌐 Add Chinese translation for `docs/tutorial/dependencies/sub-dependencies.md`. PR [#3491](https://github.com/tiangolo/fastapi/pull/3491) by [@jaystone776](https://github.com/jaystone776).
+* 🌐 Add Portuguese translation for `docs/advanced/index.md`. PR [#3460](https://github.com/tiangolo/fastapi/pull/3460) by [@ComicShrimp](https://github.com/ComicShrimp).
+* 🌐 Portuguese translation of `docs/async.md`. PR [#1330](https://github.com/tiangolo/fastapi/pull/1330) by [@Serrones](https://github.com/Serrones).
+* 🌐 Add French translation for `docs/async.md`. PR [#3416](https://github.com/tiangolo/fastapi/pull/3416) by [@Smlep](https://github.com/Smlep).
+
+### Internal
+
+* ✨ Add GitHub Action: Notify Translations. PR [#3715](https://github.com/tiangolo/fastapi/pull/3715) by [@tiangolo](https://github.com/tiangolo).
+* ✨ Update computation of FastAPI People and sponsors. PR [#3714](https://github.com/tiangolo/fastapi/pull/3714) by [@tiangolo](https://github.com/tiangolo).
+* ✨ Enable recent Material for MkDocs Insiders features. PR [#3710](https://github.com/tiangolo/fastapi/pull/3710) by [@tiangolo](https://github.com/tiangolo).
+* 🔥 Remove/clean extra imports from examples in docs for features. PR [#3709](https://github.com/tiangolo/fastapi/pull/3709) by [@tiangolo](https://github.com/tiangolo).
+* ➕ Update docs library to include sources in Markdown. PR [#3648](https://github.com/tiangolo/fastapi/pull/3648) by [@tiangolo](https://github.com/tiangolo).
+* ⬆ Enable tests for Python 3.9. PR [#2298](https://github.com/tiangolo/fastapi/pull/2298) by [@Kludex](https://github.com/Kludex).
+* 👥 Update FastAPI People. PR [#3642](https://github.com/tiangolo/fastapi/pull/3642) by [@github-actions[bot]](https://github.com/apps/github-actions).
+
+## 0.68.0
+
+### Features
+
+* ✨ Add support for extensions and updates to the OpenAPI schema in each *path operation*. New docs: [FastAPI Path Operation Advanced Configuration - OpenAPI Extra](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#openapi-extra). Initial PR [#1922](https://github.com/tiangolo/fastapi/pull/1922) by [@edouardlp](https://github.com/edouardlp).
+* ✨ Add additional OpenAPI metadata parameters to `FastAPI` class, shown on the automatic API docs UI. New docs: [Metadata and Docs URLs](https://fastapi.tiangolo.com/tutorial/metadata/). Initial PR [#1812](https://github.com/tiangolo/fastapi/pull/1812) by [@dkreeft](https://github.com/dkreeft).
+* ✨ Add `description` parameter to all the security scheme classes, e.g. `APIKeyQuery(name="key", description="A very cool API key")`. PR [#1757](https://github.com/tiangolo/fastapi/pull/1757) by [@hylkepostma](https://github.com/hylkepostma).
+* ✨ Update OpenAPI models, supporting recursive models and extensions. PR [#3628](https://github.com/tiangolo/fastapi/pull/3628) by [@tiangolo](https://github.com/tiangolo).
+* ✨ Import and re-export data structures from Starlette, used by Request properties, on `fastapi.datastructures`. Initial PR [#1872](https://github.com/tiangolo/fastapi/pull/1872) by [@jamescurtin](https://github.com/jamescurtin).
+
+### Docs
+
+* 📝 Update docs about async and response-model with more gender neutral language. PR [#1869](https://github.com/tiangolo/fastapi/pull/1869) by [@Edward-Knight](https://github.com/Edward-Knight).
+
+### Translations
+
+* 🌐 Add Russian translation for `docs/python-types.md`. PR [#3039](https://github.com/tiangolo/fastapi/pull/3039) by [@dukkee](https://github.com/dukkee).
+* 🌐 Add Chinese translation for `docs/tutorial/dependencies/index.md`. PR [#3489](https://github.com/tiangolo/fastapi/pull/3489) by [@jaystone776](https://github.com/jaystone776).
+* 🌐 Add Russian translation for `docs/external-links.md`. PR [#3036](https://github.com/tiangolo/fastapi/pull/3036) by [@dukkee](https://github.com/dukkee).
+* 🌐 Add Chinese translation for `docs/tutorial/dependencies/global-dependencies.md`. PR [#3493](https://github.com/tiangolo/fastapi/pull/3493) by [@jaystone776](https://github.com/jaystone776).
+* 🌐 Add Portuguese translation for `docs/deployment/versions.md`. PR [#3618](https://github.com/tiangolo/fastapi/pull/3618) by [@lsglucas](https://github.com/lsglucas).
+* 🌐 Add Japanese translation for `docs/tutorial/security/oauth2-jwt.md`. PR [#3526](https://github.com/tiangolo/fastapi/pull/3526) by [@sattosan](https://github.com/sattosan).
+
+### Internal
+
+* ✅ Add the `docs_src` directory to test coverage and update tests. Initial PR [#1904](https://github.com/tiangolo/fastapi/pull/1904) by [@Kludex](https://github.com/Kludex).
+* 🔧 Add new GitHub templates with forms for new issues. PR [#3612](https://github.com/tiangolo/fastapi/pull/3612) by [@tiangolo](https://github.com/tiangolo).
+* 📝 Add official FastAPI Twitter to docs: [@fastapi](https://twitter.com/fastapi). PR [#3578](https://github.com/tiangolo/fastapi/pull/3578) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.67.0
+
+### Features
+
+* ✨ Add support for `dataclasses` in request bodies and `response_model`. New documentation: [Advanced User Guide - Using Dataclasses](https://fastapi.tiangolo.com/advanced/dataclasses/). PR [#3577](https://github.com/tiangolo/fastapi/pull/3577) by [@tiangolo](https://github.com/tiangolo).
+* ✨ Support `dataclasses` in responses. PR [#3576](https://github.com/tiangolo/fastapi/pull/3576) by [@tiangolo](https://github.com/tiangolo), continuation from initial PR [#2722](https://github.com/tiangolo/fastapi/pull/2722) by [@amitlissack](https://github.com/amitlissack).
+
+### Docs
+
+* 📝 Add external link: How to Create A Fake Certificate Authority And Generate TLS Certs for FastAPI. PR [#2839](https://github.com/tiangolo/fastapi/pull/2839) by [@aitoehigie](https://github.com/aitoehigie).
+* ✏ Fix code highlighted line in: `body-nested-models.md`. PR [#3463](https://github.com/tiangolo/fastapi/pull/3463) by [@jaystone776](https://github.com/jaystone776).
+* ✏ Fix typo in `body-nested-models.md`. PR [#3462](https://github.com/tiangolo/fastapi/pull/3462) by [@jaystone776](https://github.com/jaystone776).
+* ✏ Fix typo "might me" -> "might be" in `docs/en/docs/tutorial/schema-extra-example.md`. PR [#3362](https://github.com/tiangolo/fastapi/pull/3362) by [@dbrakman](https://github.com/dbrakman).
+* 📝 Add external link: Building simple E-Commerce with NuxtJS and FastAPI. PR [#3271](https://github.com/tiangolo/fastapi/pull/3271) by [@ShahriyarR](https://github.com/ShahriyarR).
+* 📝 Add external link: Serve a machine learning model using Sklearn, FastAPI and Docker. PR [#2974](https://github.com/tiangolo/fastapi/pull/2974) by [@rodrigo-arenas](https://github.com/rodrigo-arenas).
+* ✏️ Fix typo on docstring in datastructures file. PR [#2887](https://github.com/tiangolo/fastapi/pull/2887) by [@Kludex](https://github.com/Kludex).
+* 📝 Add External Link: Deploy FastAPI on Ubuntu and Serve using Caddy 2 Web Server. PR [#3572](https://github.com/tiangolo/fastapi/pull/3572) by [@tiangolo](https://github.com/tiangolo).
+* 📝 Add External Link, replaces #1898. PR [#3571](https://github.com/tiangolo/fastapi/pull/3571) by [@tiangolo](https://github.com/tiangolo).
+
+### Internal
+
+* 🎨 Improve style for sponsors, add radius border. PR [#2388](https://github.com/tiangolo/fastapi/pull/2388) by [@Kludex](https://github.com/Kludex).
+* 👷 Update GitHub Action latest-changes. PR [#3574](https://github.com/tiangolo/fastapi/pull/3574) by [@tiangolo](https://github.com/tiangolo).
+* 👷 Update GitHub Action latest-changes. PR [#3573](https://github.com/tiangolo/fastapi/pull/3573) by [@tiangolo](https://github.com/tiangolo).
+* 👷 Rename and clarify CI workflow job names. PR [#3570](https://github.com/tiangolo/fastapi/pull/3570) by [@tiangolo](https://github.com/tiangolo).
+* 👷 Update GitHub Action latest-changes, strike 2 ⚾. PR [#3575](https://github.com/tiangolo/fastapi/pull/3575) by [@tiangolo](https://github.com/tiangolo).
+* 🔧 Sort external links in docs to have the most recent at the top. PR [#3568](https://github.com/tiangolo/fastapi/pull/3568) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.66.1
+
+### Translations
+
+* 🌐 Add basic setup for German translations. PR [#3522](https://github.com/tiangolo/fastapi/pull/3522) by [@0x4Dark](https://github.com/0x4Dark).
+* 🌐 Add Portuguese translation for `docs/tutorial/security/index.md`. PR [#3507](https://github.com/tiangolo/fastapi/pull/3507) by [@oandersonmagalhaes](https://github.com/oandersonmagalhaes).
+* 🌐 Add Portuguese translation for `docs/deployment/index.md`. PR [#3337](https://github.com/tiangolo/fastapi/pull/3337) by [@lsglucas](https://github.com/lsglucas).
+
+### Internal
+
+* 🔧 Configure strict pytest options and update/refactor tests. Upgrade pytest to `>=6.2.4,<7.0.0` and pytest-cov to `>=2.12.0,<3.0.0`. Initial PR [#2790](https://github.com/tiangolo/fastapi/pull/2790) by [@graingert](https://github.com/graingert).
+* ⬆️ Upgrade python-jose dependency to `>=3.3.0,<4.0.0` for tests. PR [#3468](https://github.com/tiangolo/fastapi/pull/3468) by [@tiangolo](https://github.com/tiangolo).
+
+## 0.66.0
+
+### Features
+
+* ✨ Allow setting the `response_class` to `RedirectResponse` or `FileResponse` and returning the URL from the function. New and updated docs are in the tutorial section **Custom Response - HTML, Stream, File, others**, in [RedirectResponse](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse) and in [FileResponse](https://fastapi.tiangolo.com/advanced/custom-response/#fileresponse). PR [#3457](https://github.com/tiangolo/fastapi/pull/3457) by [@tiangolo](https://github.com/tiangolo).
+
+### Fixes
+
+* 🐛 Fix include/exclude for dicts in `jsonable_encoder`. PR [#2016](https://github.com/tiangolo/fastapi/pull/2016) by [@Rubikoid](https://github.com/Rubikoid).
+* 🐛 Support custom OpenAPI / JSON Schema fields in the generated output OpenAPI. PR [#1429](https://github.com/tiangolo/fastapi/pull/1429) by [@jmagnusson](https://github.com/jmagnusson).
+
+### Translations
+
+* 🌐 Add Spanish translation for `tutorial/query-params.md`. PR [#2243](https://github.com/tiangolo/fastapi/pull/2243) by [@mariacamilagl](https://github.com/mariacamilagl).
+* 🌐 Add Spanish translation for `advanced/response-directly.md`. PR [#1253](https://github.com/tiangolo/fastapi/pull/1253) by [@jfunez](https://github.com/jfunez).
+* 🌐 Add Spanish translation for `advanced/additional-status-codes.md`. PR [#1252](https://github.com/tiangolo/fastapi/pull/1252) by [@jfunez](https://github.com/jfunez).
+* 🌐 Add Spanish translation for `advanced/path-operation-advanced-configuration.md`. PR [#1251](https://github.com/tiangolo/fastapi/pull/1251) by [@jfunez](https://github.com/jfunez).
+
+## 0.65.3
+
+### Fixes
+
+* ♻ Assume request bodies contain JSON when no Content-Type header is provided. This fixes a breaking change introduced by [0.65.2 with PR #2118](https://github.com/tiangolo/fastapi/pull/2118). It should allow upgrading FastAPI applications with clients that send JSON data without a `Content-Type` header. And there's still protection against CSRFs. PR [#3456](https://github.com/tiangolo/fastapi/pull/3456) by [@tiangolo](https://github.com/tiangolo).
+
+### Translations
+
+* 🌐 Initialize Indonesian translations. PR [#3014](https://github.com/tiangolo/fastapi/pull/3014) by [@pace-noge](https://github.com/pace-noge).
+* 🌐 Add Spanish translation of Tutorial - Path Parameters. PR [#2219](https://github.com/tiangolo/fastapi/pull/2219) by [@mariacamilagl](https://github.com/mariacamilagl).
+* 🌐 Add Spanish translation of Tutorial - First Steps. PR [#2208](https://github.com/tiangolo/fastapi/pull/2208) by [@mariacamilagl](https://github.com/mariacamilagl).
* 🌐 Portuguese translation of Tutorial - Body - Fields. PR [#3420](https://github.com/tiangolo/fastapi/pull/3420) by [@ComicShrimp](https://github.com/ComicShrimp).
* 🌐 Add Chinese translation for Tutorial - Request - Forms - and - Files. PR [#3249](https://github.com/tiangolo/fastapi/pull/3249) by [@jaystone776](https://github.com/jaystone776).
* 🌐 Add Chinese translation for Tutorial - Handling - Errors. PR [#3299](https://github.com/tiangolo/fastapi/pull/3299) by [@jaystone776](https://github.com/jaystone776).
* 🌐 Add Chinese translation for Tutorial - Form - Data. PR [#3248](https://github.com/tiangolo/fastapi/pull/3248) by [@jaystone776](https://github.com/jaystone776).
-* 👥 Update FastAPI People. PR [#3319](https://github.com/tiangolo/fastapi/pull/3319) by [@github-actions[bot]](https://github.com/apps/github-actions).
* 🌐 Add Chinese translation for Tutorial - Body - Updates. PR [#3237](https://github.com/tiangolo/fastapi/pull/3237) by [@jaystone776](https://github.com/jaystone776).
* 🌐 Add Chinese translation for FastAPI People. PR [#3112](https://github.com/tiangolo/fastapi/pull/3112) by [@hareru](https://github.com/hareru).
* 🌐 Add French translation for Project Generation. PR [#3197](https://github.com/tiangolo/fastapi/pull/3197) by [@Smlep](https://github.com/Smlep).
@@ -16,9 +531,14 @@
* 🌐 Add French translation for Alternatives, Inspiration and Comparisons. PR [#3020](https://github.com/tiangolo/fastapi/pull/3020) by [@rjNemo](https://github.com/rjNemo).
* 🌐 Fix Chinese translation code snippet mismatch in Tutorial - Python Types Intro. PR [#2573](https://github.com/tiangolo/fastapi/pull/2573) by [@BoYanZh](https://github.com/BoYanZh).
* 🌐 Add Portuguese translation for Development Contributing. PR [#1364](https://github.com/tiangolo/fastapi/pull/1364) by [@Serrones](https://github.com/Serrones).
-* ⬆ Upgrade docs development dependency on `typer-cli` to >=0.0.12 to fix conflicts. PR [#3429](https://github.com/tiangolo/fastapi/pull/3429) by [@tiangolo](https://github.com/tiangolo).
* 🌐 Add Chinese translation for Tutorial - Request - Files. PR [#3244](https://github.com/tiangolo/fastapi/pull/3244) by [@jaystone776](https://github.com/jaystone776).
+### Internal
+
+* 👥 Update FastAPI People. PR [#3450](https://github.com/tiangolo/fastapi/pull/3450) by [@github-actions[bot]](https://github.com/apps/github-actions).
+* 👥 Update FastAPI People. PR [#3319](https://github.com/tiangolo/fastapi/pull/3319) by [@github-actions[bot]](https://github.com/apps/github-actions).
+* ⬆ Upgrade docs development dependency on `typer-cli` to >=0.0.12 to fix conflicts. PR [#3429](https://github.com/tiangolo/fastapi/pull/3429) by [@tiangolo](https://github.com/tiangolo).
+
## 0.65.2
### Security fixes
diff --git a/docs/en/docs/tutorial/background-tasks.md b/docs/en/docs/tutorial/background-tasks.md
index 6002ce07..69aeb671 100644
--- a/docs/en/docs/tutorial/background-tasks.md
+++ b/docs/en/docs/tutorial/background-tasks.md
@@ -57,9 +57,17 @@ Using `BackgroundTasks` also works with the dependency injection system, you can
**FastAPI** knows what to do in each case and how to re-use the same object, so that all the background tasks are merged together and are run in the background afterwards:
-```Python hl_lines="13 15 22 25"
-{!../../../docs_src/background_tasks/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="13 15 22 25"
+ {!> ../../../docs_src/background_tasks/tutorial002.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="11 13 20 23"
+ {!> ../../../docs_src/background_tasks/tutorial002_py310.py!}
+ ```
In this example, the messages will be written to the `log.txt` file *after* the response is sent.
diff --git a/docs/en/docs/tutorial/bigger-applications.md b/docs/en/docs/tutorial/bigger-applications.md
index 2796d7fb..2a2e764b 100644
--- a/docs/en/docs/tutorial/bigger-applications.md
+++ b/docs/en/docs/tutorial/bigger-applications.md
@@ -234,7 +234,7 @@ mean:
* Starting in the same package that this module (the file `app/routers/items.py`) lives in (the directory `app/routers/`)...
* go to the parent package (the directory `app/`)...
-* and in there, find the module `dependencies` (the file at `app/routers/dependencies.py`)...
+* and in there, find the module `dependencies` (the file at `app/dependencies.py`)...
* and from it, import the function `get_token_header`.
That works correctly! 🎉
@@ -252,7 +252,7 @@ that would mean:
* Starting in the same package that this module (the file `app/routers/items.py`) lives in (the directory `app/routers/`)...
* go to the parent package (the directory `app/`)...
* then go to the parent of that package (there's no parent package, `app` is the top level 😱)...
-* and in there, find the module `dependencies` (the file at `app/routers/dependencies.py`)...
+* and in there, find the module `dependencies` (the file at `app/dependencies.py`)...
* and from it, import the function `get_token_header`.
That would refer to some package above `app/`, with its own file `__init__.py`, etc. But we don't have that. So, that would throw an error in our example. 🚨
diff --git a/docs/en/docs/tutorial/body-fields.md b/docs/en/docs/tutorial/body-fields.md
index 6b3fd8fb..1f38a0c5 100644
--- a/docs/en/docs/tutorial/body-fields.md
+++ b/docs/en/docs/tutorial/body-fields.md
@@ -6,9 +6,17 @@ The same way you can declare additional validation and metadata in *path operati
First, you have to import it:
-```Python hl_lines="4"
-{!../../../docs_src/body_fields/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="4"
+ {!> ../../../docs_src/body_fields/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="2"
+ {!> ../../../docs_src/body_fields/tutorial001_py310.py!}
+ ```
!!! warning
Notice that `Field` is imported directly from `pydantic`, not from `fastapi` as are all the rest (`Query`, `Path`, `Body`, etc).
@@ -17,9 +25,17 @@ First, you have to import it:
You can then use `Field` with model attributes:
-```Python hl_lines="11-14"
-{!../../../docs_src/body_fields/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="11-14"
+ {!> ../../../docs_src/body_fields/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="9-12"
+ {!> ../../../docs_src/body_fields/tutorial001_py310.py!}
+ ```
`Field` works the same way as `Query`, `Path` and `Body`, it has all the same parameters, etc.
diff --git a/docs/en/docs/tutorial/body-multiple-params.md b/docs/en/docs/tutorial/body-multiple-params.md
index 1bc8f143..13de4c8e 100644
--- a/docs/en/docs/tutorial/body-multiple-params.md
+++ b/docs/en/docs/tutorial/body-multiple-params.md
@@ -8,9 +8,17 @@ First, of course, you can mix `Path`, `Query` and request body parameter declara
And you can also declare body parameters as optional, by setting the default to `None`:
-```Python hl_lines="19-21"
-{!../../../docs_src/body_multiple_params/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="19-21"
+ {!> ../../../docs_src/body_multiple_params/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="17-19"
+ {!> ../../../docs_src/body_multiple_params/tutorial001_py310.py!}
+ ```
!!! note
Notice that, in this case, the `item` that would be taken from the body is optional. As it has a `None` default value.
@@ -30,9 +38,17 @@ In the previous example, the *path operations* would expect a JSON body with the
But you can also declare multiple body parameters, e.g. `item` and `user`:
-```Python hl_lines="22"
-{!../../../docs_src/body_multiple_params/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="22"
+ {!> ../../../docs_src/body_multiple_params/tutorial002.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="20"
+ {!> ../../../docs_src/body_multiple_params/tutorial002_py310.py!}
+ ```
In this case, **FastAPI** will notice that there are more than one body parameters in the function (two parameters that are Pydantic models).
@@ -71,14 +87,20 @@ If you declare it as is, because it is a singular value, **FastAPI** will assume
But you can instruct **FastAPI** to treat it as another body key using `Body`:
+=== "Python 3.6 and above"
-```Python hl_lines="23"
-{!../../../docs_src/body_multiple_params/tutorial003.py!}
-```
+ ```Python hl_lines="23"
+ {!> ../../../docs_src/body_multiple_params/tutorial003.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="21"
+ {!> ../../../docs_src/body_multiple_params/tutorial003_py310.py!}
+ ```
In this case, **FastAPI** will expect a body like:
-
```JSON
{
"item": {
@@ -107,12 +129,26 @@ As, by default, singular values are interpreted as query parameters, you don't h
q: Optional[str] = None
```
-as in:
+Or in Python 3.10 and above:
-```Python hl_lines="27"
-{!../../../docs_src/body_multiple_params/tutorial004.py!}
+```Python
+q: str | None = None
```
+For example:
+
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="28"
+ {!> ../../../docs_src/body_multiple_params/tutorial004.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="26"
+ {!> ../../../docs_src/body_multiple_params/tutorial004_py310.py!}
+ ```
+
!!! info
`Body` also has all the same extra validation and metadata parameters as `Query`,`Path` and others you will see later.
@@ -131,9 +167,17 @@ item: Item = Body(..., embed=True)
as in:
-```Python hl_lines="17"
-{!../../../docs_src/body_multiple_params/tutorial005.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="17"
+ {!> ../../../docs_src/body_multiple_params/tutorial005.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="15"
+ {!> ../../../docs_src/body_multiple_params/tutorial005_py310.py!}
+ ```
In this case **FastAPI** will expect a body like:
diff --git a/docs/en/docs/tutorial/body-nested-models.md b/docs/en/docs/tutorial/body-nested-models.md
index 14e725a2..fa38cfc4 100644
--- a/docs/en/docs/tutorial/body-nested-models.md
+++ b/docs/en/docs/tutorial/body-nested-models.md
@@ -6,9 +6,17 @@ With **FastAPI**, you can define, validate, document, and use arbitrarily deeply
You can define an attribute to be a subtype. For example, a Python `list`:
-```Python hl_lines="14"
-{!../../../docs_src/body_nested_models/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="14"
+ {!> ../../../docs_src/body_nested_models/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="12"
+ {!> ../../../docs_src/body_nested_models/tutorial001_py310.py!}
+ ```
This will make `tags` be a list of items. Although it doesn't declare the type of each of the items.
@@ -18,19 +26,29 @@ But Python has a specific way to declare lists with internal types, or "type par
### Import typing's `List`
-First, import `List` from standard Python's `typing` module:
+In Python 3.9 and above you can use the standard `list` to declare these type annotations as we'll see below. 💡
+
+But in Python versions before 3.9 (3.6 and above), you first need to import `List` from standard Python's `typing` module:
```Python hl_lines="1"
-{!../../../docs_src/body_nested_models/tutorial002.py!}
+{!> ../../../docs_src/body_nested_models/tutorial002.py!}
```
-### Declare a `List` with a type parameter
+### Declare a `list` with a type parameter
To declare types that have type parameters (internal types), like `list`, `dict`, `tuple`:
-* Import them from the `typing` module
+* If you are in a Python version lower than 3.9, import their equivalent version from the `typing` module
* Pass the internal type(s) as "type parameters" using square brackets: `[` and `]`
+In Python 3.9 it would be:
+
+```Python
+my_list: list[str]
+```
+
+In versions of Python before 3.9, it would be:
+
```Python
from typing import List
@@ -43,9 +61,23 @@ Use that same standard syntax for model attributes with internal types.
So, in our example, we can make `tags` be specifically a "list of strings":
-```Python hl_lines="14"
-{!../../../docs_src/body_nested_models/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="14"
+ {!> ../../../docs_src/body_nested_models/tutorial002.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="14"
+ {!> ../../../docs_src/body_nested_models/tutorial002_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="12"
+ {!> ../../../docs_src/body_nested_models/tutorial002_py310.py!}
+ ```
## Set types
@@ -53,11 +85,25 @@ But then we think about it, and realize that tags shouldn't repeat, they would p
And Python has a special data type for sets of unique items, the `set`.
-Then we can import `Set` and declare `tags` as a `set` of `str`:
+Then we can declare `tags` as a set of strings:
-```Python hl_lines="1 14"
-{!../../../docs_src/body_nested_models/tutorial003.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="1 14"
+ {!> ../../../docs_src/body_nested_models/tutorial003.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="14"
+ {!> ../../../docs_src/body_nested_models/tutorial003_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="12"
+ {!> ../../../docs_src/body_nested_models/tutorial003_py310.py!}
+ ```
With this, even if you receive a request with duplicate data, it will be converted to a set of unique items.
@@ -79,17 +125,45 @@ All that, arbitrarily nested.
For example, we can define an `Image` model:
-```Python hl_lines="9-11"
-{!../../../docs_src/body_nested_models/tutorial004.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9-11"
+ {!> ../../../docs_src/body_nested_models/tutorial004.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="9-11"
+ {!> ../../../docs_src/body_nested_models/tutorial004_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7-9"
+ {!> ../../../docs_src/body_nested_models/tutorial004_py310.py!}
+ ```
### Use the submodel as a type
And then we can use it as the type of an attribute:
-```Python hl_lines="20"
-{!../../../docs_src/body_nested_models/tutorial004.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="20"
+ {!> ../../../docs_src/body_nested_models/tutorial004.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="20"
+ {!> ../../../docs_src/body_nested_models/tutorial004_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="18"
+ {!> ../../../docs_src/body_nested_models/tutorial004_py310.py!}
+ ```
This would mean that **FastAPI** would expect a body similar to:
@@ -122,9 +196,23 @@ To see all the options you have, checkout the docs for ../../../docs_src/body_nested_models/tutorial005.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="4 10"
+ {!> ../../../docs_src/body_nested_models/tutorial005_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="2 8"
+ {!> ../../../docs_src/body_nested_models/tutorial005_py310.py!}
+ ```
The string will be checked to be a valid URL, and documented in JSON Schema / OpenAPI as such.
@@ -132,9 +220,23 @@ The string will be checked to be a valid URL, and documented in JSON Schema / Op
You can also use Pydantic models as subtypes of `list`, `set`, etc:
-```Python hl_lines="20"
-{!../../../docs_src/body_nested_models/tutorial006.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="20"
+ {!> ../../../docs_src/body_nested_models/tutorial006.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="20"
+ {!> ../../../docs_src/body_nested_models/tutorial006_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="18"
+ {!> ../../../docs_src/body_nested_models/tutorial006_py310.py!}
+ ```
This will expect (convert, validate, document, etc) a JSON body like:
@@ -169,9 +271,23 @@ This will expect (convert, validate, document, etc) a JSON body like:
You can define arbitrarily deeply nested models:
-```Python hl_lines="9 14 20 23 27"
-{!../../../docs_src/body_nested_models/tutorial007.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9 14 20 23 27"
+ {!> ../../../docs_src/body_nested_models/tutorial007.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="9 14 20 23 27"
+ {!> ../../../docs_src/body_nested_models/tutorial007_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7 12 18 21 25"
+ {!> ../../../docs_src/body_nested_models/tutorial007_py310.py!}
+ ```
!!! info
Notice how `Offer` has a list of `Item`s, which in turn have an optional list of `Image`s
@@ -184,11 +300,25 @@ If the top level value of the JSON body you expect is a JSON `array` (a Python `
images: List[Image]
```
+or in Python 3.9 and above:
+
+```Python
+images: list[Image]
+```
+
as in:
-```Python hl_lines="15"
-{!../../../docs_src/body_nested_models/tutorial008.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="15"
+ {!> ../../../docs_src/body_nested_models/tutorial008.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="13"
+ {!> ../../../docs_src/body_nested_models/tutorial008_py39.py!}
+ ```
## Editor support everywhere
@@ -198,7 +328,7 @@ Even for items inside of lists:
-You couldn't get this kind of editor support if you where working directly with `dict` instead of Pydantic models.
+You couldn't get this kind of editor support if you were working directly with `dict` instead of Pydantic models.
But you don't have to worry about them either, incoming dicts are converted automatically and your output is converted automatically to JSON too.
@@ -218,9 +348,17 @@ That's what we are going to see here.
In this case, you would accept any `dict` as long as it has `int` keys with `float` values:
-```Python hl_lines="15"
-{!../../../docs_src/body_nested_models/tutorial009.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/body_nested_models/tutorial009.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/body_nested_models/tutorial009_py39.py!}
+ ```
!!! tip
Have in mind that JSON only supports `str` as keys.
diff --git a/docs/en/docs/tutorial/body-updates.md b/docs/en/docs/tutorial/body-updates.md
index 757d7bdb..7d867506 100644
--- a/docs/en/docs/tutorial/body-updates.md
+++ b/docs/en/docs/tutorial/body-updates.md
@@ -6,9 +6,23 @@ To update an item you can use the ../../../docs_src/body_updates/tutorial001.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="30-35"
+ {!> ../../../docs_src/body_updates/tutorial001_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="28-33"
+ {!> ../../../docs_src/body_updates/tutorial001_py310.py!}
+ ```
`PUT` is used to receive data that should replace the existing data.
@@ -53,9 +67,23 @@ That would generate a `dict` with only the data that was set when creating the `
Then you can use this to generate a `dict` with only the data that was set (sent in the request), omitting default values:
-```Python hl_lines="34"
-{!../../../docs_src/body_updates/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="34"
+ {!> ../../../docs_src/body_updates/tutorial002.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="34"
+ {!> ../../../docs_src/body_updates/tutorial002_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="32"
+ {!> ../../../docs_src/body_updates/tutorial002_py310.py!}
+ ```
### Using Pydantic's `update` parameter
@@ -63,9 +91,23 @@ Now, you can create a copy of the existing model using `.copy()`, and pass the `
Like `stored_item_model.copy(update=update_data)`:
-```Python hl_lines="35"
-{!../../../docs_src/body_updates/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="35"
+ {!> ../../../docs_src/body_updates/tutorial002.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="35"
+ {!> ../../../docs_src/body_updates/tutorial002_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="33"
+ {!> ../../../docs_src/body_updates/tutorial002_py310.py!}
+ ```
### Partial updates recap
@@ -82,9 +124,23 @@ In summary, to apply partial updates you would:
* Save the data to your DB.
* Return the updated model.
-```Python hl_lines="30-37"
-{!../../../docs_src/body_updates/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="30-37"
+ {!> ../../../docs_src/body_updates/tutorial002.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="30-37"
+ {!> ../../../docs_src/body_updates/tutorial002_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="28-35"
+ {!> ../../../docs_src/body_updates/tutorial002_py310.py!}
+ ```
!!! tip
You can actually use this same technique with an HTTP `PUT` operation.
diff --git a/docs/en/docs/tutorial/body.md b/docs/en/docs/tutorial/body.md
index f18afaea..81441b41 100644
--- a/docs/en/docs/tutorial/body.md
+++ b/docs/en/docs/tutorial/body.md
@@ -19,9 +19,17 @@ To declare a **request** body, you use ../../../docs_src/body/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="2"
+ {!> ../../../docs_src/body/tutorial001_py310.py!}
+ ```
## Create your data model
@@ -29,9 +37,17 @@ Then you declare your data model as a class that inherits from `BaseModel`.
Use standard Python types for all the attributes:
-```Python hl_lines="7-11"
-{!../../../docs_src/body/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="7-11"
+ {!> ../../../docs_src/body/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="5-9"
+ {!> ../../../docs_src/body/tutorial001_py310.py!}
+ ```
The same as when declaring query parameters, when a model attribute has a default value, it is not required. Otherwise, it is required. Use `None` to make it just optional.
@@ -59,9 +75,17 @@ For example, this model above declares a JSON "`object`" (or Python `dict`) like
To add it to your *path operation*, declare it the same way you declared path and query parameters:
-```Python hl_lines="18"
-{!../../../docs_src/body/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="18"
+ {!> ../../../docs_src/body/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="16"
+ {!> ../../../docs_src/body/tutorial001_py310.py!}
+ ```
...and declare its type as the model you created, `Item`.
@@ -125,9 +149,17 @@ But you would get the same editor support with ../../../docs_src/body/tutorial002.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="19"
+ {!> ../../../docs_src/body/tutorial002_py310.py!}
+ ```
## Request body + path parameters
@@ -135,9 +167,17 @@ You can declare path parameters and request body at the same time.
**FastAPI** will recognize that the function parameters that match path parameters should be **taken from the path**, and that function parameters that are declared to be Pydantic models should be **taken from the request body**.
-```Python hl_lines="17-18"
-{!../../../docs_src/body/tutorial003.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="17-18"
+ {!> ../../../docs_src/body/tutorial003.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="15-16"
+ {!> ../../../docs_src/body/tutorial003_py310.py!}
+ ```
## Request body + path + query parameters
@@ -145,9 +185,17 @@ You can also declare **body**, **path** and **query** parameters, all at the sam
**FastAPI** will recognize each of them and take the data from the correct place.
-```Python hl_lines="18"
-{!../../../docs_src/body/tutorial004.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="18"
+ {!> ../../../docs_src/body/tutorial004.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="16"
+ {!> ../../../docs_src/body/tutorial004_py310.py!}
+ ```
The function parameters will be recognized as follows:
diff --git a/docs/en/docs/tutorial/cookie-params.md b/docs/en/docs/tutorial/cookie-params.md
index 9aa2300c..221cdfff 100644
--- a/docs/en/docs/tutorial/cookie-params.md
+++ b/docs/en/docs/tutorial/cookie-params.md
@@ -6,9 +6,17 @@ You can define Cookie parameters the same way you define `Query` and `Path` para
First import `Cookie`:
-```Python hl_lines="3"
-{!../../../docs_src/cookie_params/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="3"
+ {!> ../../../docs_src/cookie_params/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="1"
+ {!> ../../../docs_src/cookie_params/tutorial001_py310.py!}
+ ```
## Declare `Cookie` parameters
@@ -16,9 +24,17 @@ Then declare the cookie parameters using the same structure as with `Path` and `
The first value is the default value, you can pass all the extra validation or annotation parameters:
-```Python hl_lines="9"
-{!../../../docs_src/cookie_params/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/cookie_params/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/cookie_params/tutorial001_py310.py!}
+ ```
!!! note "Technical Details"
`Cookie` is a "sister" class of `Path` and `Query`. It also inherits from the same common `Param` class.
diff --git a/docs/en/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/en/docs/tutorial/dependencies/classes-as-dependencies.md
index 8c00374b..663fff15 100644
--- a/docs/en/docs/tutorial/dependencies/classes-as-dependencies.md
+++ b/docs/en/docs/tutorial/dependencies/classes-as-dependencies.md
@@ -6,9 +6,17 @@ Before diving deeper into the **Dependency Injection** system, let's upgrade the
In the previous example, we were returning a `dict` from our dependency ("dependable"):
-```Python hl_lines="9"
-{!../../../docs_src/dependencies/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/dependencies/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/dependencies/tutorial001_py310.py!}
+ ```
But then we get a `dict` in the parameter `commons` of the *path operation function*.
@@ -71,29 +79,53 @@ That also applies to callables with no parameters at all. The same as it would b
Then, we can change the dependency "dependable" `common_parameters` from above to the class `CommonQueryParams`:
-```Python hl_lines="11-15"
-{!../../../docs_src/dependencies/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="11-15"
+ {!> ../../../docs_src/dependencies/tutorial002.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="9-13"
+ {!> ../../../docs_src/dependencies/tutorial002_py310.py!}
+ ```
Pay attention to the `__init__` method used to create the instance of the class:
-```Python hl_lines="12"
-{!../../../docs_src/dependencies/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="12"
+ {!> ../../../docs_src/dependencies/tutorial002.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="10"
+ {!> ../../../docs_src/dependencies/tutorial002_py310.py!}
+ ```
...it has the same parameters as our previous `common_parameters`:
-```Python hl_lines="8"
-{!../../../docs_src/dependencies/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="8"
+ {!> ../../../docs_src/dependencies/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="6"
+ {!> ../../../docs_src/dependencies/tutorial001_py310.py!}
+ ```
Those parameters are what **FastAPI** will use to "solve" the dependency.
In both cases, it will have:
-* an optional `q` query parameter.
-* a `skip` query parameter, with a default of `0`.
-* a `limit` query parameter, with a default of `100`.
+* An optional `q` query parameter that is a `str`.
+* A `skip` query parameter that is an `int`, with a default of `0`.
+* A `limit` query parameter that is an `int`, with a default of `100`.
In both cases the data will be converted, validated, documented on the OpenAPI schema, etc.
@@ -101,9 +133,17 @@ In both cases the data will be converted, validated, documented on the OpenAPI s
Now you can declare your dependency using this class.
-```Python hl_lines="19"
-{!../../../docs_src/dependencies/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="19"
+ {!> ../../../docs_src/dependencies/tutorial002.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="17"
+ {!> ../../../docs_src/dependencies/tutorial002_py310.py!}
+ ```
**FastAPI** calls the `CommonQueryParams` class. This creates an "instance" of that class and the instance will be passed as the parameter `commons` to your function.
@@ -143,9 +183,17 @@ commons = Depends(CommonQueryParams)
..as in:
-```Python hl_lines="19"
-{!../../../docs_src/dependencies/tutorial003.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="19"
+ {!> ../../../docs_src/dependencies/tutorial003.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="17"
+ {!> ../../../docs_src/dependencies/tutorial003_py310.py!}
+ ```
But declaring the type is encouraged as that way your editor will know what will be passed as the parameter `commons`, and then it can help you with code completion, type checks, etc:
@@ -179,9 +227,17 @@ You declare the dependency as the type of the parameter, and you use `Depends()`
The same example would then look like:
-```Python hl_lines="19"
-{!../../../docs_src/dependencies/tutorial004.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="19"
+ {!> ../../../docs_src/dependencies/tutorial004.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="17"
+ {!> ../../../docs_src/dependencies/tutorial004_py310.py!}
+ ```
...and **FastAPI** will know what to do.
diff --git a/docs/en/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/en/docs/tutorial/dependencies/dependencies-with-yield.md
index 3388a082..ac2e9cb8 100644
--- a/docs/en/docs/tutorial/dependencies/dependencies-with-yield.md
+++ b/docs/en/docs/tutorial/dependencies/dependencies-with-yield.md
@@ -7,15 +7,6 @@ To do this, use `yield` instead of `return`, and write the extra steps after.
!!! tip
Make sure to use `yield` one single time.
-!!! info
- For this to work, you need to use **Python 3.7** or above, or in **Python 3.6**, install the "backports":
-
- ```
- pip install async-exit-stack async-generator
- ```
-
- This installs async-exit-stack and async-generator.
-
!!! note "Technical Details"
Any function that is valid to use with:
@@ -108,7 +99,7 @@ You saw that you can use dependencies with `yield` and have `try` blocks that ca
It might be tempting to raise an `HTTPException` or similar in the exit code, after the `yield`. But **it won't work**.
-The exit code in dependencies with `yield` is executed *after* [Exception Handlers](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}. There's nothing catching exceptions thrown by your dependencies in the exit code (after the `yield`).
+The exit code in dependencies with `yield` is executed *after* the response is sent, so [Exception Handlers](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} will have already run. There's nothing catching exceptions thrown by your dependencies in the exit code (after the `yield`).
So, if you raise an `HTTPException` after the `yield`, the default (or any custom) exception handler that catches `HTTPException`s and returns an HTTP 400 response won't be there to catch that exception anymore.
@@ -147,9 +138,11 @@ participant tasks as Background tasks
end
dep ->> operation: Run dependency, e.g. DB session
opt raise
- operation -->> handler: Raise HTTPException
+ operation -->> dep: Raise HTTPException
+ dep -->> handler: Auto forward exception
handler -->> client: HTTP error response
operation -->> dep: Raise other exception
+ dep -->> handler: Auto forward exception
end
operation ->> client: Return response to client
Note over client,operation: Response is already sent, can't change it anymore
@@ -171,9 +164,9 @@ participant tasks as Background tasks
After one of those responses is sent, no other response can be sent.
!!! tip
- This diagram shows `HTTPException`, but you could also raise any other exception for which you create a [Custom Exception Handler](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}. And that exception would be handled by that custom exception handler instead of the dependency exit code.
+ This diagram shows `HTTPException`, but you could also raise any other exception for which you create a [Custom Exception Handler](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}.
- But if you raise an exception that is not handled by the exception handlers, it will be handled by the exit code of the dependency.
+ If you raise any exception, it will be passed to the dependencies with yield, including `HTTPException`, and then **again** to the exception handlers. If there's no exception handler for that exception, it will then be handled by the default internal `ServerErrorMiddleware`, returning a 500 HTTP status code, to let the client know that there was an error in the server.
## Context Managers
diff --git a/docs/en/docs/tutorial/dependencies/index.md b/docs/en/docs/tutorial/dependencies/index.md
index cf0b5a92..fe10facf 100644
--- a/docs/en/docs/tutorial/dependencies/index.md
+++ b/docs/en/docs/tutorial/dependencies/index.md
@@ -31,9 +31,17 @@ Let's first focus on the dependency.
It is just a function that can take all the same parameters that a *path operation function* can take:
-```Python hl_lines="8-9"
-{!../../../docs_src/dependencies/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="8-9"
+ {!> ../../../docs_src/dependencies/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="6-7"
+ {!> ../../../docs_src/dependencies/tutorial001_py310.py!}
+ ```
That's it.
@@ -55,17 +63,33 @@ And then it just returns a `dict` containing those values.
### Import `Depends`
-```Python hl_lines="3"
-{!../../../docs_src/dependencies/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="3"
+ {!> ../../../docs_src/dependencies/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="1"
+ {!> ../../../docs_src/dependencies/tutorial001_py310.py!}
+ ```
### Declare the dependency, in the "dependant"
The same way you use `Body`, `Query`, etc. with your *path operation function* parameters, use `Depends` with a new parameter:
-```Python hl_lines="13 18"
-{!../../../docs_src/dependencies/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="13 18"
+ {!> ../../../docs_src/dependencies/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="11 16"
+ {!> ../../../docs_src/dependencies/tutorial001_py310.py!}
+ ```
Although you use `Depends` in the parameters of your function the same way you use `Body`, `Query`, etc, `Depends` works a bit differently.
diff --git a/docs/en/docs/tutorial/dependencies/sub-dependencies.md b/docs/en/docs/tutorial/dependencies/sub-dependencies.md
index 097edb68..51531228 100644
--- a/docs/en/docs/tutorial/dependencies/sub-dependencies.md
+++ b/docs/en/docs/tutorial/dependencies/sub-dependencies.md
@@ -6,25 +6,41 @@ They can be as **deep** as you need them to be.
**FastAPI** will take care of solving them.
-### First dependency "dependable"
+## First dependency "dependable"
You could create a first dependency ("dependable") like:
-```Python hl_lines="8-9"
-{!../../../docs_src/dependencies/tutorial005.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="8-9"
+ {!> ../../../docs_src/dependencies/tutorial005.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="6-7"
+ {!> ../../../docs_src/dependencies/tutorial005_py310.py!}
+ ```
It declares an optional query parameter `q` as a `str`, and then it just returns it.
This is quite simple (not very useful), but will help us focus on how the sub-dependencies work.
-### Second dependency, "dependable" and "dependant"
+## Second dependency, "dependable" and "dependant"
Then you can create another dependency function (a "dependable") that at the same time declares a dependency of its own (so it is a "dependant" too):
-```Python hl_lines="13"
-{!../../../docs_src/dependencies/tutorial005.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="13"
+ {!> ../../../docs_src/dependencies/tutorial005.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="11"
+ {!> ../../../docs_src/dependencies/tutorial005_py310.py!}
+ ```
Let's focus on the parameters declared:
@@ -33,13 +49,21 @@ Let's focus on the parameters declared:
* It also declares an optional `last_query` cookie, as a `str`.
* If the user didn't provide any query `q`, we use the last query used, which we saved to a cookie before.
-### Use the dependency
+## Use the dependency
Then we can use the dependency with:
-```Python hl_lines="21"
-{!../../../docs_src/dependencies/tutorial005.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="21"
+ {!> ../../../docs_src/dependencies/tutorial005.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="19"
+ {!> ../../../docs_src/dependencies/tutorial005_py310.py!}
+ ```
!!! info
Notice that we are only declaring one dependency in the *path operation function*, the `query_or_cookie_extractor`.
diff --git a/docs/en/docs/tutorial/encoder.md b/docs/en/docs/tutorial/encoder.md
index a2cbe45d..7a69c547 100644
--- a/docs/en/docs/tutorial/encoder.md
+++ b/docs/en/docs/tutorial/encoder.md
@@ -20,9 +20,17 @@ You can use `jsonable_encoder` for that.
It receives an object, like a Pydantic model, and returns a JSON compatible version:
-```Python hl_lines="5 22"
-{!../../../docs_src/encoder/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="5 22"
+ {!> ../../../docs_src/encoder/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="4 21"
+ {!> ../../../docs_src/encoder/tutorial001_py310.py!}
+ ```
In this example, it would convert the Pydantic model to a `dict`, and the `datetime` to a `str`.
diff --git a/docs/en/docs/tutorial/extra-data-types.md b/docs/en/docs/tutorial/extra-data-types.md
index 995f0b97..a00bd321 100644
--- a/docs/en/docs/tutorial/extra-data-types.md
+++ b/docs/en/docs/tutorial/extra-data-types.md
@@ -55,12 +55,28 @@ Here are some of the additional data types you can use:
Here's an example *path operation* with parameters using some of the above types.
-```Python hl_lines="1 3 12-16"
-{!../../../docs_src/extra_data_types/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="1 3 12-16"
+ {!> ../../../docs_src/extra_data_types/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="1 2 11-15"
+ {!> ../../../docs_src/extra_data_types/tutorial001_py310.py!}
+ ```
Note that the parameters inside the function have their natural data type, and you can, for example, perform normal date manipulations, like:
-```Python hl_lines="18-19"
-{!../../../docs_src/extra_data_types/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="18-19"
+ {!> ../../../docs_src/extra_data_types/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="17-18"
+ {!> ../../../docs_src/extra_data_types/tutorial001_py310.py!}
+ ```
diff --git a/docs/en/docs/tutorial/extra-models.md b/docs/en/docs/tutorial/extra-models.md
index 4eced04c..72fc7489 100644
--- a/docs/en/docs/tutorial/extra-models.md
+++ b/docs/en/docs/tutorial/extra-models.md
@@ -17,9 +17,17 @@ This is especially the case for user models, because:
Here's a general idea of how the models could look like with their password fields and the places where they are used:
-```Python hl_lines="9 11 16 22 24 29-30 33-35 40-41"
-{!../../../docs_src/extra_models/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9 11 16 22 24 29-30 33-35 40-41"
+ {!> ../../../docs_src/extra_models/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7 9 14 20 22 27-28 31-33 38-39"
+ {!> ../../../docs_src/extra_models/tutorial001_py310.py!}
+ ```
### About `**user_in.dict()`
@@ -150,9 +158,17 @@ All the data conversion, validation, documentation, etc. will still work as norm
That way, we can declare just the differences between the models (with plaintext `password`, with `hashed_password` and without password):
-```Python hl_lines="9 15-16 19-20 23-24"
-{!../../../docs_src/extra_models/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9 15-16 19-20 23-24"
+ {!> ../../../docs_src/extra_models/tutorial002.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7 13-14 17-18 21-22"
+ {!> ../../../docs_src/extra_models/tutorial002_py310.py!}
+ ```
## `Union` or `anyOf`
@@ -165,19 +181,49 @@ To do that, use the standard Python type hint `Union`, include the most specific type first, followed by the less specific type. In the example below, the more specific `PlaneItem` comes before `CarItem` in `Union[PlaneItem, CarItem]`.
-```Python hl_lines="1 14-15 18-20 33"
-{!../../../docs_src/extra_models/tutorial003.py!}
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="1 14-15 18-20 33"
+ {!> ../../../docs_src/extra_models/tutorial003.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="1 14-15 18-20 33"
+ {!> ../../../docs_src/extra_models/tutorial003_py310.py!}
+ ```
+
+### `Union` in Python 3.10
+
+In this example we pass `Union[PlaneItem, CarItem]` as the value of the argument `response_model`.
+
+Because we are passing it as a **value to an argument** instead of putting it in a **type annotation**, we have to use `Union` even in Python 3.10.
+
+If it was in a type annotation we could have used the vertical bar, as:
+
+```Python
+some_variable: PlaneItem | CarItem
```
+But if we put that in `response_model=PlaneItem | CarItem` we would get an error, because Python would try to perform an **invalid operation** between `PlaneItem` and `CarItem` instead of interpreting that as a type annotation.
+
## List of models
The same way, you can declare responses of lists of objects.
-For that, use the standard Python `typing.List`:
+For that, use the standard Python `typing.List` (or just `list` in Python 3.9 and above):
-```Python hl_lines="1 20"
-{!../../../docs_src/extra_models/tutorial004.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="1 20"
+ {!> ../../../docs_src/extra_models/tutorial004.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="18"
+ {!> ../../../docs_src/extra_models/tutorial004_py39.py!}
+ ```
## Response with arbitrary `dict`
@@ -185,11 +231,19 @@ You can also declare a response using a plain arbitrary `dict`, declaring just t
This is useful if you don't know the valid field/attribute names (that would be needed for a Pydantic model) beforehand.
-In this case, you can use `typing.Dict`:
+In this case, you can use `typing.Dict` (or just `dict` in Python 3.9 and above):
-```Python hl_lines="1 8"
-{!../../../docs_src/extra_models/tutorial005.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="1 8"
+ {!> ../../../docs_src/extra_models/tutorial005.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="6"
+ {!> ../../../docs_src/extra_models/tutorial005_py39.py!}
+ ```
## Recap
diff --git a/docs/en/docs/tutorial/handling-errors.md b/docs/en/docs/tutorial/handling-errors.md
index 4fa82d5d..82e16626 100644
--- a/docs/en/docs/tutorial/handling-errors.md
+++ b/docs/en/docs/tutorial/handling-errors.md
@@ -252,14 +252,10 @@ from starlette.exceptions import HTTPException as StarletteHTTPException
### Re-use **FastAPI**'s exception handlers
-You could also just want to use the exception somehow, but then use the same default exception handlers from **FastAPI**.
-
-You can import and re-use the default exception handlers from `fastapi.exception_handlers`:
+If you want to use the exception along with the same default exception handlers from **FastAPI**, You can import and re-use the default exception handlers from `fastapi.exception_handlers`:
```Python hl_lines="2-5 15 21"
{!../../../docs_src/handling_errors/tutorial006.py!}
```
-In this example, you are just `print`ing the error with a very expressive message.
-
-But you get the idea, you can use the exception and then just re-use the default exception handlers.
+In this example you are just `print`ing the error with a very expressive message, but you get the idea. You can use the exception and then just re-use the default exception handlers.
diff --git a/docs/en/docs/tutorial/header-params.md b/docs/en/docs/tutorial/header-params.md
index 57570153..8e294416 100644
--- a/docs/en/docs/tutorial/header-params.md
+++ b/docs/en/docs/tutorial/header-params.md
@@ -6,9 +6,17 @@ You can define Header parameters the same way you define `Query`, `Path` and `Co
First import `Header`:
-```Python hl_lines="3"
-{!../../../docs_src/header_params/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="3"
+ {!> ../../../docs_src/header_params/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="1"
+ {!> ../../../docs_src/header_params/tutorial001_py310.py!}
+ ```
## Declare `Header` parameters
@@ -16,9 +24,17 @@ Then declare the header parameters using the same structure as with `Path`, `Que
The first value is the default value, you can pass all the extra validation or annotation parameters:
-```Python hl_lines="9"
-{!../../../docs_src/header_params/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/header_params/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/header_params/tutorial001_py310.py!}
+ ```
!!! note "Technical Details"
`Header` is a "sister" class of `Path`, `Query` and `Cookie`. It also inherits from the same common `Param` class.
@@ -44,14 +60,21 @@ So, you can use `user_agent` as you normally would in Python code, instead of ne
If for some reason you need to disable automatic conversion of underscores to hyphens, set the parameter `convert_underscores` of `Header` to `False`:
-```Python hl_lines="10"
-{!../../../docs_src/header_params/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="10"
+ {!> ../../../docs_src/header_params/tutorial002.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="8"
+ {!> ../../../docs_src/header_params/tutorial002_py310.py!}
+ ```
!!! warning
Before setting `convert_underscores` to `False`, bear in mind that some HTTP proxies and servers disallow the usage of headers with underscores.
-
## Duplicate headers
It is possible to receive duplicate headers. That means, the same header with multiple values.
@@ -62,9 +85,23 @@ You will receive all the values from the duplicate header as a Python `list`.
For example, to declare a header of `X-Token` that can appear more than once, you can write:
-```Python hl_lines="9"
-{!../../../docs_src/header_params/tutorial003.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/header_params/tutorial003.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/header_params/tutorial003_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/header_params/tutorial003_py310.py!}
+ ```
If you communicate with that *path operation* sending two HTTP headers like:
diff --git a/docs/en/docs/tutorial/index.md b/docs/en/docs/tutorial/index.md
index bd02cd53..8b4a9df9 100644
--- a/docs/en/docs/tutorial/index.md
+++ b/docs/en/docs/tutorial/index.md
@@ -43,7 +43,7 @@ For the tutorial, you might want to install it with all the optional dependencie
contact fields| Parameter | Type | Description |
|---|---|---|
name | str | The identifying name of the contact person/organization. |
url | str | The URL pointing to the contact information. MUST be in the format of a URL. |
email | str | The email address of the contact person/organization. MUST be in the format of an email address. |
license_info fields| Parameter | Type | Description |
|---|---|---|
name | str | REQUIRED (if a license_info is set). The license name used for the API. |
url | str | A URL to the license used for the API. MUST be in the format of a URL. |
diff --git a/docs/en/docs/tutorial/path-operation-configuration.md b/docs/en/docs/tutorial/path-operation-configuration.md
index 0d606331..884a762e 100644
--- a/docs/en/docs/tutorial/path-operation-configuration.md
+++ b/docs/en/docs/tutorial/path-operation-configuration.md
@@ -13,9 +13,23 @@ You can pass directly the `int` code, like `404`.
But if you don't remember what each number code is for, you can use the shortcut constants in `status`:
-```Python hl_lines="3 17"
-{!../../../docs_src/path_operation_configuration/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="3 17"
+ {!> ../../../docs_src/path_operation_configuration/tutorial001.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="3 17"
+ {!> ../../../docs_src/path_operation_configuration/tutorial001_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="1 15"
+ {!> ../../../docs_src/path_operation_configuration/tutorial001_py310.py!}
+ ```
That status code will be used in the response and will be added to the OpenAPI schema.
@@ -28,21 +42,61 @@ That status code will be used in the response and will be added to the OpenAPI s
You can add tags to your *path operation*, pass the parameter `tags` with a `list` of `str` (commonly just one `str`):
-```Python hl_lines="17 22 27"
-{!../../../docs_src/path_operation_configuration/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="17 22 27"
+ {!> ../../../docs_src/path_operation_configuration/tutorial002.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="17 22 27"
+ {!> ../../../docs_src/path_operation_configuration/tutorial002_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="15 20 25"
+ {!> ../../../docs_src/path_operation_configuration/tutorial002_py310.py!}
+ ```
They will be added to the OpenAPI schema and used by the automatic documentation interfaces:
+### Tags with Enums
+
+If you have a big application, you might end up accumulating **several tags**, and you would want to make sure you always use the **same tag** for related *path operations*.
+
+In these cases, it could make sense to store the tags in an `Enum`.
+
+**FastAPI** supports that the same way as with plain strings:
+
+```Python hl_lines="1 8-10 13 18"
+{!../../../docs_src/path_operation_configuration/tutorial002b.py!}
+```
+
## Summary and description
You can add a `summary` and `description`:
-```Python hl_lines="20-21"
-{!../../../docs_src/path_operation_configuration/tutorial003.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="20-21"
+ {!> ../../../docs_src/path_operation_configuration/tutorial003.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="20-21"
+ {!> ../../../docs_src/path_operation_configuration/tutorial003_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="18-19"
+ {!> ../../../docs_src/path_operation_configuration/tutorial003_py310.py!}
+ ```
## Description from docstring
@@ -50,9 +104,23 @@ As descriptions tend to be long and cover multiple lines, you can declare the *p
You can write Markdown in the docstring, it will be interpreted and displayed correctly (taking into account docstring indentation).
-```Python hl_lines="19-27"
-{!../../../docs_src/path_operation_configuration/tutorial004.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="19-27"
+ {!> ../../../docs_src/path_operation_configuration/tutorial004.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="19-27"
+ {!> ../../../docs_src/path_operation_configuration/tutorial004_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="17-25"
+ {!> ../../../docs_src/path_operation_configuration/tutorial004_py310.py!}
+ ```
It will be used in the interactive docs:
@@ -62,9 +130,23 @@ It will be used in the interactive docs:
You can specify the response description with the parameter `response_description`:
-```Python hl_lines="21"
-{!../../../docs_src/path_operation_configuration/tutorial005.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="21"
+ {!> ../../../docs_src/path_operation_configuration/tutorial005.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="21"
+ {!> ../../../docs_src/path_operation_configuration/tutorial005_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="19"
+ {!> ../../../docs_src/path_operation_configuration/tutorial005_py310.py!}
+ ```
!!! info
Notice that `response_description` refers specifically to the response, the `description` refers to the *path operation* in general.
diff --git a/docs/en/docs/tutorial/path-params-numeric-validations.md b/docs/en/docs/tutorial/path-params-numeric-validations.md
index 5da69a21..31bf91a0 100644
--- a/docs/en/docs/tutorial/path-params-numeric-validations.md
+++ b/docs/en/docs/tutorial/path-params-numeric-validations.md
@@ -6,9 +6,17 @@ The same way you can declare more validations and metadata for query parameters
First, import `Path` from `fastapi`:
-```Python hl_lines="3"
-{!../../../docs_src/path_params_numeric_validations/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="3"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="1"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial001_py310.py!}
+ ```
## Declare metadata
@@ -16,13 +24,21 @@ You can declare all the same parameters as for `Query`.
For example, to declare a `title` metadata value for the path parameter `item_id` you can type:
-```Python hl_lines="10"
-{!../../../docs_src/path_params_numeric_validations/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="10"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="8"
+ {!> ../../../docs_src/path_params_numeric_validations/tutorial001_py310.py!}
+ ```
!!! note
A path parameter is always required as it has to be part of the path.
-
+
So, you should declare it with `...` to mark it as required.
Nevertheless, even if you declared it with `None` or set a default value, it would not affect anything, it would still be always required.
diff --git a/docs/en/docs/tutorial/query-params-str-validations.md b/docs/en/docs/tutorial/query-params-str-validations.md
index 8deccda0..ee62b971 100644
--- a/docs/en/docs/tutorial/query-params-str-validations.md
+++ b/docs/en/docs/tutorial/query-params-str-validations.md
@@ -4,11 +4,19 @@
Let's take this application as example:
-```Python hl_lines="9"
-{!../../../docs_src/query_params_str_validations/tutorial001.py!}
-```
+=== "Python 3.6 and above"
-The query parameter `q` is of type `Optional[str]`, that means that it's of type `str` but could also be `None`, and indeed, the default value is `None`, so FastAPI will know it's not required.
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/query_params_str_validations/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/query_params_str_validations/tutorial001_py310.py!}
+ ```
+
+The query parameter `q` is of type `Optional[str]` (or `str | None` in Python 3.10), that means that it's of type `str` but could also be `None`, and indeed, the default value is `None`, so FastAPI will know it's not required.
!!! note
FastAPI will know that the value of `q` is not required because of the default value `= None`.
@@ -23,17 +31,33 @@ We are going to enforce that even though `q` is optional, whenever it is provide
To achieve that, first import `Query` from `fastapi`:
-```Python hl_lines="3"
-{!../../../docs_src/query_params_str_validations/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="3"
+ {!> ../../../docs_src/query_params_str_validations/tutorial002.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="1"
+ {!> ../../../docs_src/query_params_str_validations/tutorial002_py310.py!}
+ ```
## Use `Query` as the default value
And now use it as the default value of your parameter, setting the parameter `max_length` to 50:
-```Python hl_lines="9"
-{!../../../docs_src/query_params_str_validations/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/query_params_str_validations/tutorial002.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/query_params_str_validations/tutorial002_py310.py!}
+ ```
As we have to replace the default value `None` with `Query(None)`, the first parameter to `Query` serves the same purpose of defining that default value.
@@ -49,10 +73,22 @@ q: Optional[str] = Query(None)
q: Optional[str] = None
```
+And in Python 3.10 and above:
+
+```Python
+q: str | None = Query(None)
+```
+
+...makes the parameter optional, the same as:
+
+```Python
+q: str | None = None
+```
+
But it declares it explicitly as being a query parameter.
!!! info
- Have in mind that FastAPI cares about the part:
+ Have in mind that the most important part to make a parameter optional is the part:
```Python
= None
@@ -64,9 +100,9 @@ But it declares it explicitly as being a query parameter.
= Query(None)
```
- and will use that `None` to detect that the query parameter is not required.
+ as it will use that `None` as the default value, and that way make the parameter **not required**.
- The `Optional` part is only to allow your editor to provide better support.
+ The `Optional` part allows your editor to provide better support, but it is not what tells FastAPI that this parameter is not required.
Then, we can pass more parameters to `Query`. In this case, the `max_length` parameter that applies to strings:
@@ -80,17 +116,33 @@ This will validate the data, show a clear error when the data is not valid, and
You can also add a parameter `min_length`:
-```Python hl_lines="9"
-{!../../../docs_src/query_params_str_validations/tutorial003.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/query_params_str_validations/tutorial003.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/query_params_str_validations/tutorial003_py310.py!}
+ ```
## Add regular expressions
You can define a regular expression that the parameter should match:
-```Python hl_lines="10"
-{!../../../docs_src/query_params_str_validations/tutorial004.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="10"
+ {!> ../../../docs_src/query_params_str_validations/tutorial004.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="8"
+ {!> ../../../docs_src/query_params_str_validations/tutorial004_py310.py!}
+ ```
This specific regular expression checks that the received parameter value:
@@ -152,9 +204,23 @@ When you define a query parameter explicitly with `Query` you can also declare i
For example, to declare a query parameter `q` that can appear multiple times in the URL, you can write:
-```Python hl_lines="9"
-{!../../../docs_src/query_params_str_validations/tutorial011.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/query_params_str_validations/tutorial011.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/query_params_str_validations/tutorial011_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/query_params_str_validations/tutorial011_py310.py!}
+ ```
Then, with a URL like:
@@ -186,9 +252,17 @@ The interactive API docs will update accordingly, to allow multiple values:
And you can also define a default `list` of values if none are provided:
-```Python hl_lines="9"
-{!../../../docs_src/query_params_str_validations/tutorial012.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/query_params_str_validations/tutorial012.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/query_params_str_validations/tutorial012_py39.py!}
+ ```
If you go to:
@@ -209,7 +283,7 @@ the default of `q` will be: `["foo", "bar"]` and your response will be:
#### Using `list`
-You can also use `list` directly instead of `List[str]`:
+You can also use `list` directly instead of `List[str]` (or `list[str]` in Python 3.9+):
```Python hl_lines="7"
{!../../../docs_src/query_params_str_validations/tutorial013.py!}
@@ -233,15 +307,31 @@ That information will be included in the generated OpenAPI and used by the docum
You can add a `title`:
-```Python hl_lines="10"
-{!../../../docs_src/query_params_str_validations/tutorial007.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="10"
+ {!> ../../../docs_src/query_params_str_validations/tutorial007.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/query_params_str_validations/tutorial007_py310.py!}
+ ```
And a `description`:
-```Python hl_lines="13"
-{!../../../docs_src/query_params_str_validations/tutorial008.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="13"
+ {!> ../../../docs_src/query_params_str_validations/tutorial008.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="12"
+ {!> ../../../docs_src/query_params_str_validations/tutorial008_py310.py!}
+ ```
## Alias parameters
@@ -261,9 +351,17 @@ But you still need it to be exactly `item-query`...
Then you can declare an `alias`, and that alias is what will be used to find the parameter value:
-```Python hl_lines="9"
-{!../../../docs_src/query_params_str_validations/tutorial009.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/query_params_str_validations/tutorial009.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/query_params_str_validations/tutorial009_py310.py!}
+ ```
## Deprecating parameters
@@ -273,14 +371,38 @@ You have to leave it there a while because there are clients using it, but you w
Then pass the parameter `deprecated=True` to `Query`:
-```Python hl_lines="18"
-{!../../../docs_src/query_params_str_validations/tutorial010.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="18"
+ {!> ../../../docs_src/query_params_str_validations/tutorial010.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="17"
+ {!> ../../../docs_src/query_params_str_validations/tutorial010_py310.py!}
+ ```
The docs will show it like this:
+## Exclude from OpenAPI
+
+To exclude a query parameter from the generated OpenAPI schema (and thus, from the automatic documentation systems), set the parameter `include_in_schema` of `Query` to `False`:
+
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="10"
+ {!> ../../../docs_src/query_params_str_validations/tutorial014.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/query_params_str_validations/tutorial014_py310.py!}
+ ```
+
## Recap
You can declare additional validations and metadata for your parameters.
diff --git a/docs/en/docs/tutorial/query-params.md b/docs/en/docs/tutorial/query-params.md
index 4401745a..eec55502 100644
--- a/docs/en/docs/tutorial/query-params.md
+++ b/docs/en/docs/tutorial/query-params.md
@@ -63,27 +63,38 @@ The parameter values in your function will be:
The same way, you can declare optional query parameters, by setting their default to `None`:
-```Python hl_lines="9"
-{!../../../docs_src/query_params/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/query_params/tutorial002.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/query_params/tutorial002_py310.py!}
+ ```
In this case, the function parameter `q` will be optional, and will be `None` by default.
!!! check
Also notice that **FastAPI** is smart enough to notice that the path parameter `item_id` is a path parameter and `q` is not, so, it's a query parameter.
-!!! note
- FastAPI will know that `q` is optional because of the `= None`.
-
- The `Optional` in `Optional[str]` is not used by FastAPI (FastAPI will only use the `str` part), but the `Optional[str]` will let your editor help you finding errors in your code.
-
## Query parameter type conversion
You can also declare `bool` types, and they will be converted:
-```Python hl_lines="9"
-{!../../../docs_src/query_params/tutorial003.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/query_params/tutorial003.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/query_params/tutorial003_py310.py!}
+ ```
In this case, if you go to:
@@ -126,9 +137,17 @@ And you don't have to declare them in any specific order.
They will be detected by name:
-```Python hl_lines="8 10"
-{!../../../docs_src/query_params/tutorial004.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="8 10"
+ {!> ../../../docs_src/query_params/tutorial004.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="6 8"
+ {!> ../../../docs_src/query_params/tutorial004_py310.py!}
+ ```
## Required query parameters
@@ -184,9 +203,17 @@ http://127.0.0.1:8000/items/foo-item?needy=sooooneedy
And of course, you can define some parameters as required, some as having a default value, and some entirely optional:
-```Python hl_lines="10"
-{!../../../docs_src/query_params/tutorial006.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="10"
+ {!> ../../../docs_src/query_params/tutorial006.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="8"
+ {!> ../../../docs_src/query_params/tutorial006_py310.py!}
+ ```
In this case, there are 3 query parameters:
diff --git a/docs/en/docs/tutorial/request-files.md b/docs/en/docs/tutorial/request-files.md
index 1619d6d2..3ca471a9 100644
--- a/docs/en/docs/tutorial/request-files.md
+++ b/docs/en/docs/tutorial/request-files.md
@@ -17,7 +17,7 @@ Import `File` and `UploadFile` from `fastapi`:
{!../../../docs_src/request_files/tutorial001.py!}
```
-## Define `File` parameters
+## Define `File` Parameters
Create file parameters the same way you would for `Body` or `Form`:
@@ -41,9 +41,9 @@ Have in mind that this means that the whole contents will be stored in memory. T
But there are several cases in which you might benefit from using `UploadFile`.
-## `File` parameters with `UploadFile`
+## File Parameters with `UploadFile`
-Define a `File` parameter with a type of `UploadFile`:
+Define a file parameter with a type of `UploadFile`:
```Python hl_lines="12"
{!../../../docs_src/request_files/tutorial001.py!}
@@ -51,6 +51,7 @@ Define a `File` parameter with a type of `UploadFile`:
Using `UploadFile` has several advantages over `bytes`:
+* You don't have to use `File()` in the default value of the parameter.
* It uses a "spooled" file:
* A file stored in memory up to a maximum size limit, and after passing this limit it will be stored in disk.
* This means that it will work well for large files like images, videos, large binaries, etc. without consuming all the memory.
@@ -113,32 +114,73 @@ The way HTML forms (``) sends the data to the server normally uses
This is not a limitation of **FastAPI**, it's part of the HTTP protocol.
-## Multiple file uploads
+## Optional File Upload
+
+You can make a file optional by using standard type annotations and setting a default value of `None`:
+
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9 17"
+ {!> ../../../docs_src/request_files/tutorial001_02.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="7 14"
+ {!> ../../../docs_src/request_files/tutorial001_02_py310.py!}
+ ```
+
+## `UploadFile` with Additional Metadata
+
+You can also use `File()` with `UploadFile`, for example, to set additional metadata:
+
+```Python hl_lines="13"
+{!../../../docs_src/request_files/tutorial001_03.py!}
+```
+
+## Multiple File Uploads
It's possible to upload several files at the same time.
They would be associated to the same "form field" sent using "form data".
-To use that, declare a `List` of `bytes` or `UploadFile`:
+To use that, declare a list of `bytes` or `UploadFile`:
-```Python hl_lines="10 15"
-{!../../../docs_src/request_files/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="10 15"
+ {!> ../../../docs_src/request_files/tutorial002.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="8 13"
+ {!> ../../../docs_src/request_files/tutorial002_py39.py!}
+ ```
You will receive, as declared, a `list` of `bytes` or `UploadFile`s.
-!!! note
- Notice that, as of 2019-04-14, Swagger UI doesn't support multiple file uploads in the same form field. For more information, check #4276 and #3641.
-
- Nevertheless, **FastAPI** is already compatible with it, using the standard OpenAPI.
-
- So, whenever Swagger UI supports multi-file uploads, or any other tools that supports OpenAPI, they will be compatible with **FastAPI**.
-
!!! note "Technical Details"
You could also use `from starlette.responses import HTMLResponse`.
**FastAPI** provides the same `starlette.responses` as `fastapi.responses` just as a convenience for you, the developer. But most of the available responses come directly from Starlette.
+### Multiple File Uploads with Additional Metadata
+
+And the same way as before, you can use `File()` to set additional parameters, even for `UploadFile`:
+
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="18"
+ {!> ../../../docs_src/request_files/tutorial003.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="16"
+ {!> ../../../docs_src/request_files/tutorial003_py39.py!}
+ ```
+
## Recap
-Use `File` to declare files to be uploaded as input parameters (as form data).
+Use `File`, `bytes`, and `UploadFile` to declare files to be uploaded in the request, sent as form data.
diff --git a/docs/en/docs/tutorial/response-model.md b/docs/en/docs/tutorial/response-model.md
index fdf66113..c751a925 100644
--- a/docs/en/docs/tutorial/response-model.md
+++ b/docs/en/docs/tutorial/response-model.md
@@ -8,9 +8,23 @@ You can declare the model used for the response with the parameter `response_mod
* `@app.delete()`
* etc.
-```Python hl_lines="17"
-{!../../../docs_src/response_model/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="17"
+ {!> ../../../docs_src/response_model/tutorial001.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="17"
+ {!> ../../../docs_src/response_model/tutorial001_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="15"
+ {!> ../../../docs_src/response_model/tutorial001_py310.py!}
+ ```
!!! note
Notice that `response_model` is a parameter of the "decorator" method (`get`, `post`, etc). Not of your *path operation function*, like all the parameters and body.
@@ -35,19 +49,35 @@ But most importantly:
Here we are declaring a `UserIn` model, it will contain a plaintext password:
-```Python hl_lines="9 11"
-{!../../../docs_src/response_model/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9 11"
+ {!> ../../../docs_src/response_model/tutorial002.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7 9"
+ {!> ../../../docs_src/response_model/tutorial002_py310.py!}
+ ```
And we are using this model to declare our input and the same model to declare our output:
-```Python hl_lines="17-18"
-{!../../../docs_src/response_model/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="17-18"
+ {!> ../../../docs_src/response_model/tutorial002.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="15-16"
+ {!> ../../../docs_src/response_model/tutorial002_py310.py!}
+ ```
Now, whenever a browser is creating a user with a password, the API will return the same password in the response.
-In this case, it might not be a problem, because the user himself is sending the password.
+In this case, it might not be a problem, because the user themself is sending the password.
But if we use the same model for another *path operation*, we could be sending our user's passwords to every client.
@@ -58,21 +88,45 @@ But if we use the same model for another *path operation*, we could be sending o
We can instead create an input model with the plaintext password and an output model without it:
-```Python hl_lines="9 11 16"
-{!../../../docs_src/response_model/tutorial003.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9 11 16"
+ {!> ../../../docs_src/response_model/tutorial003.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7 9 14"
+ {!> ../../../docs_src/response_model/tutorial003_py310.py!}
+ ```
Here, even though our *path operation function* is returning the same input user that contains the password:
-```Python hl_lines="24"
-{!../../../docs_src/response_model/tutorial003.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="24"
+ {!> ../../../docs_src/response_model/tutorial003.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="22"
+ {!> ../../../docs_src/response_model/tutorial003_py310.py!}
+ ```
...we declared the `response_model` to be our model `UserOut`, that doesn't include the password:
-```Python hl_lines="22"
-{!../../../docs_src/response_model/tutorial003.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="22"
+ {!> ../../../docs_src/response_model/tutorial003.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="20"
+ {!> ../../../docs_src/response_model/tutorial003_py310.py!}
+ ```
So, **FastAPI** will take care of filtering out all the data that is not declared in the output model (using Pydantic).
@@ -90,9 +144,23 @@ And both models will be used for the interactive API documentation:
Your response model could have default values, like:
-```Python hl_lines="11 13-14"
-{!../../../docs_src/response_model/tutorial004.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="11 13-14"
+ {!> ../../../docs_src/response_model/tutorial004.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="11 13-14"
+ {!> ../../../docs_src/response_model/tutorial004_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="9 11-12"
+ {!> ../../../docs_src/response_model/tutorial004_py310.py!}
+ ```
* `description: Optional[str] = None` has a default of `None`.
* `tax: float = 10.5` has a default of `10.5`.
@@ -106,9 +174,23 @@ For example, if you have models with many optional attributes in a NoSQL databas
You can set the *path operation decorator* parameter `response_model_exclude_unset=True`:
-```Python hl_lines="24"
-{!../../../docs_src/response_model/tutorial004.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="24"
+ {!> ../../../docs_src/response_model/tutorial004.py!}
+ ```
+
+=== "Python 3.9 and above"
+
+ ```Python hl_lines="24"
+ {!> ../../../docs_src/response_model/tutorial004_py39.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="22"
+ {!> ../../../docs_src/response_model/tutorial004_py310.py!}
+ ```
and those default values won't be included in the response, only the values actually set.
@@ -185,9 +267,17 @@ This can be used as a quick shortcut if you have only one Pydantic model and wan
This also applies to `response_model_by_alias` that works similarly.
-```Python hl_lines="31 37"
-{!../../../docs_src/response_model/tutorial005.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="31 37"
+ {!> ../../../docs_src/response_model/tutorial005.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="29 35"
+ {!> ../../../docs_src/response_model/tutorial005_py310.py!}
+ ```
!!! tip
The syntax `{"name", "description"}` creates a `set` with those two values.
@@ -198,9 +288,17 @@ This can be used as a quick shortcut if you have only one Pydantic model and wan
If you forget to use a `set` and use a `list` or `tuple` instead, FastAPI will still convert it to a `set` and it will work correctly:
-```Python hl_lines="31 37"
-{!../../../docs_src/response_model/tutorial006.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="31 37"
+ {!> ../../../docs_src/response_model/tutorial006.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="29 35"
+ {!> ../../../docs_src/response_model/tutorial006_py310.py!}
+ ```
## Recap
diff --git a/docs/en/docs/tutorial/schema-extra-example.md b/docs/en/docs/tutorial/schema-extra-example.md
index 47ee8503..c69df51d 100644
--- a/docs/en/docs/tutorial/schema-extra-example.md
+++ b/docs/en/docs/tutorial/schema-extra-example.md
@@ -8,9 +8,17 @@ Here are several ways to do it.
You can declare an `example` for a Pydantic model using `Config` and `schema_extra`, as described in Pydantic's docs: Schema customization:
-```Python hl_lines="15-23"
-{!../../../docs_src/schema_extra_example/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="15-23"
+ {!> ../../../docs_src/schema_extra_example/tutorial001.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="13-21"
+ {!> ../../../docs_src/schema_extra_example/tutorial001_py310.py!}
+ ```
That extra info will be added as-is to the output **JSON Schema** for that model, and it will be used in the API docs.
@@ -25,9 +33,17 @@ When using `Field()` with Pydantic models, you can also declare extra info for t
You can use this to add `example` for each field:
-```Python hl_lines="4 10-13"
-{!../../../docs_src/schema_extra_example/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="4 10-13"
+ {!> ../../../docs_src/schema_extra_example/tutorial002.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="2 8-11"
+ {!> ../../../docs_src/schema_extra_example/tutorial002_py310.py!}
+ ```
!!! warning
Keep in mind that those extra arguments passed won't add any validation, only extra information, for documentation purposes.
@@ -50,9 +66,17 @@ you can also declare a data `example` or a group of `examples` with additional i
Here we pass an `example` of the data expected in `Body()`:
-```Python hl_lines="21-26"
-{!../../../docs_src/schema_extra_example/tutorial003.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="21-26"
+ {!> ../../../docs_src/schema_extra_example/tutorial003.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="19-24"
+ {!> ../../../docs_src/schema_extra_example/tutorial003_py310.py!}
+ ```
### Example in the docs UI
@@ -73,9 +97,17 @@ Each specific example `dict` in the `examples` can contain:
* `value`: This is the actual example shown, e.g. a `dict`.
* `externalValue`: alternative to `value`, a URL pointing to the example. Although this might not be supported by as many tools as `value`.
-```Python hl_lines="22-48"
-{!../../../docs_src/schema_extra_example/tutorial004.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="22-48"
+ {!> ../../../docs_src/schema_extra_example/tutorial004.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="20-46"
+ {!> ../../../docs_src/schema_extra_example/tutorial004_py310.py!}
+ ```
### Examples in the docs UI
@@ -88,7 +120,7 @@ With `examples` added to `Body()` the `/docs` would look like:
!!! warning
These are very technical details about the standards **JSON Schema** and **OpenAPI**.
- If the ideas above already work for you, that might me enough, and you probably don't need these details, feel free to skip them.
+ If the ideas above already work for you, that might be enough, and you probably don't need these details, feel free to skip them.
When you add an example inside of a Pydantic model, using `schema_extra` or `Field(example="something")` that example is added to the **JSON Schema** for that Pydantic model.
diff --git a/docs/en/docs/tutorial/security/first-steps.md b/docs/en/docs/tutorial/security/first-steps.md
index 360d85ae..45ffaab9 100644
--- a/docs/en/docs/tutorial/security/first-steps.md
+++ b/docs/en/docs/tutorial/security/first-steps.md
@@ -121,7 +121,7 @@ When we create an instance of the `OAuth2PasswordBearer` class we pass in the `t
```
!!! tip
- here `tokenUrl="token"` refers to a relative URL `token` that we haven't created yet. As it's a relative URL, it's equivalent to `./token`.
+ Here `tokenUrl="token"` refers to a relative URL `token` that we haven't created yet. As it's a relative URL, it's equivalent to `./token`.
Because we are using a relative URL, if your API was located at `https://example.com/`, then it would refer to `https://example.com/token`. But if your API was located at `https://example.com/api/v1/`, then it would refer to `https://example.com/api/v1/token`.
diff --git a/docs/en/docs/tutorial/security/get-current-user.md b/docs/en/docs/tutorial/security/get-current-user.md
index f50b69ac..13e86721 100644
--- a/docs/en/docs/tutorial/security/get-current-user.md
+++ b/docs/en/docs/tutorial/security/get-current-user.md
@@ -16,9 +16,17 @@ First, let's create a Pydantic user model.
The same way we use Pydantic to declare bodies, we can use it anywhere else:
-```Python hl_lines="5 12-16"
-{!../../../docs_src/security/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="5 12-16"
+ {!> ../../../docs_src/security/tutorial002.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="3 10-14"
+ {!> ../../../docs_src/security/tutorial002_py310.py!}
+ ```
## Create a `get_current_user` dependency
@@ -30,25 +38,49 @@ Remember that dependencies can have sub-dependencies?
The same as we were doing before in the *path operation* directly, our new dependency `get_current_user` will receive a `token` as a `str` from the sub-dependency `oauth2_scheme`:
-```Python hl_lines="25"
-{!../../../docs_src/security/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="25"
+ {!> ../../../docs_src/security/tutorial002.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="23"
+ {!> ../../../docs_src/security/tutorial002_py310.py!}
+ ```
## Get the user
`get_current_user` will use a (fake) utility function we created, that takes a token as a `str` and returns our Pydantic `User` model:
-```Python hl_lines="19-22 26-27"
-{!../../../docs_src/security/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="19-22 26-27"
+ {!> ../../../docs_src/security/tutorial002.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="17-20 24-25"
+ {!> ../../../docs_src/security/tutorial002_py310.py!}
+ ```
## Inject the current user
So now we can use the same `Depends` with our `get_current_user` in the *path operation*:
-```Python hl_lines="31"
-{!../../../docs_src/security/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="31"
+ {!> ../../../docs_src/security/tutorial002.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="29"
+ {!> ../../../docs_src/security/tutorial002_py310.py!}
+ ```
Notice that we declare the type of `current_user` as the Pydantic model `User`.
@@ -64,7 +96,6 @@ This will help us inside of the function with all the completion and type checks
We are not restricted to having only one dependency that can return that type of data.
-
## Other models
You can now get the current user directly in the *path operation functions* and deal with the security mechanisms at the **Dependency Injection** level, using `Depends`.
@@ -81,10 +112,9 @@ You actually don't have users that log in to your application but robots, bots,
Just use any kind of model, any kind of class, any kind of database that you need for your application. **FastAPI** has you covered with the dependency injection system.
-
## Code size
-This example might seem verbose. Have in mind that we are mixing security, data models utility functions and *path operations* in the same file.
+This example might seem verbose. Have in mind that we are mixing security, data models, utility functions and *path operations* in the same file.
But here's the key point.
@@ -98,9 +128,17 @@ And all of them (or any portion of them that you want) can take the advantage of
And all these thousands of *path operations* can be as small as 3 lines:
-```Python hl_lines="30-32"
-{!../../../docs_src/security/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="30-32"
+ {!> ../../../docs_src/security/tutorial002.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="28-30"
+ {!> ../../../docs_src/security/tutorial002_py310.py!}
+ ```
## Recap
diff --git a/docs/en/docs/tutorial/security/oauth2-jwt.md b/docs/en/docs/tutorial/security/oauth2-jwt.md
index fda722cd..09557f1a 100644
--- a/docs/en/docs/tutorial/security/oauth2-jwt.md
+++ b/docs/en/docs/tutorial/security/oauth2-jwt.md
@@ -33,7 +33,7 @@ We need to install `python-jose` to generate and verify the JWT tokens in Python
requests - Requerido si quieres usar el `TestClient`.
-* aiofiles - Requerido si quieres usar `FileResponse` o `StaticFiles`.
* jinja2 - Requerido si quieres usar la configuración por defecto de templates.
* python-multipart - Requerido si quieres dar soporte a "parsing" de formularios, con `request.form()`.
* itsdangerous - Requerido para dar soporte a `SessionMiddleware`.
diff --git a/docs/es/docs/tutorial/first-steps.md b/docs/es/docs/tutorial/first-steps.md
new file mode 100644
index 00000000..79f0a51c
--- /dev/null
+++ b/docs/es/docs/tutorial/first-steps.md
@@ -0,0 +1,333 @@
+# Primeros pasos
+
+Un archivo muy simple de FastAPI podría verse así:
+
+```Python
+{!../../../docs_src/first_steps/tutorial001.py!}
+```
+
+Copia eso a un archivo `main.py`.
+
+Corre el servidor en vivo:
+
+get
+
+!!! info "Información sobre `@decorator`"
+ Esa sintaxis `@algo` se llama un "decorador" en Python.
+
+ Lo pones encima de una función. Es como un lindo sombrero decorado (creo que de ahí salió el concepto).
+
+ Un "decorador" toma la función que tiene debajo y hace algo con ella.
+
+ En nuestro caso, este decorador le dice a **FastAPI** que la función que está debajo corresponde al **path** `/` con una **operación** `get`.
+
+ Es el "**decorador de operaciones de path**".
+
+También puedes usar las otras operaciones:
+
+* `@app.post()`
+* `@app.put()`
+* `@app.delete()`
+
+y las más exóticas:
+
+* `@app.options()`
+* `@app.head()`
+* `@app.patch()`
+* `@app.trace()`
+
+!!! tip "Consejo"
+ Tienes la libertad de usar cada operación (método de HTTP) como quieras.
+
+ **FastAPI** no impone ningún significado específico.
+
+ La información que está presentada aquí es una guía, no un requerimiento.
+
+ Por ejemplo, cuando usas GraphQL normalmente realizas todas las acciones usando únicamente operaciones `POST`.
+
+### Paso 4: define la **función de la operación de path**
+
+Esta es nuestra "**función de la operación de path**":
+
+* **path**: es `/`.
+* **operación**: es `get`.
+* **función**: es la función debajo del "decorador" (debajo de `@app.get("/")`).
+
+```Python hl_lines="7"
+{!../../../docs_src/first_steps/tutorial001.py!}
+```
+
+Esto es una función de Python.
+
+Esta función será llamada por **FastAPI** cada vez que reciba un request en la URL "`/`" usando una operación `GET`.
+
+En este caso es una función `async`.
+
+---
+
+También podrías definirla como una función normal, en vez de `async def`:
+
+```Python hl_lines="7"
+{!../../../docs_src/first_steps/tutorial003.py!}
+```
+
+!!! note "Nota"
+ Si no sabes la diferencia, revisa el [Async: *"¿Tienes prisa?"*](../async.md#in-a-hurry){.internal-link target=_blank}.
+
+### Paso 5: devuelve el contenido
+
+```Python hl_lines="8"
+{!../../../docs_src/first_steps/tutorial001.py!}
+```
+
+Puedes devolver `dict`, `list`, valores singulares como un `str`, `int`, etc.
+
+También puedes devolver modelos de Pydantic (ya verás más sobre esto más adelante).
+
+Hay muchos objetos y modelos que pueden ser convertidos automáticamente a JSON (incluyendo ORMs, etc.). Intenta usar tus favoritos, es muy probable que ya tengan soporte.
+
+## Repaso
+
+* Importa `FastAPI`.
+* Crea un instance de `app`.
+* Escribe un **decorador de operación de path** (como `@app.get("/")`).
+* Escribe una **función de la operación de path** (como `def root(): ...` arriba).
+* Corre el servidor de desarrollo (como `uvicorn main:app --reload`).
diff --git a/docs/es/docs/tutorial/index.md b/docs/es/docs/tutorial/index.md
index 14ce3922..9cbdb272 100644
--- a/docs/es/docs/tutorial/index.md
+++ b/docs/es/docs/tutorial/index.md
@@ -4,9 +4,7 @@ Este tutorial te muestra cómo usar **FastAPI** con la mayoría de sus caracter
Cada sección se basa gradualmente en las anteriores, pero está estructurada en temas separados, así puedes ir directamente a cualquier tema en concreto para resolver tus necesidades específicas sobre la API.
-También está diseñado para funcionar como una referencia futura.
-
-Para que puedas volver y ver exactamente lo que necesitas.
+Funciona también como una referencia futura, para que puedas volver y ver exactamente lo que necesitas.
## Ejecuta el código
diff --git a/docs/es/docs/tutorial/path-params.md b/docs/es/docs/tutorial/path-params.md
new file mode 100644
index 00000000..6432de1c
--- /dev/null
+++ b/docs/es/docs/tutorial/path-params.md
@@ -0,0 +1,244 @@
+# Parámetros de path
+
+Puedes declarar los "parámetros" o "variables" con la misma sintaxis que usan los format strings de Python:
+
+```Python hl_lines="6-7"
+{!../../../docs_src/path_params/tutorial001.py!}
+```
+
+El valor del parámetro de path `item_id` será pasado a tu función como el argumento `item_id`.
+
+Entonces, si corres este ejemplo y vas a http://127.0.0.1:8000/items/foo, verás una respuesta de:
+
+```JSON
+{"item_id":"foo"}
+```
+
+## Parámetros de path con tipos
+
+Puedes declarar el tipo de un parámetro de path en la función usando las anotaciones de tipos estándar de Python:
+
+```Python hl_lines="7"
+{!../../../docs_src/path_params/tutorial002.py!}
+```
+
+En este caso, `item_id` es declarado como un `int`.
+
+!!! check "Revisa"
+ Esto te dará soporte en el editor dentro de tu función, con chequeos de errores, autocompletado, etc.
+
+## Conversión de datos
+
+Si corres este ejemplo y abres tu navegador en http://127.0.0.1:8000/items/3 verás una respuesta de:
+
+```JSON
+{"item_id":3}
+```
+
+!!! check "Revisa"
+ Observa que el valor que recibió (y devolvió) tu función es `3`, como un Python `int`, y no un string `"3"`.
+
+ Entonces, con esa declaración de tipos **FastAPI** te da "parsing" automático del request.
+
+## Validación de datos
+
+Pero si abres tu navegador en http://127.0.0.1:8000/items/foo verás este lindo error de HTTP:
+
+```JSON
+{
+ "detail": [
+ {
+ "loc": [
+ "path",
+ "item_id"
+ ],
+ "msg": "value is not a valid integer",
+ "type": "type_error.integer"
+ }
+ ]
+}
+```
+
+debido a que el parámetro de path `item_id` tenía el valor `"foo"`, que no es un `int`.
+
+El mismo error aparecería si pasaras un `float` en vez de un `int` como en: http://127.0.0.1:8000/items/4.2
+
+!!! check "Revisa"
+ Así, con la misma declaración de tipo de Python, **FastAPI** te da validación de datos.
+
+ Observa que el error también muestra claramente el punto exacto en el que no pasó la validación.
+
+ Esto es increíblemente útil cuando estás desarrollando y debugging código que interactúa con tu API.
+
+## Documentación
+
+Cuando abras tu navegador en http://127.0.0.1:8000/docs verás la documentación automática e interactiva del API como:
+
+
+
+!!! check "Revisa"
+ Nuevamente, con la misma declaración de tipo de Python, **FastAPI** te da documentación automática e interactiva (integrándose con Swagger UI)
+
+ Observa que el parámetro de path está declarado como un integer.
+
+## Beneficios basados en estándares, documentación alternativa
+
+Debido a que el schema generado es del estándar OpenAPI hay muchas herramientas compatibles.
+
+Es por esto que **FastAPI** mismo provee una documentación alternativa de la API (usando ReDoc), a la que puedes acceder en http://127.0.0.1:8000/redoc:
+
+
+
+De la misma manera hay muchas herramientas compatibles. Incluyendo herramientas de generación de código para muchos lenguajes.
+
+## Pydantic
+
+Toda la validación de datos es realizada tras bastidores por Pydantic, así que obtienes todos sus beneficios. Así sabes que estás en buenas manos.
+
+Puedes usar las mismas declaraciones de tipos con `str`, `float`, `bool` y otros tipos de datos más complejos.
+
+Exploraremos varios de estos tipos en los próximos capítulos del tutorial.
+
+## El orden importa
+
+Cuando creas *operaciones de path* puedes encontrarte con situaciones en las que tengas un path fijo.
+
+Digamos algo como `/users/me` que sea para obtener datos del usuario actual.
+
+... y luego puedes tener el path `/users/{user_id}` para obtener los datos sobre un usuario específico asociados a un ID de usuario.
+
+Porque las *operaciones de path* son evaluadas en orden, tienes que asegurarte de que el path para `/users/me` sea declarado antes que el path para `/users/{user_id}`:
+
+```Python hl_lines="6 11"
+{!../../../docs_src/path_params/tutorial003.py!}
+```
+
+De otra manera el path para `/users/{user_id}` coincidiría también con `/users/me` "pensando" que está recibiendo el parámetro `user_id` con el valor `"me"`.
+
+## Valores predefinidos
+
+Si tienes una *operación de path* que recibe un *parámetro de path* pero quieres que los valores posibles del *parámetro de path* sean predefinidos puedes usar un `Enum` estándar de Python.
+
+### Crea una clase `Enum`
+
+Importa `Enum` y crea una sub-clase que herede desde `str` y desde `Enum`.
+
+Al heredar desde `str` la documentación de la API podrá saber que los valores deben ser de tipo `string` y podrá mostrarlos correctamente.
+
+Luego crea atributos de clase con valores fijos, que serán los valores disponibles válidos:
+
+```Python hl_lines="1 6-9"
+{!../../../docs_src/path_params/tutorial005.py!}
+```
+
+!!! info "Información"
+ Las Enumerations (o enums) están disponibles en Python desde la versión 3.4.
+
+!!! tip "Consejo"
+ Si lo estás dudando, "AlexNet", "ResNet", y "LeNet" son solo nombres de modelos de Machine Learning.
+
+### Declara un *parámetro de path*
+
+Luego, crea un *parámetro de path* con anotaciones de tipos usando la clase enum que creaste (`ModelName`):
+
+```Python hl_lines="16"
+{!../../../docs_src/path_params/tutorial005.py!}
+```
+
+### Revisa la documentación
+
+Debido a que los valores disponibles para el *parámetro de path* están predefinidos, la documentación interactiva los puede mostrar bien:
+
+
+
+### Trabajando con los *enumerations* de Python
+
+El valor del *parámetro de path* será un *enumeration member*.
+
+#### Compara *enumeration members*
+
+Puedes compararlo con el *enumeration member* en el enum (`ModelName`) que creaste:
+
+```Python hl_lines="17"
+{!../../../docs_src/path_params/tutorial005.py!}
+```
+
+#### Obtén el *enumeration value*
+
+Puedes obtener el valor exacto (un `str` en este caso) usando `model_name.value`, o en general, `your_enum_member.value`:
+
+```Python hl_lines="20"
+{!../../../docs_src/path_params/tutorial005.py!}
+```
+
+!!! tip "Consejo"
+ También podrías obtener el valor `"lenet"` con `ModelName.lenet.value`.
+
+#### Devuelve *enumeration members*
+
+Puedes devolver *enum members* desde tu *operación de path* inclusive en un body de JSON anidado (por ejemplo, un `dict`).
+
+Ellos serán convertidos a sus valores correspondientes (strings en este caso) antes de devolverlos al cliente:
+
+```Python hl_lines="18 21 23"
+{!../../../docs_src/path_params/tutorial005.py!}
+```
+
+En tu cliente obtendrás una respuesta en JSON como:
+
+```JSON
+{
+ "model_name": "alexnet",
+ "message": "Deep Learning FTW!"
+}
+```
+
+## Parámetros de path parameters que contienen paths
+
+Digamos que tienes una *operación de path* con un path `/files/{file_path}`.
+
+Pero necesitas que el mismo `file_path` contenga un path como `home/johndoe/myfile.txt`.
+
+Entonces, la URL para ese archivo sería algo como: `/files/home/johndoe/myfile.txt`.
+
+### Soporte de OpenAPI
+
+OpenAPI no soporta una manera de declarar un *parámetro de path* que contenga un path, dado que esto podría llevar a escenarios que son difíciles de probar y definir.
+
+Sin embargo, lo puedes hacer en **FastAPI** usando una de las herramientas internas de Starlette.
+
+La documentación seguirá funcionando, aunque no añadirá ninguna información diciendo que el parámetro debería contener un path.
+
+### Convertidor de path
+
+Usando una opción directamente desde Starlette puedes declarar un *parámetro de path* que contenga un path usando una URL como:
+
+```
+/files/{file_path:path}
+```
+
+En este caso el nombre del parámetro es `file_path` y la última parte, `:path`, le dice que el parámetro debería coincidir con cualquier path.
+
+Entonces lo puedes usar con:
+
+```Python hl_lines="6"
+{!../../../docs_src/path_params/tutorial004.py!}
+```
+
+!!! tip "Consejo"
+ Podrías necesitar que el parámetro contenga `/home/johndoe/myfile.txt` con un slash inicial (`/`).
+
+ En este caso la URL sería `/files//home/johndoe/myfile.txt` con un slash doble (`//`) entre `files` y `home`.
+
+## Repaso
+
+Con **FastAPI**, usando declaraciones de tipo de Python intuitivas y estándares, obtienes:
+
+* Soporte en el editor: chequeos de errores, auto-completado, etc.
+* "Parsing" de datos
+* Validación de datos
+* Anotación de la API y documentación automática
+
+Solo tienes que declararlos una vez.
+
+Esa es probablemente la principal ventaja visible de **FastAPI** sobre otros frameworks alternativos (aparte del rendimiento puro).
diff --git a/docs/es/docs/tutorial/query-params.md b/docs/es/docs/tutorial/query-params.md
new file mode 100644
index 00000000..69caee6e
--- /dev/null
+++ b/docs/es/docs/tutorial/query-params.md
@@ -0,0 +1,197 @@
+# Parámetros de query
+
+Cuando declaras otros parámetros de la función que no hacen parte de los parámetros de path estos se interpretan automáticamente como parámetros de "query".
+
+```Python hl_lines="9"
+{!../../../docs_src/query_params/tutorial001.py!}
+```
+
+El query es el conjunto de pares de key-value que van después del `?` en la URL, separados por caracteres `&`.
+
+Por ejemplo, en la URL:
+
+```
+http://127.0.0.1:8000/items/?skip=0&limit=10
+```
+
+...los parámetros de query son:
+
+* `skip`: con un valor de `0`
+* `limit`: con un valor de `10`
+
+Dado que son parte de la URL son strings "naturalmente".
+
+Pero cuando los declaras con tipos de Python (en el ejemplo arriba, como `int`) son convertidos a ese tipo y son validados con él.
+
+Todo el proceso que aplicaba a los parámetros de path también aplica a los parámetros de query:
+
+* Soporte del editor (obviamente)
+* "Parsing" de datos
+* Validación de datos
+* Documentación automática
+
+## Configuraciones por defecto
+
+Como los parámetros de query no están fijos en una parte del path pueden ser opcionales y pueden tener valores por defecto.
+
+El ejemplo arriba tiene `skip=0` y `limit=10` como los valores por defecto.
+
+Entonces, si vas a la URL:
+
+```
+http://127.0.0.1:8000/items/
+```
+
+Sería lo mismo que ir a:
+
+```
+http://127.0.0.1:8000/items/?skip=0&limit=10
+```
+
+Pero, si por ejemplo vas a:
+
+```
+http://127.0.0.1:8000/items/?skip=20
+```
+
+Los valores de los parámetros en tu función serán:
+
+* `skip=20`: porque lo definiste en la URL
+* `limit=10`: porque era el valor por defecto
+
+## Parámetros opcionales
+
+Del mismo modo puedes declarar parámetros de query opcionales definiendo el valor por defecto como `None`:
+
+```Python hl_lines="9"
+{!../../../docs_src/query_params/tutorial002.py!}
+```
+
+En este caso el parámetro de la función `q` será opcional y será `None` por defecto.
+
+!!! check "Revisa"
+ También puedes notar que **FastAPI** es lo suficientemente inteligente para darse cuenta de que el parámetro de path `item_id` es un parámetro de path y que `q` no lo es, y por lo tanto es un parámetro de query.
+
+!!! note "Nota"
+ FastAPI sabrá que `q` es opcional por el `= None`.
+
+ El `Optional` en `Optional[str]` no es usado por FastAPI (FastAPI solo usará la parte `str`), pero el `Optional[str]` le permitirá a tu editor ayudarte a encontrar errores en tu código.
+
+## Conversión de tipos de parámetros de query
+
+También puedes declarar tipos `bool` y serán convertidos:
+
+```Python hl_lines="9"
+{!../../../docs_src/query_params/tutorial003.py!}
+```
+
+En este caso, si vas a:
+
+```
+http://127.0.0.1:8000/items/foo?short=1
+```
+
+o
+
+```
+http://127.0.0.1:8000/items/foo?short=True
+```
+
+o
+
+```
+http://127.0.0.1:8000/items/foo?short=true
+```
+
+o
+
+```
+http://127.0.0.1:8000/items/foo?short=on
+```
+
+o
+
+```
+http://127.0.0.1:8000/items/foo?short=yes
+```
+
+o cualquier otra variación (mayúsculas, primera letra en mayúscula, etc.) tu función verá el parámetro `short` con un valor `bool` de `True`. Si no, lo verá como `False`.
+
+## Múltiples parámetros de path y query
+
+Puedes declarar múltiples parámetros de path y parámetros de query al mismo tiempo. **FastAPI** sabe cuál es cuál.
+
+No los tienes que declarar en un orden específico.
+
+Serán detectados por nombre:
+
+```Python hl_lines="8 10"
+{!../../../docs_src/query_params/tutorial004.py!}
+```
+
+## Parámetros de query requeridos
+
+Cuando declaras un valor por defecto para los parámetros que no son de path (por ahora solo hemos visto parámetros de query), entonces no es requerido.
+
+Si no quieres añadir un valor específico sino solo hacerlo opcional, pon el valor por defecto como `None`.
+
+Pero cuando quieres hacer que un parámetro de query sea requerido, puedes simplemente no declararle un valor por defecto:
+
+```Python hl_lines="6-7"
+{!../../../docs_src/query_params/tutorial005.py!}
+```
+
+Aquí el parámetro de query `needy` es un parámetro de query requerido, del tipo `str`.
+
+Si abres tu navegador en una URL como:
+
+```
+http://127.0.0.1:8000/items/foo-item
+```
+
+...sin añadir el parámetro `needy` requerido, verás un error como:
+
+```JSON
+{
+ "detail": [
+ {
+ "loc": [
+ "query",
+ "needy"
+ ],
+ "msg": "field required",
+ "type": "value_error.missing"
+ }
+ ]
+}
+```
+
+Dado que `needy` es un parámetro requerido necesitarías declararlo en la URL:
+
+```
+http://127.0.0.1:8000/items/foo-item?needy=sooooneedy
+```
+
+...esto funcionaría:
+
+```JSON
+{
+ "item_id": "foo-item",
+ "needy": "sooooneedy"
+}
+```
+
+Por supuesto que también puedes definir algunos parámetros como requeridos, con un valor por defecto y otros completamente opcionales:
+
+```Python hl_lines="10"
+{!../../../docs_src/query_params/tutorial006.py!}
+```
+
+En este caso hay 3 parámetros de query:
+
+* `needy`, un `str` requerido.
+* `skip`, un `int` con un valor por defecto de `0`.
+* `limit`, un `int` opcional.
+
+!!! tip "Consejo"
+ También podrías usar los `Enum`s de la misma manera que con los [Parámetros de path](path-params.md#predefined-values){.internal-link target=_blank}.
diff --git a/docs/es/mkdocs.yml b/docs/es/mkdocs.yml
index 1554cc3e..eb7538cf 100644
--- a/docs/es/mkdocs.yml
+++ b/docs/es/mkdocs.yml
@@ -9,17 +9,18 @@ theme:
primary: teal
accent: amber
toggle:
- icon: material/lightbulb-outline
+ icon: material/lightbulb
name: Switch to light mode
- scheme: slate
primary: teal
accent: amber
toggle:
- icon: material/lightbulb
+ icon: material/lightbulb-outline
name: Switch to dark mode
features:
- search.suggest
- search.highlight
+ - content.tabs.link
icon:
repo: fontawesome/brands/github-alt
logo: https://fastapi.tiangolo.com/img/icon-white.svg
@@ -28,9 +29,6 @@ theme:
repo_name: tiangolo/fastapi
repo_url: https://github.com/tiangolo/fastapi
edit_uri: ''
-google_analytics:
-- UA-133183413-1
-- auto
plugins:
- search
- markdownextradata:
@@ -39,11 +37,16 @@ nav:
- FastAPI: index.md
- Languages:
- en: /
+ - az: /az/
+ - de: /de/
- es: /es/
+ - fa: /fa/
- fr: /fr/
+ - id: /id/
- it: /it/
- ja: /ja/
- ko: /ko/
+ - nl: /nl/
- pl: /pl/
- pt: /pt/
- ru: /ru/
@@ -55,6 +58,9 @@ nav:
- python-types.md
- Tutorial - Guía de Usuario:
- tutorial/index.md
+ - tutorial/first-steps.md
+ - tutorial/path-params.md
+ - tutorial/query-params.md
- Guía de Usuario Avanzada:
- advanced/index.md
- async.md
@@ -63,7 +69,7 @@ markdown_extensions:
permalink: true
- markdown.extensions.codehilite:
guess_lang: false
-- markdown_include.include:
+- mdx_include:
base_path: docs
- admonition
- codehilite
@@ -72,16 +78,20 @@ markdown_extensions:
custom_fences:
- name: mermaid
class: mermaid
- format: !!python/name:pymdownx.superfences.fence_div_format ''
-- pymdownx.tabbed
+ format: !!python/name:pymdownx.superfences.fence_code_format ''
+- pymdownx.tabbed:
+ alternate_style: true
extra:
+ analytics:
+ provider: google
+ property: UA-133183413-1
social:
- icon: fontawesome/brands/github-alt
link: https://github.com/tiangolo/fastapi
- icon: fontawesome/brands/discord
link: https://discord.gg/VQjSZaeJmf
- icon: fontawesome/brands/twitter
- link: https://twitter.com/tiangolo
+ link: https://twitter.com/fastapi
- icon: fontawesome/brands/linkedin
link: https://www.linkedin.com/in/tiangolo
- icon: fontawesome/brands/dev
@@ -93,16 +103,26 @@ extra:
alternate:
- link: /
name: en - English
+ - link: /az/
+ name: az
+ - link: /de/
+ name: de
- link: /es/
name: es - español
+ - link: /fa/
+ name: fa
- link: /fr/
name: fr - français
+ - link: /id/
+ name: id
- link: /it/
name: it - italiano
- link: /ja/
name: ja - 日本語
- link: /ko/
name: ko - 한국어
+ - link: /nl/
+ name: nl
- link: /pl/
name: pl
- link: /pt/
@@ -121,6 +141,5 @@ extra_css:
- https://fastapi.tiangolo.com/css/termynal.css
- https://fastapi.tiangolo.com/css/custom.css
extra_javascript:
-- https://unpkg.com/mermaid@8.4.6/dist/mermaid.min.js
- https://fastapi.tiangolo.com/js/termynal.js
- https://fastapi.tiangolo.com/js/custom.js
diff --git a/docs/fa/docs/index.md b/docs/fa/docs/index.md
new file mode 100644
index 00000000..0070de17
--- /dev/null
+++ b/docs/fa/docs/index.md
@@ -0,0 +1,468 @@
+
+{!../../../docs/missing-translation.md!}
+
+
+
++ FastAPI framework, high performance, easy to learn, fast to code, ready for production +
+ + +--- + +**Documentation**: https://fastapi.tiangolo.com + +**Source Code**: https://github.com/tiangolo/fastapi + +--- + +FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints. + +The key features are: + +* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance). + +* **Fast to code**: Increase the speed to develop features by about 200% to 300%. * +* **Fewer bugs**: Reduce about 40% of human (developer) induced errors. * +* **Intuitive**: Great editor support. Completion everywhere. Less time debugging. +* **Easy**: Designed to be easy to use and learn. Less time reading docs. +* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs. +* **Robust**: Get production-ready code. With automatic interactive documentation. +* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: OpenAPI (previously known as Swagger) and JSON Schema. + +* estimation based on tests on an internal development team, building production applications. + +## Sponsors + + + +{% if sponsors %} +{% for sponsor in sponsors.gold -%} +async def...uvicorn main:app --reload...ujson - for faster JSON "parsing".
+* email_validator - for email validation.
+
+Used by Starlette:
+
+* requests - Required if you want to use the `TestClient`.
+* jinja2 - Required if you want to use the default template configuration.
+* python-multipart - Required if you want to support form "parsing", with `request.form()`.
+* itsdangerous - Required for `SessionMiddleware` support.
+* pyyaml - Required for Starlette's `SchemaGenerator` support (you probably don't need it with FastAPI).
+* ujson - Required if you want to use `UJSONResponse`.
+
+Used by FastAPI / Starlette:
+
+* uvicorn - for the server that loads and serves your application.
+* orjson - Required if you want to use `ORJSONResponse`.
+
+You can install all of these with `pip install "fastapi[all]"`.
+
+## License
+
+This project is licensed under the terms of the MIT license.
diff --git a/docs/fa/mkdocs.yml b/docs/fa/mkdocs.yml
new file mode 100644
index 00000000..6fb3891b
--- /dev/null
+++ b/docs/fa/mkdocs.yml
@@ -0,0 +1,135 @@
+site_name: FastAPI
+site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production
+site_url: https://fastapi.tiangolo.com/fa/
+theme:
+ name: material
+ custom_dir: overrides
+ palette:
+ - scheme: default
+ primary: teal
+ accent: amber
+ toggle:
+ icon: material/lightbulb
+ name: Switch to light mode
+ - scheme: slate
+ primary: teal
+ accent: amber
+ toggle:
+ icon: material/lightbulb-outline
+ name: Switch to dark mode
+ features:
+ - search.suggest
+ - search.highlight
+ - content.tabs.link
+ icon:
+ repo: fontawesome/brands/github-alt
+ logo: https://fastapi.tiangolo.com/img/icon-white.svg
+ favicon: https://fastapi.tiangolo.com/img/favicon.png
+ language: fa
+repo_name: tiangolo/fastapi
+repo_url: https://github.com/tiangolo/fastapi
+edit_uri: ''
+plugins:
+- search
+- markdownextradata:
+ data: data
+nav:
+- FastAPI: index.md
+- Languages:
+ - en: /
+ - az: /az/
+ - de: /de/
+ - es: /es/
+ - fa: /fa/
+ - fr: /fr/
+ - id: /id/
+ - it: /it/
+ - ja: /ja/
+ - ko: /ko/
+ - nl: /nl/
+ - pl: /pl/
+ - pt: /pt/
+ - ru: /ru/
+ - sq: /sq/
+ - tr: /tr/
+ - uk: /uk/
+ - zh: /zh/
+markdown_extensions:
+- toc:
+ permalink: true
+- markdown.extensions.codehilite:
+ guess_lang: false
+- mdx_include:
+ base_path: docs
+- admonition
+- codehilite
+- extra
+- pymdownx.superfences:
+ custom_fences:
+ - name: mermaid
+ class: mermaid
+ format: !!python/name:pymdownx.superfences.fence_code_format ''
+- pymdownx.tabbed:
+ alternate_style: true
+extra:
+ analytics:
+ provider: google
+ property: UA-133183413-1
+ social:
+ - icon: fontawesome/brands/github-alt
+ link: https://github.com/tiangolo/fastapi
+ - icon: fontawesome/brands/discord
+ link: https://discord.gg/VQjSZaeJmf
+ - icon: fontawesome/brands/twitter
+ link: https://twitter.com/fastapi
+ - icon: fontawesome/brands/linkedin
+ link: https://www.linkedin.com/in/tiangolo
+ - icon: fontawesome/brands/dev
+ link: https://dev.to/tiangolo
+ - icon: fontawesome/brands/medium
+ link: https://medium.com/@tiangolo
+ - icon: fontawesome/solid/globe
+ link: https://tiangolo.com
+ alternate:
+ - link: /
+ name: en - English
+ - link: /az/
+ name: az
+ - link: /de/
+ name: de
+ - link: /es/
+ name: es - español
+ - link: /fa/
+ name: fa
+ - link: /fr/
+ name: fr - français
+ - link: /id/
+ name: id
+ - link: /it/
+ name: it - italiano
+ - link: /ja/
+ name: ja - 日本語
+ - link: /ko/
+ name: ko - 한국어
+ - link: /nl/
+ name: nl
+ - link: /pl/
+ name: pl
+ - link: /pt/
+ name: pt - português
+ - link: /ru/
+ name: ru - русский язык
+ - link: /sq/
+ name: sq - shqip
+ - link: /tr/
+ name: tr - Türkçe
+ - link: /uk/
+ name: uk - українська мова
+ - link: /zh/
+ name: zh - 汉语
+extra_css:
+- https://fastapi.tiangolo.com/css/termynal.css
+- https://fastapi.tiangolo.com/css/custom.css
+extra_javascript:
+- https://fastapi.tiangolo.com/js/termynal.js
+- https://fastapi.tiangolo.com/js/custom.js
diff --git a/docs/fa/overrides/.gitignore b/docs/fa/overrides/.gitignore
new file mode 100644
index 00000000..e69de29b
diff --git a/docs/fr/docs/async.md b/docs/fr/docs/async.md
new file mode 100644
index 00000000..20f4ee10
--- /dev/null
+++ b/docs/fr/docs/async.md
@@ -0,0 +1,394 @@
+# Concurrence et les mots-clés async et await
+
+Cette page vise à fournir des détails sur la syntaxe `async def` pour les *fonctions de chemins* et quelques rappels sur le code asynchrone, la concurrence et le parallélisme.
+
+## Vous êtes pressés ?
+
+TL;DR :
+
+Si vous utilisez des bibliothèques tierces qui nécessitent d'être appelées avec `await`, telles que :
+
+```Python
+results = await some_library()
+```
+Alors, déclarez vos *fonctions de chemins* avec `async def` comme ceci :
+
+```Python hl_lines="2"
+@app.get('/')
+async def read_results():
+ results = await some_library()
+ return results
+```
+
+!!! note
+ Vous pouvez uniquement utiliser `await` dans les fonctions créées avec `async def`.
+
+---
+
+Si vous utilisez une bibliothèque externe qui communique avec quelque chose (une BDD, une API, un système de fichiers, etc.) et qui ne supporte pas l'utilisation d'`await` (ce qui est actuellement le cas pour la majorité des bibliothèques de BDD), alors déclarez vos *fonctions de chemin* normalement, avec le classique `def`, comme ceci :
+
+```Python hl_lines="2"
+@app.get('/')
+def results():
+ results = some_library()
+ return results
+```
+
+---
+
+Si votre application n'a pas à communiquer avec une bibliothèque externe et pas à attendre de réponse, utilisez `async def`.
+
+---
+
+Si vous ne savez pas, utilisez seulement `def` comme vous le feriez habituellement.
+
+---
+
+**Note** : vous pouvez mélanger `def` et `async def` dans vos *fonctions de chemin* autant que nécessaire, **FastAPI** saura faire ce qu'il faut avec.
+
+Au final, peu importe le cas parmi ceux ci-dessus, **FastAPI** fonctionnera de manière asynchrone et sera extrêmement rapide.
+
+Mais si vous suivez bien les instructions ci-dessus, alors **FastAPI** pourra effectuer quelques optimisations et ainsi améliorer les performances.
+
+## Détails techniques
+
+Les versions modernes de Python supportent le **code asynchrone** grâce aux **"coroutines"** avec les syntaxes **`async` et `await`**.
+
+Analysons les différentes parties de cette phrase dans les sections suivantes :
+
+* **Code asynchrone**
+* **`async` et `await`**
+* **Coroutines**
+
+## Code asynchrone
+
+Faire du code asynchrone signifie que le langage 💬 est capable de dire à l'ordinateur / au programme 🤖 qu'à un moment du code, il 🤖 devra attendre que *quelque chose d'autre* se termine autre part. Disons que ce *quelque chose d'autre* est appelé "fichier-lent" 📝.
+
+Donc, pendant ce temps, l'ordinateur pourra effectuer d'autres tâches, pendant que "fichier-lent" 📝 se termine.
+
+Ensuite l'ordinateur / le programme 🤖 reviendra à chaque fois qu'il en a la chance que ce soit parce qu'il attend à nouveau, ou car il 🤖 a fini tout le travail qu'il avait à faire. Il 🤖 regardera donc si les tâches qu'il attend ont terminé d'être effectuées.
+
+Ensuite, il 🤖 prendra la première tâche à finir (disons, notre "fichier-lent" 📝) et continuera à faire avec cette dernière ce qu'il était censé.
+
+Ce "attendre quelque chose d'autre" fait généralement référence à des opérations I/O qui sont relativement "lentes" (comparées à la vitesse du processeur et de la mémoire RAM) telles qu'attendre que :
+
+* de la donnée soit envoyée par le client à travers le réseau
+* de la donnée envoyée depuis votre programme soit reçue par le client à travers le réseau
+* le contenu d'un fichier sur le disque soit lu par le système et passé à votre programme
+* le contenu que votre programme a passé au système soit écrit sur le disque
+* une opération effectuée à distance par une API se termine
+* une opération en BDD se termine
+* une requête à une BDD renvoie un résultat
+* etc.
+
+Le temps d'exécution étant consommé majoritairement par l'attente d'opérations I/O on appelle ceci des opérations "I/O bound".
+
+Ce concept se nomme l'"asynchronisme" car l'ordinateur / le programme n'a pas besoin d'être "synchronisé" avec la tâche, attendant le moment exact où cette dernière se terminera en ne faisant rien, pour être capable de récupérer le résultat de la tâche et l'utiliser dans la suite des opérations.
+
+À la place, en étant "asynchrone", une fois terminée, une tâche peut légèrement attendre (quelques microsecondes) que l'ordinateur / le programme finisse ce qu'il était en train de faire, et revienne récupérer le résultat.
+
+Pour parler de tâches "synchrones" (en opposition à "asynchrones"), on utilise souvent le terme "séquentiel", car l'ordinateur / le programme va effectuer toutes les étapes d'une tâche séquentiellement avant de passer à une autre tâche, même si ces étapes impliquent de l'attente.
+
+### Concurrence et Burgers
+
+L'idée de code **asynchrone** décrite ci-dessus est parfois aussi appelée **"concurrence"**. Ce qui est différent du **"parallélisme"**.
+
+La **concurrence** et le **parallélisme** sont tous deux liés à l'idée de "différentes choses arrivant plus ou moins au même moment".
+
+Mais les détails entre la **concurrence** et le **parallélisme** diffèrent sur de nombreux points.
+
+Pour expliquer la différence, voici une histoire de burgers :
+
+#### Burgers concurrents
+
+Vous amenez votre crush 😍 dans votre fast food 🍔 favori, et faites la queue pendant que le serveur 💁 prend les commandes des personnes devant vous.
+
+Puis vient votre tour, vous commandez alors 2 magnifiques burgers 🍔 pour votre crush 😍 et vous.
+
+Vous payez 💸.
+
+Le serveur 💁 dit quelque chose à son collègue dans la cuisine 👨🍳 pour qu'il sache qu'il doit préparer vos burgers 🍔 (bien qu'il soit déjà en train de préparer ceux des clients précédents).
+
+Le serveur 💁 vous donne le numéro assigné à votre commande.
+
+Pendant que vous attendez, vous allez choisir une table avec votre crush 😍, vous discutez avec votre crush 😍 pendant un long moment (les burgers étant "magnifiques" ils sont très longs à préparer ✨🍔✨).
+
+Pendant que vous êtes assis à table, en attendant que les burgers 🍔 soient prêts, vous pouvez passer ce temps à admirer à quel point votre crush 😍 est géniale, mignonne et intelligente ✨😍✨.
+
+Pendant que vous discutez avec votre crush 😍, de temps en temps vous jetez un coup d'oeil au nombre affiché au-dessus du comptoir pour savoir si c'est à votre tour d'être servis.
+
+Jusqu'au moment où c'est (enfin) votre tour. Vous allez au comptoir, récupérez vos burgers 🍔 et revenez à votre table.
+
+Vous et votre crush 😍 mangez les burgers 🍔 et passez un bon moment ✨.
+
+---
+
+Imaginez que vous êtes l'ordinateur / le programme 🤖 dans cette histoire.
+
+Pendant que vous faites la queue, vous être simplement inactif 😴, attendant votre tour, ne faisant rien de "productif". Mais la queue est rapide car le serveur 💁 prend seulement les commandes (et ne les prépare pas), donc tout va bien.
+
+Ensuite, quand c'est votre tour, vous faites des actions "productives" 🤓, vous étudiez le menu, décidez ce que vous voulez, demandez à votre crush 😍 son choix, payez 💸, vérifiez que vous utilisez la bonne carte de crédit, vérifiez que le montant débité sur la carte est correct, vérifiez que la commande contient les bons produits, etc.
+
+Mais ensuite, même si vous n'avez pas encore vos burgers 🍔, votre travail avec le serveur 💁 est "en pause" ⏸, car vous devez attendre 🕙 que vos burgers soient prêts.
+
+Après vous être écarté du comptoir et vous être assis à votre table avec le numéro de votre commande, vous pouvez tourner 🔀 votre attention vers votre crush 😍, et "travailler" ⏯ 🤓 là-dessus. Vous êtes donc à nouveau en train de faire quelque chose de "productif" 🤓, vous flirtez avec votre crush 😍.
+
+Puis le serveur 💁 dit "J'ai fini de préparer les burgers" 🍔 en mettant votre numéro sur l'affichage du comptoir, mais vous ne courrez pas immédiatement au moment où votre numéro s'affiche. Vous savez que personne ne volera vos burgers 🍔 car vous avez votre numéro et les autres clients ont le leur.
+
+Vous attendez donc que votre crush 😍 finisse son histoire, souriez gentiment et dites que vous allez chercher les burgers ⏸.
+
+Pour finir vous allez au comptoir 🔀, vers la tâche initiale qui est désormais terminée ⏯, récupérez les burgers 🍔, remerciez le serveur et ramenez les burgers 🍔 à votre table. Ceci termine l'étape / la tâche d'interaction avec le comptoir ⏹. Ce qui ensuite, crée une nouvelle tâche de "manger les burgers" 🔀 ⏯, mais la précédente, "récupérer les burgers" est terminée ⏹.
+
+#### Burgers parallèles
+
+Imaginons désormais que ce ne sont pas des "burgers concurrents" mais des "burgers parallèles".
+
+Vous allez avec votre crush 😍 dans un fast food 🍔 parallélisé.
+
+Vous attendez pendant que plusieurs (disons 8) serveurs qui sont aussi des cuisiniers 👨🍳👨🍳👨🍳👨🍳👨🍳👨🍳👨🍳👨🍳 prennent les commandes des personnes devant vous.
+
+Chaque personne devant vous attend 🕙 que son burger 🍔 soit prêt avant de quitter le comptoir car chacun des 8 serveurs va lui-même préparer le burger directement avant de prendre la commande suivante.
+
+Puis c'est enfin votre tour, vous commandez 2 magnifiques burgers 🍔 pour vous et votre crush 😍.
+
+Vous payez 💸.
+
+Le serveur va dans la cuisine 👨🍳.
+
+Vous attendez devant le comptoir afin que personne ne prenne vos burgers 🍔 avant vous, vu qu'il n'y a pas de numéro de commande.
+
+Vous et votre crush 😍 étant occupés à vérifier que personne ne passe devant vous prendre vos burgers au moment où ils arriveront 🕙, vous ne pouvez pas vous préoccuper de votre crush 😞.
+
+C'est du travail "synchrone", vous être "synchronisés" avec le serveur/cuisinier 👨🍳. Vous devez attendre 🕙 et être présent au moment exact où le serveur/cuisinier 👨🍳 finira les burgers 🍔 et vous les donnera, sinon quelqu'un risque de vous les prendre.
+
+Puis le serveur/cuisinier 👨🍳 revient enfin avec vos burgers 🍔, après un long moment d'attente 🕙 devant le comptoir.
+
+Vous prenez vos burgers 🍔 et allez à une table avec votre crush 😍
+
+Vous les mangez, et vous avez terminé 🍔 ⏹.
+
+Durant tout ce processus, il n'y a presque pas eu de discussions ou de flirts car la plupart de votre temps à été passé à attendre 🕙 devant le comptoir 😞.
+
+---
+
+Dans ce scénario de burgers parallèles, vous êtes un ordinateur / programme 🤖 avec deux processeurs (vous et votre crush 😍) attendant 🕙 à deux et dédiant votre attention 🕙 à "attendre devant le comptoir" pour une longue durée.
+
+Le fast-food a 8 processeurs (serveurs/cuisiniers) 👨🍳👨🍳👨🍳👨🍳👨🍳👨🍳👨🍳👨🍳. Alors que le fast-food de burgers concurrents en avait 2 (un serveur et un cuisinier).
+
+Et pourtant l'expérience finale n'est pas meilleure 😞.
+
+---
+
+C'est donc l'histoire équivalente parallèle pour les burgers 🍔.
+
+Pour un exemple plus courant dans la "vie réelle", imaginez une banque.
+
+Jusqu'à récemment, la plupart des banques avaient plusieurs caisses (et banquiers) 👨💼👨💼👨💼👨💼 et une unique file d'attente 🕙🕙🕙🕙🕙🕙🕙🕙.
+
+Tous les banquiers faisaient l'intégralité du travail avec chaque client avant de passer au suivant 👨💼⏯.
+
+Et vous deviez attendre 🕙 dans la file pendant un long moment ou vous perdiez votre place.
+
+Vous n'auriez donc probablement pas envie d'amener votre crush 😍 avec vous à la banque 🏦.
+
+#### Conclusion
+
+Dans ce scénario des "burgers du fast-food avec votre crush", comme il y a beaucoup d'attente 🕙, il est très logique d'avoir un système concurrent ⏸🔀⏯.
+
+Et c'est le cas pour la plupart des applications web.
+
+Vous aurez de nombreux, nombreux utilisateurs, mais votre serveur attendra 🕙 que leur connexion peu performante envoie des requêtes.
+
+Puis vous attendrez 🕙 de nouveau que leurs réponses reviennent.
+
+Cette "attente" 🕙 se mesure en microsecondes, mais tout de même, en cumulé cela fait beaucoup d'attente.
+
+C'est pourquoi il est logique d'utiliser du code asynchrone ⏸🔀⏯ pour des APIs web.
+
+La plupart des frameworks Python existants (y compris Flask et Django) ont été créés avant que les nouvelles fonctionnalités asynchrones de Python n'existent. Donc, les façons dont ils peuvent être déployés supportent l'exécution parallèle et une ancienne forme d'exécution asynchrone qui n'est pas aussi puissante que les nouvelles fonctionnalités de Python.
+
+Et cela, bien que les spécifications principales du web asynchrone en Python (ou ASGI) ont été développées chez Django, pour ajouter le support des WebSockets.
+
+Ce type d'asynchronicité est ce qui a rendu NodeJS populaire (bien que NodeJS ne soit pas parallèle) et c'est la force du Go en tant que langage de programmation.
+
+Et c'est le même niveau de performance que celui obtenu avec **FastAPI**.
+
+Et comme on peut avoir du parallélisme et de l'asynchronicité en même temps, on obtient des performances plus hautes que la plupart des frameworks NodeJS et égales à celles du Go, qui est un langage compilé plus proche du C (tout ça grâce à Starlette).
+
+### Est-ce que la concurrence est mieux que le parallélisme ?
+
+Nope ! C'est ça la morale de l'histoire.
+
+La concurrence est différente du parallélisme. C'est mieux sur des scénarios **spécifiques** qui impliquent beaucoup d'attente. À cause de ça, c'est généralement bien meilleur que le parallélisme pour le développement d'applications web. Mais pas pour tout.
+
+Donc pour équilibrer tout ça, imaginez l'histoire suivante :
+
+> Vous devez nettoyer une grande et sale maison.
+
+*Oui, c'est toute l'histoire*.
+
+---
+
+Il n'y a plus d'attente 🕙 nulle part, juste beaucoup de travail à effectuer, dans différentes pièces de la maison.
+
+Vous pourriez diviser en différentes sections comme avec les burgers, d'abord le salon, puis la cuisine, etc. Mais vous n'attendez 🕙 rien, vous ne faites que nettoyer et nettoyer, la séparation en sections ne changerait rien au final.
+
+Cela prendrait autant de temps pour finir avec ou sans sections (concurrence) et vous auriez effectué la même quantité de travail.
+
+Mais dans ce cas, si pouviez amener 8 ex-serveurs/cuisiniers/devenus-nettoyeurs 👨🍳👨🍳👨🍳👨🍳👨🍳👨🍳👨🍳👨🍳, et que chacun d'eux (plus vous) pouvait prendre une zone de la maison pour la nettoyer, vous pourriez faire tout le travail en parallèle, et finir plus tôt.
+
+Dans ce scénario, chacun des nettoyeurs (vous y compris) serait un processeur, faisant sa partie du travail.
+
+Et comme la plupart du temps d'exécution est pris par du "vrai" travail (et non de l'attente), et que le travail dans un ordinateur est fait par un CPU, ce sont des problèmes dits "CPU bound".
+
+---
+
+Des exemples communs d'opérations "CPU bounds" sont les procédés qui requièrent des traitements mathématiques complexes.
+
+Par exemple :
+
+* Traitements d'**audio** et d'**images**.
+* La **vision par ordinateur** : une image est composée de millions de pixels, chaque pixel ayant 3 valeurs / couleurs, les traiter tous va nécessiter d'effectuer des traitements sur chaque pixel, et de préférence tous en même temps.
+* L'apprentissage automatique (ou **Machine Learning**) : cela nécessite de nombreuses multiplications de matrices et vecteurs. Imaginez une énorme feuille de calcul remplie de nombres que vous multiplierez entre eux tous au même moment.
+* L'apprentissage profond (ou **Deep Learning**) : est un sous-domaine du **Machine Learning**, donc les mêmes raisons s'appliquent. Avec la différence qu'il n'y a pas une unique feuille de calcul de nombres à multiplier, mais une énorme quantité d'entre elles, et dans de nombreux cas, on utilise un processeur spécial pour construire et / ou utiliser ces modèles.
+
+### Concurrence + Parallélisme : Web + Machine Learning
+
+Avec **FastAPI** vous pouvez bénéficier de la concurrence qui est très courante en developement web (c'est l'attrait principal de NodeJS).
+
+Mais vous pouvez aussi profiter du parallélisme et multiprocessing afin de gérer des charges **CPU bound** qui sont récurrentes dans les systèmes de *Machine Learning*.
+
+Ça, ajouté au fait que Python soit le langage le plus populaire pour la **Data Science**, le **Machine Learning** et surtout le **Deep Learning**, font de **FastAPI** un très bon choix pour les APIs et applications de **Data Science** / **Machine Learning**.
+
+Pour comprendre comment mettre en place ce parallélisme en production, allez lire la section [Déploiement](deployment/index.md){.internal-link target=_blank}.
+
+## `async` et `await`
+
+Les versions modernes de Python ont une manière très intuitive de définir le code asynchrone, tout en gardant une apparence de code "séquentiel" classique en laissant Python faire l'attente pour vous au bon moment.
+
+Pour une opération qui nécessite de l'attente avant de donner un résultat et qui supporte ces nouvelles fonctionnalités Python, vous pouvez l'utiliser comme tel :
+
+```Python
+burgers = await get_burgers(2)
+```
+
+Le mot-clé important ici est `await`. Il informe Python qu'il faut attendre ⏸ que `get_burgers(2)` finisse d'effectuer ses opérations 🕙 avant de stocker les résultats dans la variable `burgers`. Grâce à cela, Python saura qu'il peut aller effectuer d'autres opérations 🔀 ⏯ pendant ce temps (comme par exemple recevoir une autre requête).
+
+Pour que `await` fonctionne, il doit être placé dans une fonction qui supporte l'asynchronicité. Pour que ça soit le cas, il faut déclarer cette dernière avec `async def` :
+
+```Python hl_lines="1"
+async def get_burgers(number: int):
+ # Opérations asynchrones pour créer les burgers
+ return burgers
+```
+
+...et non `def` :
+
+```Python hl_lines="2"
+# Ceci n'est pas asynchrone
+def get_sequential_burgers(number: int):
+ # Opérations asynchrones pour créer les burgers
+ return burgers
+```
+
+Avec `async def`, Python sait que dans cette fonction il doit prendre en compte les expressions `await`, et qu'il peut mettre en pause ⏸ l'exécution de la fonction pour aller faire autre chose 🔀 avant de revenir.
+
+Pour appeler une fonction définie avec `async def`, vous devez utiliser `await`. Donc ceci ne marche pas :
+
+```Python
+# Ceci ne fonctionne pas, car get_burgers a été défini avec async def
+burgers = get_burgers(2)
+```
+
+---
+
+Donc, si vous utilisez une bibliothèque qui nécessite que ses fonctions soient appelées avec `await`, vous devez définir la *fonction de chemin* en utilisant `async def` comme dans :
+
+```Python hl_lines="2-3"
+@app.get('/burgers')
+async def read_burgers():
+ burgers = await get_burgers(2)
+ return burgers
+```
+
+### Plus de détails techniques
+
+Vous avez donc compris que `await` peut seulement être utilisé dans des fonctions définies avec `async def`.
+
+Mais en même temps, les fonctions définies avec `async def` doivent être appelées avec `await` et donc dans des fonctions définies elles aussi avec `async def`.
+
+Vous avez donc remarqué ce paradoxe d'oeuf et de la poule, comment appelle-t-on la première fonction `async` ?
+
+Si vous utilisez **FastAPI**, pas besoin de vous en inquiéter, car cette "première" fonction sera votre *fonction de chemin* ; et **FastAPI** saura comment arriver au résultat attendu.
+
+Mais si vous utilisez `async` / `await` sans **FastAPI**, allez jetez un coup d'oeil à la documentation officielle de Python.
+
+### Autres formes de code asynchrone
+
+L'utilisation d'`async` et `await` est relativement nouvelle dans ce langage.
+
+Mais cela rend la programmation asynchrone bien plus simple.
+
+Cette même syntaxe (ou presque) était aussi incluse dans les versions modernes de Javascript (dans les versions navigateur et NodeJS).
+
+Mais avant ça, gérer du code asynchrone était bien plus complexe et difficile.
+
+Dans les versions précédentes de Python, vous auriez utilisé des *threads* ou Gevent. Mais le code aurait été bien plus difficile à comprendre, débugger, et concevoir.
+
+Dans les versions précédentes de Javascript NodeJS / Navigateur, vous auriez utilisé des "callbacks". Menant potentiellement à ce que l'on appelle le "callback hell".
+
+
+## Coroutines
+
+**Coroutine** est juste un terme élaboré pour désigner ce qui est retourné par une fonction définie avec `async def`. Python sait que c'est comme une fonction classique qui va démarrer à un moment et terminer à un autre, mais qu'elle peut aussi être mise en pause ⏸, du moment qu'il y a un `await` dans son contenu.
+
+Mais toutes ces fonctionnalités d'utilisation de code asynchrone avec `async` et `await` sont souvent résumées comme l'utilisation des *coroutines*. On peut comparer cela à la principale fonctionnalité clé de Go, les "Goroutines".
+
+## Conclusion
+
+Reprenons la phrase du début de la page :
+
+> Les versions modernes de Python supportent le **code asynchrone** grâce aux **"coroutines"** avec les syntaxes **`async` et `await`**.
+
+Ceci devrait être plus compréhensible désormais. ✨
+
+Tout ceci est donc ce qui donne sa force à **FastAPI** (à travers Starlette) et lui permet d'avoir des performances aussi impressionnantes.
+
+## Détails très techniques
+
+!!! warning "Attention !"
+ Vous pouvez probablement ignorer cela.
+
+ Ce sont des détails très poussés sur comment **FastAPI** fonctionne en arrière-plan.
+
+ Si vous avez de bonnes connaissances techniques (coroutines, threads, code bloquant, etc.) et êtes curieux de comment **FastAPI** gère `async def` versus le `def` classique, cette partie est faite pour vous.
+
+### Fonctions de chemin
+
+Quand vous déclarez une *fonction de chemin* avec un `def` normal et non `async def`, elle est exécutée dans un groupe de threads (threadpool) externe qui est ensuite attendu, plutôt que d'être appelée directement (car cela bloquerait le serveur).
+
+Si vous venez d'un autre framework asynchrone qui ne fonctionne pas comme de la façon décrite ci-dessus et que vous êtes habitués à définir des *fonctions de chemin* basiques avec un simple `def` pour un faible gain de performance (environ 100 nanosecondes), veuillez noter que dans **FastAPI**, l'effet serait plutôt contraire. Dans ces cas-là, il vaut mieux utiliser `async def` à moins que votre *fonction de chemin* utilise du code qui effectue des opérations I/O bloquantes.
+
+Au final, dans les deux situations, il est fort probable que **FastAPI** soit tout de même [plus rapide](/#performance){.internal-link target=_blank} que (ou au moins de vitesse égale à) votre framework précédent.
+
+### Dépendances
+
+La même chose s'applique aux dépendances. Si une dépendance est définie avec `def` plutôt que `async def`, elle est exécutée dans la threadpool externe.
+
+### Sous-dépendances
+
+Vous pouvez avoir de multiples dépendances et sous-dépendances dépendant les unes des autres (en tant que paramètres de la définition de la *fonction de chemin*), certaines créées avec `async def` et d'autres avec `def`. Cela fonctionnerait aussi, et celles définies avec un simple `def` seraient exécutées sur un thread externe (venant de la threadpool) plutôt que d'être "attendues".
+
+### Autres fonctions utilitaires
+
+Toute autre fonction utilitaire que vous appelez directement peut être créée avec un classique `def` ou avec `async def` et **FastAPI** n'aura pas d'impact sur la façon dont vous l'appelez.
+
+Contrairement aux fonctions que **FastAPI** appelle pour vous : les *fonctions de chemin* et dépendances.
+
+Si votre fonction utilitaire est une fonction classique définie avec `def`, elle sera appelée directement (telle qu'écrite dans votre code), pas dans une threadpool, si la fonction est définie avec `async def` alors vous devrez attendre (avec `await`) que cette fonction se termine avant de passer à la suite du code.
+
+---
+
+Encore une fois, ce sont des détails très techniques qui peuvent être utiles si vous venez ici les chercher.
+
+Sinon, les instructions de la section Vous êtes pressés ? ci-dessus sont largement suffisantes.
diff --git a/docs/fr/docs/deployment/docker.md b/docs/fr/docs/deployment/docker.md
new file mode 100644
index 00000000..e4b59afb
--- /dev/null
+++ b/docs/fr/docs/deployment/docker.md
@@ -0,0 +1,182 @@
+# Déployer avec Docker
+
+Dans cette section, vous verrez des instructions et des liens vers des guides pour savoir comment :
+
+* Faire de votre application **FastAPI** une image/conteneur Docker avec une performance maximale. En environ **5 min**.
+* (Optionnellement) comprendre ce que vous, en tant que développeur, devez savoir sur HTTPS.
+* Configurer un cluster en mode Docker Swarm avec HTTPS automatique, même sur un simple serveur à 5 dollars US/mois. En environ **20 min**.
+* Générer et déployer une application **FastAPI** complète, en utilisant votre cluster Docker Swarm, avec HTTPS, etc. En environ **10 min**.
+
+Vous pouvez utiliser **Docker** pour le déploiement. Il présente plusieurs avantages comme la sécurité, la réplicabilité, la simplicité de développement, etc.
+
+Si vous utilisez Docker, vous pouvez utiliser l'image Docker officielle :
+
+## tiangolo/uvicorn-gunicorn-fastapi
+
+Cette image est dotée d'un mécanisme d'"auto-tuning", de sorte qu'il vous suffit d'ajouter votre code pour obtenir automatiquement des performances très élevées. Et sans faire de sacrifices.
+
+Mais vous pouvez toujours changer et mettre à jour toutes les configurations avec des variables d'environnement ou des fichiers de configuration.
+
+!!! tip "Astuce"
+ Pour voir toutes les configurations et options, rendez-vous sur la page de l'image Docker : tiangolo/uvicorn-gunicorn-fastapi.
+
+## Créer un `Dockerfile`
+
+* Allez dans le répertoire de votre projet.
+* Créez un `Dockerfile` avec :
+
+```Dockerfile
+FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7
+
+COPY ./app /app
+```
+
+### Applications plus larges
+
+Si vous avez suivi la section sur la création d' [Applications avec plusieurs fichiers](../tutorial/bigger-applications.md){.internal-link target=_blank}, votre `Dockerfile` pourrait ressembler à ceci :
+
+```Dockerfile
+FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7
+
+COPY ./app /app/app
+```
+
+### Raspberry Pi et autres architectures
+
+Si vous utilisez Docker sur un Raspberry Pi (qui a un processeur ARM) ou toute autre architecture, vous pouvez créer un `Dockerfile` à partir de zéro, basé sur une image de base Python (qui est multi-architecture) et utiliser Uvicorn seul.
+
+Dans ce cas, votre `Dockerfile` pourrait ressembler à ceci :
+
+```Dockerfile
+FROM python:3.7
+
+RUN pip install fastapi uvicorn
+
+EXPOSE 80
+
+COPY ./app /app
+
+CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
+```
+
+## Créer le code **FastAPI**.
+
+* Créer un répertoire `app` et y entrer.
+* Créez un fichier `main.py` avec :
+
+```Python
+from typing import Optional
+
+from fastapi import FastAPI
+
+app = FastAPI()
+
+
+@app.get("/")
+def read_root():
+ return {"Hello": "World"}
+
+
+@app.get("/items/{item_id}")
+def read_item(item_id: int, q: Optional[str] = None):
+ return {"item_id": item_id, "q": q}
+```
+
+* Vous devriez maintenant avoir une structure de répertoire telle que :
+
+```
+.
+├── app
+│ └── main.py
+└── Dockerfile
+```
+
+## Construire l'image Docker
+
+* Allez dans le répertoire du projet (dans lequel se trouve votre `Dockerfile`, contenant votre répertoire `app`).
+* Construisez votre image FastAPI :
+
+requests - Required if you want to use the `TestClient`.
-* aiofiles - Required if you want to use `FileResponse` or `StaticFiles`.
* jinja2 - Required if you want to use the default template configuration.
* python-multipart - Required if you want to support form "parsing", with `request.form()`.
* itsdangerous - Required for `SessionMiddleware` support.
diff --git a/docs/fr/docs/tutorial/body.md b/docs/fr/docs/tutorial/body.md
new file mode 100644
index 00000000..30458449
--- /dev/null
+++ b/docs/fr/docs/tutorial/body.md
@@ -0,0 +1,165 @@
+# Corps de la requête
+
+Quand vous avez besoin d'envoyer de la donnée depuis un client (comme un navigateur) vers votre API, vous l'envoyez en tant que **corps de requête**.
+
+Le corps d'une **requête** est de la donnée envoyée par le client à votre API. Le corps d'une **réponse** est la donnée envoyée par votre API au client.
+
+Votre API aura presque toujours à envoyer un corps de **réponse**. Mais un client n'a pas toujours à envoyer un corps de **requête**.
+
+Pour déclarer un corps de **requête**, on utilise les modèles de Pydantic en profitant de tous leurs avantages et fonctionnalités.
+
+!!! info
+ Pour envoyer de la donnée, vous devriez utiliser : `POST` (le plus populaire), `PUT`, `DELETE` ou `PATCH`.
+
+ Envoyer un corps dans une requête `GET` a un comportement non défini dans les spécifications, cela est néanmoins supporté par **FastAPI**, seulement pour des cas d'utilisation très complexes/extrêmes.
+
+ Ceci étant découragé, la documentation interactive générée par Swagger UI ne montrera pas de documentation pour le corps d'une requête `GET`, et les proxys intermédiaires risquent de ne pas le supporter.
+
+## Importez le `BaseModel` de Pydantic
+
+Commencez par importer la classe `BaseModel` du module `pydantic` :
+
+```Python hl_lines="4"
+{!../../../docs_src/body/tutorial001.py!}
+```
+
+## Créez votre modèle de données
+
+Déclarez ensuite votre modèle de données en tant que classe qui hérite de `BaseModel`.
+
+Utilisez les types Python standard pour tous les attributs :
+
+```Python hl_lines="7-11"
+{!../../../docs_src/body/tutorial001.py!}
+```
+
+Tout comme pour la déclaration de paramètres de requête, quand un attribut de modèle a une valeur par défaut, il n'est pas nécessaire. Sinon, cet attribut doit être renseigné dans le corps de la requête. Pour rendre ce champ optionnel simplement, utilisez `None` comme valeur par défaut.
+
+Par exemple, le modèle ci-dessus déclare un "objet" JSON (ou `dict` Python) tel que :
+
+```JSON
+{
+ "name": "Foo",
+ "description": "An optional description",
+ "price": 45.2,
+ "tax": 3.5
+}
+```
+
+...`description` et `tax` étant des attributs optionnels (avec `None` comme valeur par défaut), cet "objet" JSON serait aussi valide :
+
+```JSON
+{
+ "name": "Foo",
+ "price": 45.2
+}
+```
+
+## Déclarez-le comme paramètre
+
+Pour l'ajouter à votre *opération de chemin*, déclarez-le comme vous déclareriez des paramètres de chemin ou de requête :
+
+```Python hl_lines="18"
+{!../../../docs_src/body/tutorial001.py!}
+```
+
+...et déclarez que son type est le modèle que vous avez créé : `Item`.
+
+## Résultats
+
+En utilisant uniquement les déclarations de type Python, **FastAPI** réussit à :
+
+* Lire le contenu de la requête en tant que JSON.
+* Convertir les types correspondants (si nécessaire).
+* Valider la donnée.
+ * Si la donnée est invalide, une erreur propre et claire sera renvoyée, indiquant exactement où était la donnée incorrecte.
+* Passer la donnée reçue dans le paramètre `item`.
+ * Ce paramètre ayant été déclaré dans la fonction comme étant de type `Item`, vous aurez aussi tout le support offert par l'éditeur (auto-complétion, etc.) pour tous les attributs de ce paramètre et les types de ces attributs.
+* Générer des définitions JSON Schema pour votre modèle, qui peuvent être utilisées où vous en avez besoin dans votre projet ensuite.
+* Ces schémas participeront à la constitution du schéma généré OpenAPI, et seront donc utilisés par les documentations automatiquement générées.
+
+## Documentation automatique
+
+Les schémas JSON de vos modèles seront intégrés au schéma OpenAPI global de votre application, et seront donc affichés dans la documentation interactive de l'API :
+
+
+
+Et seront aussi utilisés dans chaque *opération de chemin* de la documentation utilisant ces modèles :
+
+
+
+## Support de l'éditeur
+
+Dans votre éditeur, vous aurez des annotations de types et de l'auto-complétion partout dans votre fonction (ce qui n'aurait pas été le cas si vous aviez utilisé un classique `dict` plutôt qu'un modèle Pydantic) :
+
+
+
+Et vous obtenez aussi de la vérification d'erreur pour les opérations incorrectes de types :
+
+
+
+Ce n'est pas un hasard, ce framework entier a été bati avec ce design comme objectif.
+
+Et cela a été rigoureusement testé durant la phase de design, avant toute implémentation, pour s'assurer que cela fonctionnerait avec tous les éditeurs.
+
+Des changements sur Pydantic ont même été faits pour supporter cela.
+
+Les captures d'écrans précédentes ont été prises sur Visual Studio Code.
+
+Mais vous auriez le même support de l'éditeur avec PyCharm et la majorité des autres éditeurs de code Python.
+
+
+
+!!! tip "Astuce"
+ Si vous utilisez PyCharm comme éditeur, vous pouvez utiliser le Plugin Pydantic PyCharm Plugin.
+
+ Ce qui améliore le support pour les modèles Pydantic avec :
+
+ * de l'auto-complétion
+ * des vérifications de type
+ * du "refactoring" (ou remaniement de code)
+ * de la recherche
+ * de l'inspection
+
+## Utilisez le modèle
+
+Dans la fonction, vous pouvez accéder à tous les attributs de l'objet du modèle directement :
+
+```Python hl_lines="21"
+{!../../../docs_src/body/tutorial002.py!}
+```
+
+## Corps de la requête + paramètres de chemin
+
+Vous pouvez déclarer des paramètres de chemin et un corps de requête pour la même *opération de chemin*.
+
+**FastAPI** est capable de reconnaître que les paramètres de la fonction qui correspondent aux paramètres de chemin doivent être **récupérés depuis le chemin**, et que les paramètres de fonctions déclarés comme modèles Pydantic devraient être **récupérés depuis le corps de la requête**.
+
+```Python hl_lines="17-18"
+{!../../../docs_src/body/tutorial003.py!}
+```
+
+## Corps de la requête + paramètres de chemin et de requête
+
+Vous pouvez aussi déclarer un **corps**, et des paramètres de **chemin** et de **requête** dans la même *opération de chemin*.
+
+**FastAPI** saura reconnaître chacun d'entre eux et récupérer la bonne donnée au bon endroit.
+
+```Python hl_lines="18"
+{!../../../docs_src/body/tutorial004.py!}
+```
+
+Les paramètres de la fonction seront reconnus comme tel :
+
+* Si le paramètre est aussi déclaré dans le **chemin**, il sera utilisé comme paramètre de chemin.
+* Si le paramètre est d'un **type singulier** (comme `int`, `float`, `str`, `bool`, etc.), il sera interprété comme un paramètre de **requête**.
+* Si le paramètre est déclaré comme ayant pour type un **modèle Pydantic**, il sera interprété comme faisant partie du **corps** de la requête.
+
+!!! note
+ **FastAPI** saura que la valeur de `q` n'est pas requise grâce à la valeur par défaut `=None`.
+
+ Le type `Optional` dans `Optional[str]` n'est pas utilisé par **FastAPI**, mais sera utile à votre éditeur pour améliorer le support offert par ce dernier et détecter plus facilement des erreurs de type.
+
+## Sans Pydantic
+
+Si vous ne voulez pas utiliser des modèles Pydantic, vous pouvez aussi utiliser des paramètres de **Corps**. Pour cela, allez voir la partie de la documentation sur [Corps de la requête - Paramètres multiples](body-multiple-params.md){.internal-link target=_blank}.
\ No newline at end of file
diff --git a/docs/fr/docs/tutorial/first-steps.md b/docs/fr/docs/tutorial/first-steps.md
new file mode 100644
index 00000000..3a81362f
--- /dev/null
+++ b/docs/fr/docs/tutorial/first-steps.md
@@ -0,0 +1,334 @@
+# Démarrage
+
+Le fichier **FastAPI** le plus simple possible pourrait ressembler à cela :
+
+```Python
+{!../../../docs_src/first_steps/tutorial001.py!}
+```
+
+Copiez ce code dans un fichier nommé `main.py`.
+
+Démarrez le serveur :
+
+get
+
+!!! info "`@décorateur` Info"
+ Cette syntaxe `@something` en Python est appelée un "décorateur".
+
+ Vous la mettez au dessus d'une fonction. Comme un joli chapeau décoratif (j'imagine que ce terme vient de là 🤷🏻♂).
+
+ Un "décorateur" prend la fonction en dessous et en fait quelque chose.
+
+ Dans notre cas, ce décorateur dit à **FastAPI** que la fonction en dessous correspond au **chemin** `/` avec l'**opération** `get`.
+
+ C'est le "**décorateur d'opération de chemin**".
+
+Vous pouvez aussi utiliser les autres opérations :
+
+* `@app.post()`
+* `@app.put()`
+* `@app.delete()`
+
+Tout comme celles les plus exotiques :
+
+* `@app.options()`
+* `@app.head()`
+* `@app.patch()`
+* `@app.trace()`
+
+!!! tip "Astuce"
+ Vous êtes libres d'utiliser chaque opération (méthode HTTP) comme vous le désirez.
+
+ **FastAPI** n'impose pas de sens spécifique à chacune d'elle.
+
+ Les informations qui sont présentées ici forment une directive générale, pas des obligations.
+
+ Par exemple, quand l'on utilise **GraphQL**, toutes les actions sont effectuées en utilisant uniquement des opérations `POST`.
+
+### Étape 4 : définir la **fonction de chemin**.
+
+Voici notre "**fonction de chemin**" (ou fonction d'opération de chemin) :
+
+* **chemin** : `/`.
+* **opération** : `get`.
+* **fonction** : la fonction sous le "décorateur" (sous `@app.get("/")`).
+
+```Python hl_lines="7"
+{!../../../docs_src/first_steps/tutorial001.py!}
+```
+
+C'est une fonction Python.
+
+Elle sera appelée par **FastAPI** quand une requête sur l'URL `/` sera reçue via une opération `GET`.
+
+Ici, c'est une fonction asynchrone (définie avec `async def`).
+
+---
+
+Vous pourriez aussi la définir comme une fonction classique plutôt qu'avec `async def` :
+
+```Python hl_lines="7"
+{!../../../docs_src/first_steps/tutorial003.py!}
+```
+
+!!! note
+ Si vous ne connaissez pas la différence, allez voir la section [Concurrence : *"Vous êtes pressés ?"*](../async.md#vous-etes-presses){.internal-link target=_blank}.
+
+### Étape 5 : retourner le contenu
+
+```Python hl_lines="8"
+{!../../../docs_src/first_steps/tutorial001.py!}
+```
+
+Vous pouvez retourner un dictionnaire (`dict`), une liste (`list`), des valeurs seules comme des chaines de caractères (`str`) et des entiers (`int`), etc.
+
+Vous pouvez aussi retourner des models **Pydantic** (qui seront détaillés plus tard).
+
+Il y a de nombreux autres objets et modèles qui seront automatiquement convertis en JSON. Essayez d'utiliser vos favoris, il est fort probable qu'ils soient déjà supportés.
+
+## Récapitulatif
+
+* Importez `FastAPI`.
+* Créez une instance d'`app`.
+* Ajoutez une **décorateur d'opération de chemin** (tel que `@app.get("/")`).
+* Ajoutez une **fonction de chemin** (telle que `def root(): ...` comme ci-dessus).
+* Lancez le serveur de développement (avec `uvicorn main:app --reload`).
diff --git a/docs/fr/docs/tutorial/path-params.md b/docs/fr/docs/tutorial/path-params.md
new file mode 100644
index 00000000..58f53e00
--- /dev/null
+++ b/docs/fr/docs/tutorial/path-params.md
@@ -0,0 +1,254 @@
+# Paramètres de chemin
+
+Vous pouvez déclarer des "paramètres" ou "variables" de chemin avec la même syntaxe que celle utilisée par le
+formatage de chaîne Python :
+
+
+```Python hl_lines="6-7"
+{!../../../docs_src/path_params/tutorial001.py!}
+```
+
+La valeur du paramètre `item_id` sera transmise à la fonction dans l'argument `item_id`.
+
+Donc, si vous exécutez cet exemple et allez sur http://127.0.0.1:8000/items/foo,
+vous verrez comme réponse :
+
+```JSON
+{"item_id":"foo"}
+```
+
+## Paramètres de chemin typés
+
+Vous pouvez déclarer le type d'un paramètre de chemin dans la fonction, en utilisant les annotations de type Python :
+
+
+```Python hl_lines="7"
+{!../../../docs_src/path_params/tutorial002.py!}
+```
+
+Ici, `item_id` est déclaré comme `int`.
+
+!!! hint "Astuce"
+ Ceci vous permettra d'obtenir des fonctionnalités de l'éditeur dans votre fonction, telles
+ que des vérifications d'erreur, de l'auto-complétion, etc.
+
+## Conversion de données
+
+Si vous exécutez cet exemple et allez sur http://127.0.0.1:8000/items/3, vous aurez comme réponse :
+
+```JSON
+{"item_id":3}
+```
+
+!!! hint "Astuce"
+ Comme vous l'avez remarqué, la valeur reçue par la fonction (et renvoyée ensuite) est `3`,
+ en tant qu'entier (`int`) Python, pas la chaîne de caractères (`string`) `"3"`.
+
+ Grâce aux déclarations de types, **FastAPI** fournit du
+ "parsing" automatique.
+
+## Validation de données
+
+Si vous allez sur http://127.0.0.1:8000/items/foo, vous aurez une belle erreur HTTP :
+
+```JSON
+{
+ "detail": [
+ {
+ "loc": [
+ "path",
+ "item_id"
+ ],
+ "msg": "value is not a valid integer",
+ "type": "type_error.integer"
+ }
+ ]
+}
+```
+
+car le paramètre de chemin `item_id` possède comme valeur `"foo"`, qui ne peut pas être convertie en entier (`int`).
+
+La même erreur se produira si vous passez un nombre flottant (`float`) et non un entier, comme ici
+http://127.0.0.1:8000/items/4.2.
+
+
+!!! hint "Astuce"
+ Donc, avec ces mêmes déclarations de type Python, **FastAPI** vous fournit de la validation de données.
+
+ Notez que l'erreur mentionne le point exact où la validation n'a pas réussi.
+
+ Ce qui est incroyablement utile au moment de développer et débugger du code qui interagit avec votre API.
+
+## Documentation
+
+Et quand vous vous rendez sur http://127.0.0.1:8000/docs, vous verrez la
+documentation générée automatiquement et interactive :
+
+
+
+!!! info
+ À nouveau, en utilisant uniquement les déclarations de type Python, **FastAPI** vous fournit automatiquement une documentation interactive (via Swagger UI).
+
+ On voit bien dans la documentation que `item_id` est déclaré comme entier.
+
+## Les avantages d'avoir une documentation basée sur une norme, et la documentation alternative.
+
+Le schéma généré suivant la norme OpenAPI,
+il existe de nombreux outils compatibles.
+
+Grâce à cela, **FastAPI** lui-même fournit une documentation alternative (utilisant ReDoc), qui peut être lue
+sur http://127.0.0.1:8000/redoc :
+
+
+
+De la même façon, il existe bien d'autres outils compatibles, y compris des outils de génération de code
+pour de nombreux langages.
+
+## Pydantic
+
+Toute la validation de données est effectué en arrière-plan avec Pydantic,
+dont vous bénéficierez de tous les avantages. Vous savez donc que vous êtes entre de bonnes mains.
+
+## L'ordre importe
+
+Quand vous créez des *fonctions de chemins*, vous pouvez vous retrouver dans une situation où vous avez un chemin fixe.
+
+Tel que `/users/me`, disons pour récupérer les données sur l'utilisateur actuel.
+
+Et vous avez un second chemin : `/users/{user_id}` pour récupérer de la donnée sur un utilisateur spécifique grâce à son identifiant d'utilisateur
+
+Les *fonctions de chemin* étant évaluées dans l'ordre, il faut s'assurer que la fonction correspondant à `/users/me` est déclarée avant celle de `/users/{user_id}` :
+
+```Python hl_lines="6 11"
+{!../../../docs_src/path_params/tutorial003.py!}
+```
+
+Sinon, le chemin `/users/{user_id}` correspondrait aussi à `/users/me`, la fonction "croyant" qu'elle a reçu un paramètre `user_id` avec pour valeur `"me"`.
+
+## Valeurs prédéfinies
+
+Si vous avez une *fonction de chemin* qui reçoit un *paramètre de chemin*, mais que vous voulez que les valeurs possibles des paramètres soient prédéfinies, vous pouvez utiliser les `Enum` de Python.
+
+### Création d'un `Enum`
+
+Importez `Enum` et créez une sous-classe qui hérite de `str` et `Enum`.
+
+En héritant de `str` la documentation sera capable de savoir que les valeurs doivent être de type `string` et pourra donc afficher cette `Enum` correctement.
+
+Créez ensuite des attributs de classe avec des valeurs fixes, qui seront les valeurs autorisées pour cette énumération.
+
+```Python hl_lines="1 6-9"
+{!../../../docs_src/path_params/tutorial005.py!}
+```
+
+!!! info
+ Les énumérations (ou enums) sont disponibles en Python depuis la version 3.4.
+
+!!! tip "Astuce"
+ Pour ceux qui se demandent, "AlexNet", "ResNet", et "LeNet" sont juste des noms de modèles de Machine Learning.
+
+### Déclarer un paramètre de chemin
+
+Créez ensuite un *paramètre de chemin* avec une annotation de type désignant l'énumération créée précédemment (`ModelName`) :
+
+```Python hl_lines="16"
+{!../../../docs_src/path_params/tutorial005.py!}
+```
+
+### Documentation
+
+Les valeurs disponibles pour le *paramètre de chemin* sont bien prédéfinies, la documentation les affiche correctement :
+
+
+
+### Manipuler les *énumérations* Python
+
+La valeur du *paramètre de chemin* sera un des "membres" de l'énumération.
+
+#### Comparer les *membres d'énumération*
+
+Vous pouvez comparer ce paramètre avec les membres de votre énumération `ModelName` :
+
+```Python hl_lines="17"
+{!../../../docs_src/path_params/tutorial005.py!}
+```
+
+#### Récupérer la *valeur de l'énumération*
+
+Vous pouvez obtenir la valeur réel d'un membre (une chaîne de caractères ici), avec `model_name.value`, ou en général, `votre_membre_d'enum.value` :
+
+```Python hl_lines="20"
+{!../../../docs_src/path_params/tutorial005.py!}
+```
+
+!!! tip "Astuce"
+ Vous pouvez aussi accéder la valeur `"lenet"` avec `ModelName.lenet.value`.
+
+#### Retourner des *membres d'énumération*
+
+Vous pouvez retourner des *membres d'énumération* dans vos *fonctions de chemin*, même imbriquée dans un JSON (e.g. un `dict`).
+
+Ils seront convertis vers leurs valeurs correspondantes (chaînes de caractères ici) avant d'être transmis au client :
+
+```Python hl_lines="18 21 23"
+{!../../../docs_src/path_params/tutorial005.py!}
+```
+
+Le client recevra une réponse JSON comme celle-ci :
+
+```JSON
+{
+ "model_name": "alexnet",
+ "message": "Deep Learning FTW!"
+}
+```
+
+## Paramètres de chemin contenant des chemins
+
+Disons que vous avez une *fonction de chemin* liée au chemin `/files/{file_path}`.
+
+Mais que `file_path` lui-même doit contenir un *chemin*, comme `home/johndoe/myfile.txt` par exemple.
+
+Donc, l'URL pour ce fichier pourrait être : `/files/home/johndoe/myfile.txt`.
+
+### Support d'OpenAPI
+
+OpenAPI ne supporte pas de manière de déclarer un paramètre de chemin contenant un *chemin*, cela pouvant causer des scénarios difficiles à tester et définir.
+
+Néanmoins, cela reste faisable dans **FastAPI**, via les outils internes de Starlette.
+
+Et la documentation fonctionne quand même, bien qu'aucune section ne soit ajoutée pour dire que la paramètre devrait contenir un *chemin*.
+
+### Convertisseur de *chemin*
+
+En utilisant une option de Starlette directement, vous pouvez déclarer un *paramètre de chemin* contenant un *chemin* avec une URL comme :
+
+```
+/files/{file_path:path}
+```
+
+Dans ce cas, le nom du paramètre est `file_path`, et la dernière partie, `:path`, indique à Starlette que le paramètre devrait correspondre à un *chemin*.
+
+Vous pouvez donc l'utilisez comme tel :
+
+```Python hl_lines="6"
+{!../../../docs_src/path_params/tutorial004.py!}
+```
+
+!!! tip "Astuce"
+ Vous pourriez avoir besoin que le paramètre contienne `/home/johndoe/myfile.txt`, avec un slash au début (`/`).
+
+ Dans ce cas, l'URL serait : `/files//home/johndoe/myfile.txt`, avec un double slash (`//`) entre `files` et `home`.
+
+## Récapitulatif
+
+Avec **FastAPI**, en utilisant les déclarations de type rapides, intuitives et standards de Python, vous bénéficiez de :
+
+* Support de l'éditeur : vérification d'erreurs, auto-complétion, etc.
+* "Parsing" de données.
+* Validation de données.
+* Annotations d'API et documentation automatique.
+
+Et vous n'avez besoin de le déclarer qu'une fois.
+
+C'est probablement l'avantage visible principal de **FastAPI** comparé aux autres *frameworks* (outre les performances pures).
diff --git a/docs/fr/docs/tutorial/query-params.md b/docs/fr/docs/tutorial/query-params.md
new file mode 100644
index 00000000..f1f2a605
--- /dev/null
+++ b/docs/fr/docs/tutorial/query-params.md
@@ -0,0 +1,198 @@
+# Paramètres de requête
+
+Quand vous déclarez des paramètres dans votre fonction de chemin qui ne font pas partie des paramètres indiqués dans le chemin associé, ces paramètres sont automatiquement considérés comme des paramètres de "requête".
+
+```Python hl_lines="9"
+{!../../../docs_src/query_params/tutorial001.py!}
+```
+
+La partie appelée requête (ou **query**) dans une URL est l'ensemble des paires clés-valeurs placées après le `?` , séparées par des `&`.
+
+Par exemple, dans l'URL :
+
+```
+http://127.0.0.1:8000/items/?skip=0&limit=10
+```
+
+...les paramètres de requête sont :
+
+* `skip` : avec une valeur de`0`
+* `limit` : avec une valeur de `10`
+
+Faisant partie de l'URL, ces valeurs sont des chaînes de caractères (`str`).
+
+Mais quand on les déclare avec des types Python (dans l'exemple précédent, en tant qu'`int`), elles sont converties dans les types renseignés.
+
+Toutes les fonctionnalités qui s'appliquent aux paramètres de chemin s'appliquent aussi aux paramètres de requête :
+
+* Support de l'éditeur : vérification d'erreurs, auto-complétion, etc.
+* "Parsing" de données.
+* Validation de données.
+* Annotations d'API et documentation automatique.
+
+## Valeurs par défaut
+
+Les paramètres de requête ne sont pas une partie fixe d'un chemin, ils peuvent être optionnels et avoir des valeurs par défaut.
+
+Dans l'exemple ci-dessus, ils ont des valeurs par défaut qui sont `skip=0` et `limit=10`.
+
+Donc, accéder à l'URL :
+
+```
+http://127.0.0.1:8000/items/
+```
+
+serait équivalent à accéder à l'URL :
+
+```
+http://127.0.0.1:8000/items/?skip=0&limit=10
+```
+
+Mais si vous accédez à, par exemple :
+
+```
+http://127.0.0.1:8000/items/?skip=20
+```
+
+Les valeurs des paramètres de votre fonction seront :
+
+* `skip=20` : car c'est la valeur déclarée dans l'URL.
+* `limit=10` : car `limit` n'a pas été déclaré dans l'URL, et que la valeur par défaut était `10`.
+
+## Paramètres optionnels
+
+De la même façon, vous pouvez définir des paramètres de requête comme optionnels, en leur donnant comme valeur par défaut `None` :
+
+```Python hl_lines="9"
+{!../../../docs_src/query_params/tutorial002.py!}
+```
+
+Ici, le paramètre `q` sera optionnel, et aura `None` comme valeur par défaut.
+
+!!! check "Remarque"
+ On peut voir que **FastAPI** est capable de détecter que le paramètre de chemin `item_id` est un paramètre de chemin et que `q` n'en est pas un, c'est donc un paramètre de requête.
+
+!!! note
+ **FastAPI** saura que `q` est optionnel grâce au `=None`.
+
+ Le `Optional` dans `Optional[str]` n'est pas utilisé par **FastAPI** (**FastAPI** n'en utilisera que la partie `str`), mais il servira tout de même à votre editeur de texte pour détecter des erreurs dans votre code.
+
+
+## Conversion des types des paramètres de requête
+
+Vous pouvez aussi déclarer des paramètres de requête comme booléens (`bool`), **FastAPI** les convertira :
+
+```Python hl_lines="9"
+{!../../../docs_src/query_params/tutorial003.py!}
+```
+
+Avec ce code, en allant sur :
+
+```
+http://127.0.0.1:8000/items/foo?short=1
+```
+
+ou
+
+```
+http://127.0.0.1:8000/items/foo?short=True
+```
+
+ou
+
+```
+http://127.0.0.1:8000/items/foo?short=true
+```
+
+ou
+
+```
+http://127.0.0.1:8000/items/foo?short=on
+```
+
+ou
+
+```
+http://127.0.0.1:8000/items/foo?short=yes
+```
+
+ou n'importe quelle autre variation de casse (tout en majuscules, uniquement la première lettre en majuscule, etc.), votre fonction considérera le paramètre `short` comme ayant une valeur booléenne à `True`. Sinon la valeur sera à `False`.
+
+## Multiples paramètres de chemin et de requête
+
+Vous pouvez déclarer plusieurs paramètres de chemin et paramètres de requête dans la même fonction, **FastAPI** saura comment les gérer.
+
+Et vous n'avez pas besoin de les déclarer dans un ordre spécifique.
+
+Ils seront détectés par leurs noms :
+
+```Python hl_lines="8 10"
+{!../../../docs_src/query_params/tutorial004.py!}
+```
+
+## Paramètres de requête requis
+
+Quand vous déclarez une valeur par défaut pour un paramètre qui n'est pas un paramètre de chemin (actuellement, nous n'avons vu que les paramètres de requête), alors ce paramètre n'est pas requis.
+
+Si vous ne voulez pas leur donner de valeur par défaut mais juste les rendre optionnels, utilisez `None` comme valeur par défaut.
+
+Mais si vous voulez rendre un paramètre de requête obligatoire, vous pouvez juste ne pas y affecter de valeur par défaut :
+
+```Python hl_lines="6-7"
+{!../../../docs_src/query_params/tutorial005.py!}
+```
+
+Ici le paramètre `needy` est un paramètre requis (ou obligatoire) de type `str`.
+
+Si vous ouvrez une URL comme :
+
+```
+http://127.0.0.1:8000/items/foo-item
+```
+
+...sans ajouter le paramètre requis `needy`, vous aurez une erreur :
+
+```JSON
+{
+ "detail": [
+ {
+ "loc": [
+ "query",
+ "needy"
+ ],
+ "msg": "field required",
+ "type": "value_error.missing"
+ }
+ ]
+}
+```
+
+La présence de `needy` étant nécessaire, vous auriez besoin de l'insérer dans l'URL :
+
+```
+http://127.0.0.1:8000/items/foo-item?needy=sooooneedy
+```
+
+...ce qui fonctionnerait :
+
+```JSON
+{
+ "item_id": "foo-item",
+ "needy": "sooooneedy"
+}
+```
+
+Et bien sur, vous pouvez définir certains paramètres comme requis, certains avec des valeurs par défaut et certains entièrement optionnels :
+
+```Python hl_lines="10"
+{!../../../docs_src/query_params/tutorial006.py!}
+```
+
+Ici, on a donc 3 paramètres de requête :
+
+* `needy`, requis et de type `str`.
+* `skip`, un `int` avec comme valeur par défaut `0`.
+* `limit`, un `int` optionnel.
+
+!!! tip "Astuce"
+ Vous pouvez utiliser les `Enum`s de la même façon qu'avec les [Paramètres de chemin](path-params.md#valeurs-predefinies){.internal-link target=_blank}.
diff --git a/docs/fr/mkdocs.yml b/docs/fr/mkdocs.yml
index 09c13ed8..8d15130f 100644
--- a/docs/fr/mkdocs.yml
+++ b/docs/fr/mkdocs.yml
@@ -9,17 +9,18 @@ theme:
primary: teal
accent: amber
toggle:
- icon: material/lightbulb-outline
+ icon: material/lightbulb
name: Switch to light mode
- scheme: slate
primary: teal
accent: amber
toggle:
- icon: material/lightbulb
+ icon: material/lightbulb-outline
name: Switch to dark mode
features:
- search.suggest
- search.highlight
+ - content.tabs.link
icon:
repo: fontawesome/brands/github-alt
logo: https://fastapi.tiangolo.com/img/icon-white.svg
@@ -28,9 +29,6 @@ theme:
repo_name: tiangolo/fastapi
repo_url: https://github.com/tiangolo/fastapi
edit_uri: ''
-google_analytics:
-- UA-133183413-1
-- auto
plugins:
- search
- markdownextradata:
@@ -39,11 +37,16 @@ nav:
- FastAPI: index.md
- Languages:
- en: /
+ - az: /az/
+ - de: /de/
- es: /es/
+ - fa: /fa/
- fr: /fr/
+ - id: /id/
- it: /it/
- ja: /ja/
- ko: /ko/
+ - nl: /nl/
- pl: /pl/
- pt: /pt/
- ru: /ru/
@@ -55,7 +58,14 @@ nav:
- fastapi-people.md
- python-types.md
- Tutoriel - Guide utilisateur:
+ - tutorial/first-steps.md
+ - tutorial/path-params.md
+ - tutorial/query-params.md
+ - tutorial/body.md
- tutorial/background-tasks.md
+- async.md
+- Déploiement:
+ - deployment/docker.md
- project-generation.md
- alternatives.md
- history-design-future.md
@@ -65,7 +75,7 @@ markdown_extensions:
permalink: true
- markdown.extensions.codehilite:
guess_lang: false
-- markdown_include.include:
+- mdx_include:
base_path: docs
- admonition
- codehilite
@@ -74,16 +84,20 @@ markdown_extensions:
custom_fences:
- name: mermaid
class: mermaid
- format: !!python/name:pymdownx.superfences.fence_div_format ''
-- pymdownx.tabbed
+ format: !!python/name:pymdownx.superfences.fence_code_format ''
+- pymdownx.tabbed:
+ alternate_style: true
extra:
+ analytics:
+ provider: google
+ property: UA-133183413-1
social:
- icon: fontawesome/brands/github-alt
link: https://github.com/tiangolo/fastapi
- icon: fontawesome/brands/discord
link: https://discord.gg/VQjSZaeJmf
- icon: fontawesome/brands/twitter
- link: https://twitter.com/tiangolo
+ link: https://twitter.com/fastapi
- icon: fontawesome/brands/linkedin
link: https://www.linkedin.com/in/tiangolo
- icon: fontawesome/brands/dev
@@ -95,16 +109,26 @@ extra:
alternate:
- link: /
name: en - English
+ - link: /az/
+ name: az
+ - link: /de/
+ name: de
- link: /es/
name: es - español
+ - link: /fa/
+ name: fa
- link: /fr/
name: fr - français
+ - link: /id/
+ name: id
- link: /it/
name: it - italiano
- link: /ja/
name: ja - 日本語
- link: /ko/
name: ko - 한국어
+ - link: /nl/
+ name: nl
- link: /pl/
name: pl
- link: /pt/
@@ -123,6 +147,5 @@ extra_css:
- https://fastapi.tiangolo.com/css/termynal.css
- https://fastapi.tiangolo.com/css/custom.css
extra_javascript:
-- https://unpkg.com/mermaid@8.4.6/dist/mermaid.min.js
- https://fastapi.tiangolo.com/js/termynal.js
- https://fastapi.tiangolo.com/js/custom.js
diff --git a/docs/id/docs/index.md b/docs/id/docs/index.md
new file mode 100644
index 00000000..a7af1478
--- /dev/null
+++ b/docs/id/docs/index.md
@@ -0,0 +1,466 @@
+
+{!../../../docs/missing-translation.md!}
+
+
+
++ FastAPI framework, high performance, easy to learn, fast to code, ready for production +
+ + +--- + +**Documentation**: https://fastapi.tiangolo.com + +**Source Code**: https://github.com/tiangolo/fastapi + +--- + +FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints. + +The key features are: + +* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance). + +* **Fast to code**: Increase the speed to develop features by about 200% to 300%. * +* **Fewer bugs**: Reduce about 40% of human (developer) induced errors. * +* **Intuitive**: Great editor support. Completion everywhere. Less time debugging. +* **Easy**: Designed to be easy to use and learn. Less time reading docs. +* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs. +* **Robust**: Get production-ready code. With automatic interactive documentation. +* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: OpenAPI (previously known as Swagger) and JSON Schema. + +* estimation based on tests on an internal development team, building production applications. + +## Sponsors + + + +{% if sponsors %} +{% for sponsor in sponsors.gold -%} +async def...uvicorn main:app --reload...ujson - for faster JSON "parsing".
+* email_validator - for email validation.
+
+Used by Starlette:
+
+* requests - Required if you want to use the `TestClient`.
+* jinja2 - Required if you want to use the default template configuration.
+* python-multipart - Required if you want to support form "parsing", with `request.form()`.
+* itsdangerous - Required for `SessionMiddleware` support.
+* pyyaml - Required for Starlette's `SchemaGenerator` support (you probably don't need it with FastAPI).
+* graphene - Required for `GraphQLApp` support.
+* ujson - Required if you want to use `UJSONResponse`.
+
+Used by FastAPI / Starlette:
+
+* uvicorn - for the server that loads and serves your application.
+* orjson - Required if you want to use `ORJSONResponse`.
+
+You can install all of these with `pip install fastapi[all]`.
+
+## License
+
+This project is licensed under the terms of the MIT license.
diff --git a/docs/id/mkdocs.yml b/docs/id/mkdocs.yml
new file mode 100644
index 00000000..0c60fecd
--- /dev/null
+++ b/docs/id/mkdocs.yml
@@ -0,0 +1,135 @@
+site_name: FastAPI
+site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production
+site_url: https://fastapi.tiangolo.com/id/
+theme:
+ name: material
+ custom_dir: overrides
+ palette:
+ - scheme: default
+ primary: teal
+ accent: amber
+ toggle:
+ icon: material/lightbulb
+ name: Switch to light mode
+ - scheme: slate
+ primary: teal
+ accent: amber
+ toggle:
+ icon: material/lightbulb-outline
+ name: Switch to dark mode
+ features:
+ - search.suggest
+ - search.highlight
+ - content.tabs.link
+ icon:
+ repo: fontawesome/brands/github-alt
+ logo: https://fastapi.tiangolo.com/img/icon-white.svg
+ favicon: https://fastapi.tiangolo.com/img/favicon.png
+ language: id
+repo_name: tiangolo/fastapi
+repo_url: https://github.com/tiangolo/fastapi
+edit_uri: ''
+plugins:
+- search
+- markdownextradata:
+ data: data
+nav:
+- FastAPI: index.md
+- Languages:
+ - en: /
+ - az: /az/
+ - de: /de/
+ - es: /es/
+ - fa: /fa/
+ - fr: /fr/
+ - id: /id/
+ - it: /it/
+ - ja: /ja/
+ - ko: /ko/
+ - nl: /nl/
+ - pl: /pl/
+ - pt: /pt/
+ - ru: /ru/
+ - sq: /sq/
+ - tr: /tr/
+ - uk: /uk/
+ - zh: /zh/
+markdown_extensions:
+- toc:
+ permalink: true
+- markdown.extensions.codehilite:
+ guess_lang: false
+- mdx_include:
+ base_path: docs
+- admonition
+- codehilite
+- extra
+- pymdownx.superfences:
+ custom_fences:
+ - name: mermaid
+ class: mermaid
+ format: !!python/name:pymdownx.superfences.fence_code_format ''
+- pymdownx.tabbed:
+ alternate_style: true
+extra:
+ analytics:
+ provider: google
+ property: UA-133183413-1
+ social:
+ - icon: fontawesome/brands/github-alt
+ link: https://github.com/tiangolo/fastapi
+ - icon: fontawesome/brands/discord
+ link: https://discord.gg/VQjSZaeJmf
+ - icon: fontawesome/brands/twitter
+ link: https://twitter.com/fastapi
+ - icon: fontawesome/brands/linkedin
+ link: https://www.linkedin.com/in/tiangolo
+ - icon: fontawesome/brands/dev
+ link: https://dev.to/tiangolo
+ - icon: fontawesome/brands/medium
+ link: https://medium.com/@tiangolo
+ - icon: fontawesome/solid/globe
+ link: https://tiangolo.com
+ alternate:
+ - link: /
+ name: en - English
+ - link: /az/
+ name: az
+ - link: /de/
+ name: de
+ - link: /es/
+ name: es - español
+ - link: /fa/
+ name: fa
+ - link: /fr/
+ name: fr - français
+ - link: /id/
+ name: id
+ - link: /it/
+ name: it - italiano
+ - link: /ja/
+ name: ja - 日本語
+ - link: /ko/
+ name: ko - 한국어
+ - link: /nl/
+ name: nl
+ - link: /pl/
+ name: pl
+ - link: /pt/
+ name: pt - português
+ - link: /ru/
+ name: ru - русский язык
+ - link: /sq/
+ name: sq - shqip
+ - link: /tr/
+ name: tr - Türkçe
+ - link: /uk/
+ name: uk - українська мова
+ - link: /zh/
+ name: zh - 汉语
+extra_css:
+- https://fastapi.tiangolo.com/css/termynal.css
+- https://fastapi.tiangolo.com/css/custom.css
+extra_javascript:
+- https://fastapi.tiangolo.com/js/termynal.js
+- https://fastapi.tiangolo.com/js/custom.js
diff --git a/docs/id/overrides/.gitignore b/docs/id/overrides/.gitignore
new file mode 100644
index 00000000..e69de29b
diff --git a/docs/it/docs/index.md b/docs/it/docs/index.md
index 318acc41..e9e42856 100644
--- a/docs/it/docs/index.md
+++ b/docs/it/docs/index.md
@@ -44,13 +44,16 @@ The key features are:
* estimation based on tests on an internal development team, building production applications.
-## Gold Sponsors
+## Sponsors
{% if sponsors %}
{% for sponsor in sponsors.gold -%}
-requests - Required if you want to use the `TestClient`.
-* aiofiles - Required if you want to use `FileResponse` or `StaticFiles`.
* jinja2 - Required if you want to use the default template configuration.
* python-multipart - Required if you want to support form "parsing", with `request.form()`.
* itsdangerous - Required for `SessionMiddleware` support.
diff --git a/docs/it/mkdocs.yml b/docs/it/mkdocs.yml
index f0373046..3bf3d739 100644
--- a/docs/it/mkdocs.yml
+++ b/docs/it/mkdocs.yml
@@ -9,17 +9,18 @@ theme:
primary: teal
accent: amber
toggle:
- icon: material/lightbulb-outline
+ icon: material/lightbulb
name: Switch to light mode
- scheme: slate
primary: teal
accent: amber
toggle:
- icon: material/lightbulb
+ icon: material/lightbulb-outline
name: Switch to dark mode
features:
- search.suggest
- search.highlight
+ - content.tabs.link
icon:
repo: fontawesome/brands/github-alt
logo: https://fastapi.tiangolo.com/img/icon-white.svg
@@ -28,9 +29,6 @@ theme:
repo_name: tiangolo/fastapi
repo_url: https://github.com/tiangolo/fastapi
edit_uri: ''
-google_analytics:
-- UA-133183413-1
-- auto
plugins:
- search
- markdownextradata:
@@ -39,11 +37,16 @@ nav:
- FastAPI: index.md
- Languages:
- en: /
+ - az: /az/
+ - de: /de/
- es: /es/
+ - fa: /fa/
- fr: /fr/
+ - id: /id/
- it: /it/
- ja: /ja/
- ko: /ko/
+ - nl: /nl/
- pl: /pl/
- pt: /pt/
- ru: /ru/
@@ -56,7 +59,7 @@ markdown_extensions:
permalink: true
- markdown.extensions.codehilite:
guess_lang: false
-- markdown_include.include:
+- mdx_include:
base_path: docs
- admonition
- codehilite
@@ -65,16 +68,20 @@ markdown_extensions:
custom_fences:
- name: mermaid
class: mermaid
- format: !!python/name:pymdownx.superfences.fence_div_format ''
-- pymdownx.tabbed
+ format: !!python/name:pymdownx.superfences.fence_code_format ''
+- pymdownx.tabbed:
+ alternate_style: true
extra:
+ analytics:
+ provider: google
+ property: UA-133183413-1
social:
- icon: fontawesome/brands/github-alt
link: https://github.com/tiangolo/fastapi
- icon: fontawesome/brands/discord
link: https://discord.gg/VQjSZaeJmf
- icon: fontawesome/brands/twitter
- link: https://twitter.com/tiangolo
+ link: https://twitter.com/fastapi
- icon: fontawesome/brands/linkedin
link: https://www.linkedin.com/in/tiangolo
- icon: fontawesome/brands/dev
@@ -86,16 +93,26 @@ extra:
alternate:
- link: /
name: en - English
+ - link: /az/
+ name: az
+ - link: /de/
+ name: de
- link: /es/
name: es - español
+ - link: /fa/
+ name: fa
- link: /fr/
name: fr - français
+ - link: /id/
+ name: id
- link: /it/
name: it - italiano
- link: /ja/
name: ja - 日本語
- link: /ko/
name: ko - 한국어
+ - link: /nl/
+ name: nl
- link: /pl/
name: pl
- link: /pt/
@@ -114,6 +131,5 @@ extra_css:
- https://fastapi.tiangolo.com/css/termynal.css
- https://fastapi.tiangolo.com/css/custom.css
extra_javascript:
-- https://unpkg.com/mermaid@8.4.6/dist/mermaid.min.js
- https://fastapi.tiangolo.com/js/termynal.js
- https://fastapi.tiangolo.com/js/custom.js
diff --git a/docs/ja/docs/advanced/conditional-openapi.md b/docs/ja/docs/advanced/conditional-openapi.md
new file mode 100644
index 00000000..b892ed6c
--- /dev/null
+++ b/docs/ja/docs/advanced/conditional-openapi.md
@@ -0,0 +1,58 @@
+# 条件付き OpenAPI
+
+必要であれば、設定と環境変数を利用して、環境に応じて条件付きでOpenAPIを構成することが可能です。また、完全にOpenAPIを無効にすることもできます。
+
+## セキュリティとAPI、およびドキュメントについて
+
+本番環境においてドキュメントのUIを非表示にすることによって、APIを保護しようと *すべきではありません*。
+
+それは、APIのセキュリティの強化にはならず、*path operations* は依然として利用可能です。
+
+もしセキュリティ上の欠陥がソースコードにあるならば、それは存在したままです。
+
+ドキュメンテーションを非表示にするのは、単にあなたのAPIへのアクセス方法を難解にするだけでなく、同時にあなた自身の本番環境でのAPIのデバッグを困難にしてしまう可能性があります。単純に、 Security through obscurity の一つの形態として考えられるでしょう。
+
+もしあなたのAPIのセキュリティを強化したいなら、いくつかのよりよい方法があります。例を示すと、
+
+* リクエストボディとレスポンスのためのPydanticモデルの定義を見直す。
+* 依存関係に基づきすべての必要なパーミッションとロールを設定する。
+* パスワードを絶対に平文で保存しない。パスワードハッシュのみを保存する。
+* PasslibやJWTトークンに代表される、よく知られた暗号化ツールを使って実装する。
+* そして必要なところでは、もっと細かいパーミッション制御をOAuth2スコープを使って行う。
+* など
+
+それでも、例えば本番環境のような特定の環境のみで、あるいは環境変数の設定によってAPIドキュメントをどうしても無効にしたいという、非常に特殊なユースケースがあるかもしれません。
+
+## 設定と環境変数による条件付き OpenAPI
+
+生成するOpenAPIとドキュメントUIの構成は、共通のPydanticの設定を使用して簡単に切り替えられます。
+
+例えば、
+
+```Python hl_lines="6 11"
+{!../../../docs_src/conditional_openapi/tutorial001.py!}
+```
+
+ここでは `openapi_url` の設定を、デフォルトの `"/openapi.json"` のまま宣言しています。
+
+そして、これを `FastAPI` appを作る際に使います。
+
+それから、以下のように `OPENAPI_URL` という環境変数を空文字列に設定することによってOpenAPI (UIドキュメントを含む) を無効化することができます。
+
+requests - `TestClient`を使用するために必要です。
-- aiofiles - `FileResponse` または `StaticFiles`を使用したい場合は必要です。
- jinja2 - デフォルトのテンプレート設定を使用する場合は必要です。
- python-multipart - "parsing"`request.form()`からの変換をサポートしたい場合は必要です。
- itsdangerous - `SessionMiddleware` サポートのためには必要です。
diff --git a/docs/ja/docs/tutorial/body.md b/docs/ja/docs/tutorial/body.md
index 9367ea25..59388d90 100644
--- a/docs/ja/docs/tutorial/body.md
+++ b/docs/ja/docs/tutorial/body.md
@@ -162,4 +162,4 @@ APIはほとんどの場合 **レスポンス** ボディを送らなければ
## Pydanticを使わない方法
-もしPydanticモデルを使用したくない場合は、**ボディ**パラメータが利用できます。[Body - Multiple Parameters: Singular values in body](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}を確認してください。
+もしPydanticモデルを使用したくない場合は、**Body**パラメータが利用できます。[Body - Multiple Parameters: Singular values in body](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}を確認してください。
diff --git a/docs/ja/docs/tutorial/security/oauth2-jwt.md b/docs/ja/docs/tutorial/security/oauth2-jwt.md
new file mode 100644
index 00000000..348ffda0
--- /dev/null
+++ b/docs/ja/docs/tutorial/security/oauth2-jwt.md
@@ -0,0 +1,266 @@
+# パスワード(およびハッシュ化)によるOAuth2、JWTトークンによるBearer
+
+これでセキュリティの流れが全てわかったので、JWTトークンと安全なパスワードのハッシュ化を使用して、実際にアプリケーションを安全にしてみましょう。
+
+このコードは、アプリケーションで実際に使用したり、パスワードハッシュをデータベースに保存するといった用途に利用できます。
+
+本章では、前章の続きから始めて、コードをアップデートしていきます。
+
+## JWT について
+
+JWTとは「JSON Web Tokens」の略称です。
+
+JSONオブジェクトをスペースのない長く密集した文字列で表現したトークンの仕様です。例えば次のようになります:
+
+```
+eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
+```
+
+これらは暗号化されていないので、誰でもコンテンツから情報を復元できてしまいます。
+
+しかし、トークンは署名されているため、あなたが発行したトークンを受け取った人は、あなたが実際に発行したということを検証できます。
+
+例えば、1週間の有効期限を持つトークンを作成したとします。ユーザーが翌日そのトークンを持って戻ってきたとき、そのユーザーはまだシステムにログインしていることがわかります。
+
+1週間後、トークンが期限切れとなるとどうなるでしょうか?ユーザーは認可されず、新しいトークンを得るために再びサインインしなければなりません。また、ユーザー(または第三者)がトークンを修正して有効期限を変更しようとした場合、署名が一致しないため、トークンの修正を検知できます。
+
+JWT トークンを使って遊んでみたいという方は、https://jwt.io をチェックしてください。
+
+## `python-jose` のインストール
+
+PythonでJWTトークンの生成と検証を行うために、`python-jose`をインストールする必要があります:
+
+
+
+前回と同じ方法でアプリケーションの認可を行います。
+
+次の認証情報を使用します:
+
+Username: `johndoe`
+Password: `secret`
+
+!!! check "確認"
+ コードのどこにも平文のパスワード"`secret`"はなく、ハッシュ化されたものしかないことを確認してください。
+
+
+
+エンドポイント`/users/me/`を呼び出すと、次のようなレスポンスが得られます:
+
+```JSON
+{
+ "username": "johndoe",
+ "email": "johndoe@example.com",
+ "full_name": "John Doe",
+ "disabled": false
+}
+```
+
+
+
+開発者ツールを開くと、送信されるデータにはトークンだけが含まれており、パスワードはユーザーを認証してアクセストークンを取得する最初のリクエストでのみ送信され、その後は送信されないことがわかります。
+
+
+
+!!! note "備考"
+ ヘッダーの`Authorization`には、`Bearer`で始まる値があります。
+
+## `scopes` を使った高度なユースケース
+
+OAuth2には、「スコープ」という概念があります。
+
+これらを利用して、JWTトークンに特定の権限セットを追加することができます。
+
+そして、このトークンをユーザーに直接、または第三者に与えて、制限付きでAPIを操作できます。
+
+これらの使用方法や**FastAPI**への統合方法については、**高度なユーザーガイド**で後ほど説明します。
+
+## まとめ
+
+ここまでの説明で、OAuth2やJWTなどの規格を使った安全な**FastAPI**アプリケーションを設定することができます。
+
+ほとんどのフレームワークにおいて、セキュリティを扱うことは非常に複雑な課題となります。
+
+簡略化しすぎたパッケージの多くは、データモデルやデータベース、利用可能な機能について多くの妥協をしなければなりません。そして、あまりにも単純化されたパッケージの中には、実はセキュリティ上の欠陥があるものもあります。
+
+---
+
+**FastAPI**は、どのようなデータベース、データモデル、ツールに対しても妥協することはありません。
+
+そのため、プロジェクトに合わせて自由に選択することができます。
+
+また、**FastAPI**は外部パッケージを統合するために複雑な仕組みを必要としないため、`passlib`や`python-jose`のようなよく整備され広く使われている多くのパッケージを直接使用することができます。
+
+しかし、柔軟性、堅牢性、セキュリティを損なうことなく、可能な限りプロセスを簡素化するためのツールを提供します。
+
+また、OAuth2のような安全で標準的なプロトコルを比較的簡単な方法で使用できるだけではなく、実装することもできます。
+
+OAuth2の「スコープ」を使って、同じ基準でより細かい権限システムを実現する方法については、**高度なユーザーガイド**で詳しく説明しています。スコープ付きのOAuth2は、Facebook、Google、GitHub、Microsoft、Twitterなど、多くの大手認証プロバイダが、サードパーティのアプリケーションと自社のAPIとのやり取りをユーザーに代わって認可するために使用している仕組みです。
diff --git a/docs/ja/docs/tutorial/static-files.md b/docs/ja/docs/tutorial/static-files.md
index fcc3ba92..1d9c434c 100644
--- a/docs/ja/docs/tutorial/static-files.md
+++ b/docs/ja/docs/tutorial/static-files.md
@@ -2,20 +2,6 @@
`StaticFiles` を使用して、ディレクトリから静的ファイルを自動的に提供できます。
-## `aiofiles` をインストール
-
-まず、`aiofiles` をインストールする必要があります:
-
-requests - `TestClient`를 사용하려면 필요.
-* aiofiles - `FileResponse` 또는 `StaticFiles`를 사용하려면 필요.
* jinja2 - 기본 템플릿 설정을 사용하려면 필요.
* python-multipart - `request.form()`과 함께 "parsing"의 지원을 원하면 필요.
* itsdangerous - `SessionMiddleware` 지원을 위해 필요.
-* pyyaml - Starlette의 `SchemaGenerator` 지원을 위해 필요 (FastAPI와 쓸때는 필요가 없을 겁니다).
+* pyyaml - Starlette의 `SchemaGenerator` 지원을 위해 필요 (FastAPI와 쓸때는 필요 없을 것입니다).
* graphene - `GraphQLApp` 지원을 위해 필요.
* ujson - `UJSONResponse`를 사용하려면 필요.
diff --git a/docs/ko/docs/tutorial/encoder.md b/docs/ko/docs/tutorial/encoder.md
new file mode 100644
index 00000000..8b5fdb8b
--- /dev/null
+++ b/docs/ko/docs/tutorial/encoder.md
@@ -0,0 +1,34 @@
+# JSON 호환 가능 인코더
+
+데이터 유형(예: Pydantic 모델)을 JSON과 호환된 형태로 반환해야 하는 경우가 있습니다. (예: `dict`, `list` 등)
+
+예를 들면, 데이터베이스에 저장해야하는 경우입니다.
+
+이를 위해, **FastAPI** 에서는 `jsonable_encoder()` 함수를 제공합니다.
+
+## `jsonable_encoder` 사용
+
+JSON 호환 가능 데이터만 수신하는 `fake_db` 데이터베이스가 존재한다고 가정하겠습니다.
+
+예를 들면, `datetime` 객체는 JSON과 호환되는 데이터가 아니므로 이 데이터는 받아들여지지 않습니다.
+
+따라서 `datetime` 객체는 ISO format 데이터를 포함하는 `str`로 변환되어야 합니다.
+
+같은 방식으로 이 데이터베이스는 Pydantic 모델(속성이 있는 객체)을 받지 않고, `dict` 만을 받습니다.
+
+이를 위해 `jsonable_encoder` 를 사용할 수 있습니다.
+
+Pydantic 모델과 같은 객체를 받고 JSON 호환 가능한 버전으로 반환합니다:
+
+```Python hl_lines="5 22"
+{!../../../docs_src/encoder/tutorial001.py!}
+```
+
+이 예시는 Pydantic 모델을 `dict`로, `datetime` 형식을 `str`로 변환합니다.
+
+이렇게 호출한 결과는 파이썬 표준인 `json.dumps()`로 인코딩 할 수 있습니다.
+
+길이가 긴 문자열 형태의 JSON 형식(문자열)의 데이터가 들어있는 상황에서는 `str`로 반환하지 않습니다. JSON과 모두 호환되는 값과 하위 값이 있는 Python 표준 데이터 구조 (예: `dict`)를 반환합니다.
+
+!!! note "참고"
+ 실제로 `jsonable_encoder`는 **FastAPI** 에서 내부적으로 데이터를 변환하는 데 사용하지만, 다른 많은 곳에서도 이는 유용합니다.
diff --git a/docs/ko/docs/tutorial/index.md b/docs/ko/docs/tutorial/index.md
index a18a9ffc..622aad1a 100644
--- a/docs/ko/docs/tutorial/index.md
+++ b/docs/ko/docs/tutorial/index.md
@@ -2,11 +2,11 @@
이 자습서는 **FastAPI**의 대부분의 기능을 단계별로 사용하는 방법을 보여줍니다.
-각 섹션은 이전 섹션을 기반해서 점진적으로 만들어 졌지만, 주제를 구분하여 구성 되었기 때문에 특정 API 요구사항을 해결하기 위해 어떤 특정 항목이던지 직접 이동할 수 있습니다.
+각 섹션은 이전 섹션을 기반해서 점진적으로 만들어 졌지만, 주제에 따라 다르게 구성되었기 때문에 특정 API 요구사항을 해결하기 위해서라면 어느 특정 항목으로던지 직접 이동할 수 있습니다.
또한 향후 참조가 될 수 있도록 만들어졌습니다.
-그러므로 다시 돌아와서 정확히 필요한 것을 볼 수 있습니다.
+그러므로 다시 돌아와서 정확히 필요한 것을 확인할 수 있습니다.
## 코드 실행하기
@@ -30,7 +30,7 @@ $ uvicorn main:app --reload
코드를 작성하거나 복사, 편집할 때, 로컬에서 실행하는 것을 **강력히 장려**합니다.
-편집기에서 이렇게 사용하면, 모든 타입 검사, 자동완성 등 작성해야 하는 코드가 얼마나 적은지 보면서 FastAPI의 장점을 실제로 확인할 수 있습니다.
+편집기에서 이렇게 사용한다면, 모든 타입 검사와 자동완성 등 작성해야 하는 코드가 얼마나 적은지 보면서 FastAPI의 장점을 실제로 확인할 수 있습니다.
---
@@ -77,4 +77,4 @@ $ pip install fastapi[all]
하지만 (지금 읽고 있는) **자습서 - 사용자 안내서**를 먼저 읽는게 좋습니다.
-**자습서 - 사용자 안내서**만으로 완전한 애플리케이션을 구축한 다음, **고급 사용자 안내서**의 몇 가지 추가 아이디어를 사용하여 필요에 따라 다양한 방식으로 확장할 수 있도록 설계되었습니다.
+**자습서 - 사용자 안내서**만으로도 완전한 애플리케이션을 구축할 수 있으며, 필요에 따라 **고급 사용자 안내서**에서 제공하는 몇 가지 추가적인 기능을 사용하여 다양한 방식으로 확장할 수 있도록 설계되었습니다.
diff --git a/docs/ko/docs/tutorial/path-params-numeric-validations.md b/docs/ko/docs/tutorial/path-params-numeric-validations.md
new file mode 100644
index 00000000..abb9d03d
--- /dev/null
+++ b/docs/ko/docs/tutorial/path-params-numeric-validations.md
@@ -0,0 +1,122 @@
+# 경로 매개변수와 숫자 검증
+
+`Query`를 사용하여 쿼리 매개변수에 더 많은 검증과 메타데이터를 선언하는 방법과 동일하게 `Path`를 사용하여 경로 매개변수에 검증과 메타데이터를 같은 타입으로 선언할 수 있습니다.
+
+## 경로 임포트
+
+먼저 `fastapi`에서 `Path`를 임포트합니다:
+
+```Python hl_lines="3"
+{!../../../docs_src/path_params_numeric_validations/tutorial001.py!}
+```
+
+## 메타데이터 선언
+
+`Query`에 동일한 매개변수를 선언할 수 있습니다.
+
+예를 들어, `title` 메타데이터 값을 경로 매개변수 `item_id`에 선언하려면 다음과 같이 입력할 수 있습니다:
+
+```Python hl_lines="10"
+{!../../../docs_src/path_params_numeric_validations/tutorial001.py!}
+```
+
+!!! note "참고"
+ 경로 매개변수는 경로의 일부여야 하므로 언제나 필수적입니다.
+
+ 즉, `...`로 선언해서 필수임을 나타내는게 좋습니다.
+
+ 그럼에도 `None`으로 선언하거나 기본값을 지정할지라도 아무 영향을 끼치지 않으며 언제나 필수입니다.
+
+## 필요한 경우 매개변수 정렬하기
+
+`str` 형인 쿼리 매개변수 `q`를 필수로 선언하고 싶다고 해봅시다.
+
+해당 매개변수에 대해 아무런 선언을 할 필요가 없으므로 `Query`를 정말로 써야할 필요는 없습니다.
+
+하지만 `item_id` 경로 매개변수는 여전히 `Path`를 사용해야 합니다.
+
+파이썬은 "기본값"이 없는 값 앞에 "기본값"이 있는 값을 입력하면 불평합니다.
+
+그러나 매개변수들을 재정렬함으로써 기본값(쿼리 매개변수 `q`)이 없는 값을 처음 부분에 위치 할 수 있습니다.
+
+**FastAPI**에서는 중요하지 않습니다. 이름, 타입 그리고 선언구(`Query`, `Path` 등)로 매개변수를 감지하며 순서는 신경 쓰지 않습니다.
+
+따라서 함수를 다음과 같이 선언 할 수 있습니다:
+
+```Python hl_lines="8"
+{!../../../docs_src/path_params_numeric_validations/tutorial002.py!}
+```
+
+## 필요한 경우 매개변수 정렬하기, 트릭
+
+`Query`나 아무런 기본값으로도 `q` 경로 매개변수를 선언하고 싶지 않지만 `Path`를 사용하여 경로 매개변수를 `item_id` 다른 순서로 선언하고 싶다면, 파이썬은 이를 위한 작고 특별한 문법이 있습니다.
+
+`*`를 함수의 첫 번째 매개변수로 전달하세요.
+
+파이썬은 `*`으로 아무런 행동도 하지 않지만, 따르는 매개변수들은 kwargs로도 알려진 키워드 인자(키-값 쌍)여야 함을 인지합니다. 기본값을 가지고 있지 않더라도 그렇습니다.
+
+```Python hl_lines="8"
+{!../../../docs_src/path_params_numeric_validations/tutorial003.py!}
+```
+
+## 숫자 검증: 크거나 같음
+
+`Query`와 `Path`(나중에 볼 다른 것들도)를 사용하여 문자열 뿐만 아니라 숫자의 제약을 선언할 수 있습니다.
+
+여기서 `ge=1`인 경우, `item_id`는 `1`보다 "크거나(`g`reater) 같은(`e`qual)" 정수형 숫자여야 합니다.
+
+```Python hl_lines="8"
+{!../../../docs_src/path_params_numeric_validations/tutorial004.py!}
+```
+
+## 숫자 검증: 크거나 같음 및 작거나 같음
+
+동일하게 적용됩니다:
+
+* `gt`: 크거나(`g`reater `t`han)
+* `le`: 작거나 같은(`l`ess than or `e`qual)
+
+```Python hl_lines="9"
+{!../../../docs_src/path_params_numeric_validations/tutorial005.py!}
+```
+
+## 숫자 검증: 부동소수, 크거나 및 작거나
+
+숫자 검증은 `float` 값에도 동작합니다.
+
+여기에서 ge뿐만 아니라 gt를 선언 할 수있는 것이 중요해집니다. 예를 들어 필요한 경우, 값이 `1`보다 작더라도 반드시 `0`보다 커야합니다.
+
+즉, `0.5`는 유효한 값입니다. 그러나 `0.0` 또는 `0`은 그렇지 않습니다.
+
+lt 역시 마찬가지입니다.
+
+```Python hl_lines="11"
+{!../../../docs_src/path_params_numeric_validations/tutorial006.py!}
+```
+
+## 요약
+
+`Query`, `Path`(아직 보지 못한 다른 것들도)를 사용하면 [쿼리 매개변수와 문자열 검증](query-params-str-validations.md){.internal-link target=_blank}에서와 마찬가지로 메타데이터와 문자열 검증을 선언할 수 있습니다.
+
+그리고 숫자 검증 또한 선언할 수 있습니다:
+
+* `gt`: 크거나(`g`reater `t`han)
+* `ge`: 크거나 같은(`g`reater than or `e`qual)
+* `lt`: 작거나(`l`ess `t`han)
+* `le`: 작거나 같은(`l`ess than or `e`qual)
+
+!!! info "정보"
+ `Query`, `Path`, 그리고 나중에게 보게될 것들은 (여러분이 사용할 필요가 없는) 공통 `Param` 클래스의 서브 클래스입니다.
+
+ 그리고 이들 모두는 여태까지 본 추가 검증과 메타데이터의 동일한 모든 매개변수를 공유합니다.
+
+!!! note "기술 세부사항"
+ `fastapi`에서 `Query`, `Path` 등을 임포트 할 때, 이것들은 실제로 함수입니다.
+
+ 호출되면 동일한 이름의 클래스의 인스턴스를 반환합니다.
+
+ 즉, 함수인 `Query`를 임포트한 겁니다. 그리고 호출하면 `Query`라는 이름을 가진 클래스의 인스턴스를 반환합니다.
+
+ 편집기에서 타입에 대한 오류를 표시하지 않도록 하기 위해 (클래스를 직접 사용하는 대신) 이러한 함수들이 있습니다.
+
+ 이렇게 하면 오류를 무시하기 위한 사용자 설정을 추가하지 않고도 일반 편집기와 코딩 도구를 사용할 수 있습니다.
diff --git a/docs/ko/docs/tutorial/request-files.md b/docs/ko/docs/tutorial/request-files.md
new file mode 100644
index 00000000..769a676c
--- /dev/null
+++ b/docs/ko/docs/tutorial/request-files.md
@@ -0,0 +1,144 @@
+# 파일 요청
+
+`File`을 사용하여 클라이언트가 업로드할 파일들을 정의할 수 있습니다.
+
+!!! info "정보"
+ 업로드된 파일을 전달받기 위해 먼저 `python-multipart`를 설치해야합니다.
+
+ 예시) `pip install python-multipart`.
+
+ 업로드된 파일들은 "폼 데이터"의 형태로 전송되기 때문에 이 작업이 필요합니다.
+
+## `File` 임포트
+
+`fastapi` 에서 `File` 과 `UploadFile` 을 임포트 합니다:
+
+```Python hl_lines="1"
+{!../../../docs_src/request_files/tutorial001.py!}
+```
+
+## `File` 매개변수 정의
+
+`Body` 및 `Form` 과 동일한 방식으로 파일의 매개변수를 생성합니다:
+
+```Python hl_lines="7"
+{!../../../docs_src/request_files/tutorial001.py!}
+```
+
+!!! info "정보"
+ `File` 은 `Form` 으로부터 직접 상속된 클래스입니다.
+
+ 하지만 `fastapi`로부터 `Query`, `Path`, `File` 등을 임포트 할 때, 이것들은 특별한 클래스들을 반환하는 함수라는 것을 기억하기 바랍니다.
+
+!!! tip "팁"
+ File의 본문을 선언할 때, 매개변수가 쿼리 매개변수 또는 본문(JSON) 매개변수로 해석되는 것을 방지하기 위해 `File` 을 사용해야합니다.
+
+파일들은 "폼 데이터"의 형태로 업로드 됩니다.
+
+*경로 작동 함수*의 매개변수를 `bytes` 로 선언하는 경우 **FastAPI**는 파일을 읽고 `bytes` 형태의 내용을 전달합니다.
+
+이것은 전체 내용이 메모리에 저장된다는 것을 의미한다는 걸 염두하기 바랍니다. 이는 작은 크기의 파일들에 적합합니다.
+
+어떤 경우에는 `UploadFile` 을 사용하는 것이 더 유리합니다.
+
+## `File` 매개변수와 `UploadFile`
+
+`File` 매개변수를 `UploadFile` 타입으로 정의합니다:
+
+```Python hl_lines="12"
+{!../../../docs_src/request_files/tutorial001.py!}
+```
+
+`UploadFile` 을 사용하는 것은 `bytes` 과 비교해 다음과 같은 장점이 있습니다:
+
+* "스풀 파일"을 사용합니다.
+ * 최대 크기 제한까지만 메모리에 저장되며, 이를 초과하는 경우 디스크에 저장됩니다.
+* 따라서 이미지, 동영상, 큰 이진코드와 같은 대용량 파일들을 많은 메모리를 소모하지 않고 처리하기에 적합합니다.
+* 업로드 된 파일의 메타데이터를 얻을 수 있습니다.
+* file-like `async` 인터페이스를 갖고 있습니다.
+* file-like object를 필요로하는 다른 라이브러리에 직접적으로 전달할 수 있는 파이썬 `SpooledTemporaryFile` 객체를 반환합니다.
+
+### `UploadFile`
+
+`UploadFile` 은 다음과 같은 어트리뷰트가 있습니다:
+
+* `filename` : 문자열(`str`)로 된 업로드된 파일의 파일명입니다 (예: `myimage.jpg`).
+* `content_type` : 문자열(`str`)로 된 파일 형식(MIME type / media type)입니다 (예: `image/jpeg`).
+* `file` : `SpooledTemporaryFile` (파일류 객체)입니다. 이것은 "파일류" 객체를 필요로하는 다른 라이브러리에 직접적으로 전달할 수 있는 실질적인 파이썬 파일입니다.
+
+`UploadFile` 에는 다음의 `async` 메소드들이 있습니다. 이들은 내부적인 `SpooledTemporaryFile` 을 사용하여 해당하는 파일 메소드를 호출합니다.
+
+* `write(data)`: `data`(`str` 또는 `bytes`)를 파일에 작성합니다.
+* `read(size)`: 파일의 바이트 및 글자의 `size`(`int`)를 읽습니다.
+* `seek(offset)`: 파일 내 `offset`(`int`) 위치의 바이트로 이동합니다.
+ * 예) `await myfile.seek(0)` 를 사용하면 파일의 시작부분으로 이동합니다.
+ * `await myfile.read()` 를 사용한 후 내용을 다시 읽을 때 유용합니다.
+* `close()`: 파일을 닫습니다.
+
+상기 모든 메소드들이 `async` 메소드이기 때문에 “await”을 사용하여야 합니다.
+
+예를들어, `async` *경로 작동 함수*의 내부에서 다음과 같은 방식으로 내용을 가져올 수 있습니다:
+
+```Python
+contents = await myfile.read()
+```
+
+만약 일반적인 `def` *경로 작동 함수*의 내부라면, 다음과 같이 `UploadFile.file` 에 직접 접근할 수 있습니다:
+
+```Python
+contents = myfile.file.read()
+```
+
+!!! note "`async` 기술적 세부사항"
+ `async` 메소드들을 사용할 때 **FastAPI**는 스레드풀에서 파일 메소드들을 실행하고 그들을 기다립니다.
+
+!!! note "Starlette 기술적 세부사항"
+ **FastAPI**의 `UploadFile` 은 **Starlette**의 `UploadFile` 을 직접적으로 상속받지만, **Pydantic** 및 FastAPI의 다른 부분들과의 호환성을 위해 필요한 부분들이 추가되었습니다.
+
+## "폼 데이터"란
+
+HTML의 폼들(``)이 서버에 데이터를 전송하는 방식은 대개 데이터에 JSON과는 다른 "특별한" 인코딩을 사용합니다.
+
+**FastAPI**는 JSON 대신 올바른 위치에서 데이터를 읽을 수 있도록 합니다.
+
+!!! note "기술적 세부사항"
+ 폼의 데이터는 파일이 포함되지 않은 경우 일반적으로 "미디어 유형" `application/x-www-form-urlencoded` 을 사용해 인코딩 됩니다.
+
+ 하지만 파일이 포함된 경우, `multipart/form-data`로 인코딩됩니다. `File`을 사용하였다면, **FastAPI**는 본문의 적합한 부분에서 파일을 가져와야 한다는 것을 인지합니다.
+
+ 인코딩과 폼 필드에 대해 더 알고싶다면, POST에 관한MDN웹 문서 를 참고하기 바랍니다,.
+
+!!! warning "주의"
+ 다수의 `File` 과 `Form` 매개변수를 한 *경로 작동*에 선언하는 것이 가능하지만, 요청의 본문이 `application/json` 가 아닌 `multipart/form-data` 로 인코딩 되기 때문에 JSON으로 받아야하는 `Body` 필드를 함께 선언할 수는 없습니다.
+
+ 이는 **FastAPI**의 한계가 아니라, HTTP 프로토콜에 의한 것입니다.
+
+## 다중 파일 업로드
+
+여러 파일을 동시에 업로드 할 수 있습니다.
+
+그들은 "폼 데이터"를 사용하여 전송된 동일한 "폼 필드"에 연결됩니다.
+
+이 기능을 사용하기 위해 , `bytes` 의 `List` 또는 `UploadFile` 를 선언하기 바랍니다:
+
+```Python hl_lines="10 15"
+{!../../../docs_src/request_files/tutorial002.py!}
+```
+
+선언한대로, `bytes` 의 `list` 또는 `UploadFile` 들을 전송받을 것입니다.
+
+!!! note "참고"
+ 2019년 4월 14일부터 Swagger UI가 하나의 폼 필드로 다수의 파일을 업로드하는 것을 지원하지 않습니다. 더 많은 정보를 원하면, #4276과 #3641을 참고하세요.
+
+ 그럼에도, **FastAPI**는 표준 Open API를 사용해 이미 호환이 가능합니다.
+
+ 따라서 Swagger UI 또는 기타 그 외의 OpenAPI를 지원하는 툴이 다중 파일 업로드를 지원하는 경우, 이들은 **FastAPI**와 호환됩니다.
+
+!!! note "기술적 세부사항"
+ `from starlette.responses import HTMLResponse` 역시 사용할 수 있습니다.
+
+ **FastAPI**는 개발자의 편의를 위해 `fastapi.responses` 와 동일한 `starlette.responses` 도 제공합니다. 하지만 대부분의 응답들은 Starlette로부터 직접 제공됩니다.
+
+## 요약
+
+폼 데이터로써 입력 매개변수로 업로드할 파일을 선언할 경우 `File` 을 사용하기 바랍니다.
diff --git a/docs/ko/docs/tutorial/request-forms-and-files.md b/docs/ko/docs/tutorial/request-forms-and-files.md
new file mode 100644
index 00000000..6750c7b2
--- /dev/null
+++ b/docs/ko/docs/tutorial/request-forms-and-files.md
@@ -0,0 +1,35 @@
+# 폼 및 파일 요청
+
+`File` 과 `Form` 을 사용하여 파일과 폼을 함께 정의할 수 있습니다.
+
+!!! info "정보"
+ 파일과 폼 데이터를 함께, 또는 각각 업로드하기 위해 먼저 `python-multipart`를 설치해야합니다.
+
+ 예 ) `pip install python-multipart`.
+
+## `File` 및 `Form` 업로드
+
+```Python hl_lines="1"
+{!../../../docs_src/request_forms_and_files/tutorial001.py!}
+```
+
+## `File` 및 `Form` 매개변수 정의
+
+`Body` 및 `Query`와 동일한 방식으로 파일과 폼의 매개변수를 생성합니다:
+
+```Python hl_lines="8"
+{!../../../docs_src/request_forms_and_files/tutorial001.py!}
+```
+
+파일과 폼 필드는 폼 데이터 형식으로 업로드되어 파일과 폼 필드로 전달됩니다.
+
+어떤 파일들은 `bytes`로, 또 어떤 파일들은 `UploadFile`로 선언할 수 있습니다.
+
+!!! warning "주의"
+ 다수의 `File`과 `Form` 매개변수를 한 *경로 작동*에 선언하는 것이 가능하지만, 요청의 본문이 `application/json`가 아닌 `multipart/form-data`로 인코딩 되기 때문에 JSON으로 받아야하는 `Body` 필드를 함께 선언할 수는 없습니다.
+
+ 이는 **FastAPI**의 한계가 아니라, HTTP 프로토콜에 의한 것입니다.
+
+## 요약
+
+하나의 요청으로 데이터와 파일들을 받아야 할 경우 `File`과 `Form`을 함께 사용하기 바랍니다.
diff --git a/docs/ko/docs/tutorial/response-status-code.md b/docs/ko/docs/tutorial/response-status-code.md
new file mode 100644
index 00000000..d201867a
--- /dev/null
+++ b/docs/ko/docs/tutorial/response-status-code.md
@@ -0,0 +1,89 @@
+# 응답 상태 코드
+
+응답 모델과 같은 방법으로, 어떤 *경로 작동*이든 `status_code` 매개변수를 사용하여 응답에 대한 HTTP 상태 코드를 선언할 수 있습니다.
+
+* `@app.get()`
+* `@app.post()`
+* `@app.put()`
+* `@app.delete()`
+* 기타
+
+```Python hl_lines="6"
+{!../../../docs_src/response_status_code/tutorial001.py!}
+```
+
+!!! note "참고"
+ `status_code` 는 "데코레이터" 메소드(`get`, `post` 등)의 매개변수입니다. 모든 매개변수들과 본문처럼 *경로 작동 함수*가 아닙니다.
+
+`status_code` 매개변수는 HTTP 상태 코드를 숫자로 입력받습니다.
+
+!!! info "정보"
+ `status_code` 는 파이썬의 `http.HTTPStatus` 와 같은 `IntEnum` 을 입력받을 수도 있습니다.
+
+`status_code` 매개변수는:
+
+* 응답에서 해당 상태 코드를 반환합니다.
+* 상태 코드를 OpenAPI 스키마(및 사용자 인터페이스)에 문서화 합니다.
+
+
+
+!!! note "참고"
+ 어떤 응답 코드들은 해당 응답에 본문이 없다는 것을 의미하기도 합니다 (다음 항목 참고).
+
+ 이에 따라 FastAPI는 응답 본문이 없음을 명시하는 OpenAPI를 생성합니다.
+
+## HTTP 상태 코드에 대하여
+
+!!! note "참고"
+ 만약 HTTP 상태 코드에 대하여 이미 알고있다면, 다음 항목으로 넘어가십시오.
+
+HTTP는 세자리의 숫자 상태 코드를 응답의 일부로 전송합니다.
+
+이 상태 코드들은 각자를 식별할 수 있도록 지정된 이름이 있으나, 중요한 것은 숫자 코드입니다.
+
+요약하자면:
+
+* `**1xx**` 상태 코드는 "정보"용입니다. 이들은 직접적으로는 잘 사용되지는 않습니다. 이 상태 코드를 갖는 응답들은 본문을 가질 수 없습니다.
+* `**2xx**` 상태 코드는 "성공적인" 응답을 위해 사용됩니다. 가장 많이 사용되는 유형입니다.
+ * `200` 은 디폴트 상태 코드로, 모든 것이 "성공적임"을 의미합니다.
+ * 다른 예로는 `201` "생성됨"이 있습니다. 일반적으로 데이터베이스에 새로운 레코드를 생성한 후 사용합니다.
+ * 단, `204` "내용 없음"은 특별한 경우입니다. 이것은 클라이언트에게 반환할 내용이 없는 경우 사용합니다. 따라서 응답은 본문을 가질 수 없습니다.
+* `**3xx**` 상태 코드는 "리다이렉션"용입니다. 본문을 가질 수 없는 `304` "수정되지 않음"을 제외하고, 이 상태 코드를 갖는 응답에는 본문이 있을 수도, 없을 수도 있습니다.
+* `**4xx**` 상태 코드는 "클라이언트 오류" 응답을 위해 사용됩니다. 이것은 아마 가장 많이 사용하게 될 두번째 유형입니다.
+ * 일례로 `404` 는 "찾을 수 없음" 응답을 위해 사용합니다.
+ * 일반적인 클라이언트 오류의 경우 `400` 을 사용할 수 있습니다.
+* `**5xx**` 상태 코드는 서버 오류에 사용됩니다. 이것들을 직접 사용할 일은 거의 없습니다. 응용 프로그램 코드나 서버의 일부에서 문제가 발생하면 자동으로 이들 상태 코드 중 하나를 반환합니다.
+
+!!! tip "팁"
+ 각각의 상태 코드와 이들이 의미하는 내용에 대해 더 알고싶다면 MDN HTTP 상태 코드에 관한 문서 를 확인하십시오.
+
+## 이름을 기억하는 쉬운 방법
+
+상기 예시 참고:
+
+```Python hl_lines="6"
+{!../../../docs_src/response_status_code/tutorial001.py!}
+```
+
+`201` 은 "생성됨"를 의미하는 상태 코드입니다.
+
+하지만 모든 상태 코드들이 무엇을 의미하는지 외울 필요는 없습니다.
+
+`fastapi.status` 의 편의 변수를 사용할 수 있습니다.
+
+```Python hl_lines="1 6"
+{!../../../docs_src/response_status_code/tutorial002.py!}
+```
+
+이것은 단순히 작업을 편리하게 하기 위한 것으로, HTTP 상태 코드와 동일한 번호를 갖고있지만, 이를 사용하면 편집기의 자동완성 기능을 사용할 수 있습니다:
+
+
+
+!!! note "기술적 세부사항"
+ `from starlette import status` 역시 사용할 수 있습니다.
+
+ **FastAPI**는 개발자인 당신의 편의를 위해 `fastapi.status` 와 동일한 `starlette.status` 도 제공합니다. 하지만 이것은 Starlette로부터 직접 제공됩니다.
+
+## 기본값 변경
+
+추후 여기서 선언하는 기본 상태 코드가 아닌 다른 상태 코드를 반환하는 방법을 [숙련된 사용자 지침서](https://fastapi.tiangolo.com/ko/advanced/response-change-status-code/){.internal-link target=_blank}에서 확인할 수 있습니다.
diff --git a/docs/ko/mkdocs.yml b/docs/ko/mkdocs.yml
index 7f1991ed..1e7d60db 100644
--- a/docs/ko/mkdocs.yml
+++ b/docs/ko/mkdocs.yml
@@ -9,17 +9,18 @@ theme:
primary: teal
accent: amber
toggle:
- icon: material/lightbulb-outline
+ icon: material/lightbulb
name: Switch to light mode
- scheme: slate
primary: teal
accent: amber
toggle:
- icon: material/lightbulb
+ icon: material/lightbulb-outline
name: Switch to dark mode
features:
- search.suggest
- search.highlight
+ - content.tabs.link
icon:
repo: fontawesome/brands/github-alt
logo: https://fastapi.tiangolo.com/img/icon-white.svg
@@ -28,9 +29,6 @@ theme:
repo_name: tiangolo/fastapi
repo_url: https://github.com/tiangolo/fastapi
edit_uri: ''
-google_analytics:
-- UA-133183413-1
-- auto
plugins:
- search
- markdownextradata:
@@ -39,11 +37,16 @@ nav:
- FastAPI: index.md
- Languages:
- en: /
+ - az: /az/
+ - de: /de/
- es: /es/
+ - fa: /fa/
- fr: /fr/
+ - id: /id/
- it: /it/
- ja: /ja/
- ko: /ko/
+ - nl: /nl/
- pl: /pl/
- pt: /pt/
- ru: /ru/
@@ -57,12 +60,16 @@ nav:
- tutorial/path-params.md
- tutorial/query-params.md
- tutorial/header-params.md
+ - tutorial/path-params-numeric-validations.md
+ - tutorial/response-status-code.md
+ - tutorial/request-files.md
+ - tutorial/request-forms-and-files.md
markdown_extensions:
- toc:
permalink: true
- markdown.extensions.codehilite:
guess_lang: false
-- markdown_include.include:
+- mdx_include:
base_path: docs
- admonition
- codehilite
@@ -71,16 +78,20 @@ markdown_extensions:
custom_fences:
- name: mermaid
class: mermaid
- format: !!python/name:pymdownx.superfences.fence_div_format ''
-- pymdownx.tabbed
+ format: !!python/name:pymdownx.superfences.fence_code_format ''
+- pymdownx.tabbed:
+ alternate_style: true
extra:
+ analytics:
+ provider: google
+ property: UA-133183413-1
social:
- icon: fontawesome/brands/github-alt
link: https://github.com/tiangolo/fastapi
- icon: fontawesome/brands/discord
link: https://discord.gg/VQjSZaeJmf
- icon: fontawesome/brands/twitter
- link: https://twitter.com/tiangolo
+ link: https://twitter.com/fastapi
- icon: fontawesome/brands/linkedin
link: https://www.linkedin.com/in/tiangolo
- icon: fontawesome/brands/dev
@@ -92,16 +103,26 @@ extra:
alternate:
- link: /
name: en - English
+ - link: /az/
+ name: az
+ - link: /de/
+ name: de
- link: /es/
name: es - español
+ - link: /fa/
+ name: fa
- link: /fr/
name: fr - français
+ - link: /id/
+ name: id
- link: /it/
name: it - italiano
- link: /ja/
name: ja - 日本語
- link: /ko/
name: ko - 한국어
+ - link: /nl/
+ name: nl
- link: /pl/
name: pl
- link: /pt/
@@ -120,6 +141,5 @@ extra_css:
- https://fastapi.tiangolo.com/css/termynal.css
- https://fastapi.tiangolo.com/css/custom.css
extra_javascript:
-- https://unpkg.com/mermaid@8.4.6/dist/mermaid.min.js
- https://fastapi.tiangolo.com/js/termynal.js
- https://fastapi.tiangolo.com/js/custom.js
diff --git a/docs/nl/docs/index.md b/docs/nl/docs/index.md
new file mode 100644
index 00000000..0070de17
--- /dev/null
+++ b/docs/nl/docs/index.md
@@ -0,0 +1,468 @@
+
+{!../../../docs/missing-translation.md!}
+
+
+
++ FastAPI framework, high performance, easy to learn, fast to code, ready for production +
+ + +--- + +**Documentation**: https://fastapi.tiangolo.com + +**Source Code**: https://github.com/tiangolo/fastapi + +--- + +FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints. + +The key features are: + +* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance). + +* **Fast to code**: Increase the speed to develop features by about 200% to 300%. * +* **Fewer bugs**: Reduce about 40% of human (developer) induced errors. * +* **Intuitive**: Great editor support. Completion everywhere. Less time debugging. +* **Easy**: Designed to be easy to use and learn. Less time reading docs. +* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs. +* **Robust**: Get production-ready code. With automatic interactive documentation. +* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: OpenAPI (previously known as Swagger) and JSON Schema. + +* estimation based on tests on an internal development team, building production applications. + +## Sponsors + + + +{% if sponsors %} +{% for sponsor in sponsors.gold -%} +async def...uvicorn main:app --reload...ujson - for faster JSON "parsing".
+* email_validator - for email validation.
+
+Used by Starlette:
+
+* requests - Required if you want to use the `TestClient`.
+* jinja2 - Required if you want to use the default template configuration.
+* python-multipart - Required if you want to support form "parsing", with `request.form()`.
+* itsdangerous - Required for `SessionMiddleware` support.
+* pyyaml - Required for Starlette's `SchemaGenerator` support (you probably don't need it with FastAPI).
+* ujson - Required if you want to use `UJSONResponse`.
+
+Used by FastAPI / Starlette:
+
+* uvicorn - for the server that loads and serves your application.
+* orjson - Required if you want to use `ORJSONResponse`.
+
+You can install all of these with `pip install "fastapi[all]"`.
+
+## License
+
+This project is licensed under the terms of the MIT license.
diff --git a/docs/nl/mkdocs.yml b/docs/nl/mkdocs.yml
new file mode 100644
index 00000000..c853216f
--- /dev/null
+++ b/docs/nl/mkdocs.yml
@@ -0,0 +1,135 @@
+site_name: FastAPI
+site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production
+site_url: https://fastapi.tiangolo.com/nl/
+theme:
+ name: material
+ custom_dir: overrides
+ palette:
+ - scheme: default
+ primary: teal
+ accent: amber
+ toggle:
+ icon: material/lightbulb
+ name: Switch to light mode
+ - scheme: slate
+ primary: teal
+ accent: amber
+ toggle:
+ icon: material/lightbulb-outline
+ name: Switch to dark mode
+ features:
+ - search.suggest
+ - search.highlight
+ - content.tabs.link
+ icon:
+ repo: fontawesome/brands/github-alt
+ logo: https://fastapi.tiangolo.com/img/icon-white.svg
+ favicon: https://fastapi.tiangolo.com/img/favicon.png
+ language: nl
+repo_name: tiangolo/fastapi
+repo_url: https://github.com/tiangolo/fastapi
+edit_uri: ''
+plugins:
+- search
+- markdownextradata:
+ data: data
+nav:
+- FastAPI: index.md
+- Languages:
+ - en: /
+ - az: /az/
+ - de: /de/
+ - es: /es/
+ - fa: /fa/
+ - fr: /fr/
+ - id: /id/
+ - it: /it/
+ - ja: /ja/
+ - ko: /ko/
+ - nl: /nl/
+ - pl: /pl/
+ - pt: /pt/
+ - ru: /ru/
+ - sq: /sq/
+ - tr: /tr/
+ - uk: /uk/
+ - zh: /zh/
+markdown_extensions:
+- toc:
+ permalink: true
+- markdown.extensions.codehilite:
+ guess_lang: false
+- mdx_include:
+ base_path: docs
+- admonition
+- codehilite
+- extra
+- pymdownx.superfences:
+ custom_fences:
+ - name: mermaid
+ class: mermaid
+ format: !!python/name:pymdownx.superfences.fence_code_format ''
+- pymdownx.tabbed:
+ alternate_style: true
+extra:
+ analytics:
+ provider: google
+ property: UA-133183413-1
+ social:
+ - icon: fontawesome/brands/github-alt
+ link: https://github.com/tiangolo/fastapi
+ - icon: fontawesome/brands/discord
+ link: https://discord.gg/VQjSZaeJmf
+ - icon: fontawesome/brands/twitter
+ link: https://twitter.com/fastapi
+ - icon: fontawesome/brands/linkedin
+ link: https://www.linkedin.com/in/tiangolo
+ - icon: fontawesome/brands/dev
+ link: https://dev.to/tiangolo
+ - icon: fontawesome/brands/medium
+ link: https://medium.com/@tiangolo
+ - icon: fontawesome/solid/globe
+ link: https://tiangolo.com
+ alternate:
+ - link: /
+ name: en - English
+ - link: /az/
+ name: az
+ - link: /de/
+ name: de
+ - link: /es/
+ name: es - español
+ - link: /fa/
+ name: fa
+ - link: /fr/
+ name: fr - français
+ - link: /id/
+ name: id
+ - link: /it/
+ name: it - italiano
+ - link: /ja/
+ name: ja - 日本語
+ - link: /ko/
+ name: ko - 한국어
+ - link: /nl/
+ name: nl
+ - link: /pl/
+ name: pl
+ - link: /pt/
+ name: pt - português
+ - link: /ru/
+ name: ru - русский язык
+ - link: /sq/
+ name: sq - shqip
+ - link: /tr/
+ name: tr - Türkçe
+ - link: /uk/
+ name: uk - українська мова
+ - link: /zh/
+ name: zh - 汉语
+extra_css:
+- https://fastapi.tiangolo.com/css/termynal.css
+- https://fastapi.tiangolo.com/css/custom.css
+extra_javascript:
+- https://fastapi.tiangolo.com/js/termynal.js
+- https://fastapi.tiangolo.com/js/custom.js
diff --git a/docs/nl/overrides/.gitignore b/docs/nl/overrides/.gitignore
new file mode 100644
index 00000000..e69de29b
diff --git a/docs/pl/docs/index.md b/docs/pl/docs/index.md
index edc19fa4..4a300ae6 100644
--- a/docs/pl/docs/index.md
+++ b/docs/pl/docs/index.md
@@ -1,12 +1,8 @@
-
-{!../../../docs/missing-translation.md!}
-
-
- FastAPI framework, high performance, easy to learn, fast to code, ready for production + FastAPI to szybki, prosty w nauce i gotowy do użycia w produkcji framework
@@ -22,43 +18,45 @@
---
-**Documentation**: https://fastapi.tiangolo.com
+**Dokumentacja**: https://fastapi.tiangolo.com
-**Source Code**: https://github.com/tiangolo/fastapi
+**Kod żródłowy**: https://github.com/tiangolo/fastapi
---
-FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints.
+FastAPI to nowoczesny, wydajny framework webowy do budowania API z użyciem Pythona 3.6+ bazujący na standardowym typowaniu Pythona.
-The key features are:
+Kluczowe cechy:
-* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance).
+* **Wydajność**: FastAPI jest bardzo wydajny, na równi z **NodeJS** oraz **Go** (dzięki Starlette i Pydantic). [Jeden z najszybszych dostępnych frameworków Pythonowych](#wydajnosc).
+* **Szybkość kodowania**: Przyśpiesza szybkość pisania nowych funkcjonalności o około 200% do 300%. *
+* **Mniejsza ilość błędów**: Zmniejsza ilość ludzkich (dewelopera) błędy o około 40%. *
+* **Intuicyjność**: Wspaniałe wsparcie dla edytorów kodu. Dostępne wszędzie automatyczne uzupełnianie kodu. Krótszy czas debugowania.
+* **Łatwość**: Zaprojektowany by być prosty i łatwy do nauczenia. Mniej czasu spędzonego na czytanie dokumentacji.
+* **Kompaktowość**: Minimalizacja powtarzającego się kodu. Wiele funkcjonalności dla każdej deklaracji parametru. Mniej błędów.
+* **Solidność**: Kod gotowy dla środowiska produkcyjnego. Wraz z automatyczną interaktywną dokumentacją.
+* **Bazujący na standardach**: Oparty na (i w pełni kompatybilny z) otwartych standardach API: OpenAPI (wcześniej znane jako Swagger) oraz JSON Schema.
-* **Fast to code**: Increase the speed to develop features by about 200% to 300%. *
-* **Fewer bugs**: Reduce about 40% of human (developer) induced errors. *
-* **Intuitive**: Great editor support. Completion everywhere. Less time debugging.
-* **Easy**: Designed to be easy to use and learn. Less time reading docs.
-* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs.
-* **Robust**: Get production-ready code. With automatic interactive documentation.
-* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: OpenAPI (previously known as Swagger) and JSON Schema.
+* oszacowania bazowane na testach wykonanych przez wewnętrzny zespół deweloperów, budujących aplikacie używane na środowisku produkcyjnym.
-* estimation based on tests on an internal development team, building production applications.
-
-## Gold Sponsors
+## Sponsorzy
{% if sponsors %}
{% for sponsor in sponsors.gold -%}
-
+
+{% endfor -%}
+{%- for sponsor in sponsors.silver -%}
+
{% endfor %}
{% endif %}
-Other sponsors
+Inni sponsorzy
-## Opinions
+## Opinie
"_[...] I'm using **FastAPI** a ton these days. [...] I'm actually planning to use it for all of my team's **ML services at Microsoft**. Some of them are getting integrated into the core **Windows** product and some **Office** products._"
@@ -98,24 +96,24 @@ The key features are:
---
-## **Typer**, the FastAPI of CLIs
+## **Typer**, FastAPI aplikacji konsolowych
-If you are building a CLI app to be used in the terminal instead of a web API, check out **Typer**.
+Jeżeli tworzysz aplikacje CLI, która ma być używana w terminalu zamiast API, sprawdź **Typer**.
-**Typer** is FastAPI's little sibling. And it's intended to be the **FastAPI of CLIs**. ⌨️ 🚀
+**Typer** to młodsze rodzeństwo FastAPI. Jego celem jest pozostanie **FastAPI aplikacji konsolowych** . ⌨️ 🚀
-## Requirements
+## Wymagania
Python 3.6+
-FastAPI stands on the shoulders of giants:
+FastAPI oparty jest na:
-* Starlette for the web parts.
-* Pydantic for the data parts.
+* Starlette dla części webowej.
+* Pydantic dla części obsługujących dane.
-## Installation
+## Instalacja
async def...async def...uvicorn main:app --reload...uvicorn main:app --reload...ujson - for faster JSON "parsing".
-* email_validator - for email validation.
+* ujson - dla szybszego "parsowania" danych JSON.
+* email_validator - dla walidacji adresów email.
-Used by Starlette:
+Używane przez Starlette:
-* requests - Required if you want to use the `TestClient`.
-* aiofiles - Required if you want to use `FileResponse` or `StaticFiles`.
-* jinja2 - Required if you want to use the default template configuration.
-* python-multipart - Required if you want to support form "parsing", with `request.form()`.
-* itsdangerous - Required for `SessionMiddleware` support.
-* pyyaml - Required for Starlette's `SchemaGenerator` support (you probably don't need it with FastAPI).
-* graphene - Required for `GraphQLApp` support.
-* ujson - Required if you want to use `UJSONResponse`.
+* requests - Wymagane jeżeli chcesz korzystać z `TestClient`.
+* aiofiles - Wymagane jeżeli chcesz korzystać z `FileResponse` albo `StaticFiles`.
+* jinja2 - Wymagane jeżeli chcesz używać domyślnej konfiguracji szablonów.
+* python-multipart - Wymagane jeżelich chcesz wsparcie "parsowania" formularzy, używając `request.form()`.
+* itsdangerous - Wymagany dla wsparcia `SessionMiddleware`.
+* pyyaml - Wymagane dla wsparcia `SchemaGenerator` z Starlette (z FastAPI prawdopodobnie tego nie potrzebujesz).
+* graphene - Wymagane dla wsparcia `GraphQLApp`.
+* ujson - Wymagane jeżeli chcesz korzystać z `UJSONResponse`.
-Used by FastAPI / Starlette:
+Używane przez FastAPI / Starlette:
-* uvicorn - for the server that loads and serves your application.
-* orjson - Required if you want to use `ORJSONResponse`.
+* uvicorn - jako serwer, który ładuje i obsługuje Twoją aplikację.
+* orjson - Wymagane jeżeli chcesz używać `ORJSONResponse`.
-You can install all of these with `pip install fastapi[all]`.
+Możesz zainstalować wszystkie te aplikacje przy pomocy `pip install fastapi[all]`.
-## License
+## Licencja
-This project is licensed under the terms of the MIT license.
+Ten projekt jest na licencji MIT.
diff --git a/docs/pl/docs/tutorial/index.md b/docs/pl/docs/tutorial/index.md
new file mode 100644
index 00000000..1a97214a
--- /dev/null
+++ b/docs/pl/docs/tutorial/index.md
@@ -0,0 +1,81 @@
+# Samouczek - Wprowadzenie
+
+Ten samouczek pokaże Ci, krok po kroku, jak używać większości funkcji **FastAPI**.
+
+Każda część korzysta z poprzednich, ale jest jednocześnie osobnym tematem. Możesz przejść bezpośrednio do każdego rozdziału, jeśli szukasz rozwiązania konkretnego problemu.
+
+Samouczek jest tak zbudowany, żeby służył jako punkt odniesienia w przyszłości.
+
+Możesz wracać i sprawdzać dokładnie to czego potrzebujesz.
+
+## Wykonywanie kodu
+
+Wszystkie fragmenty kodu mogą być skopiowane bezpośrednio i użyte (są poprawnymi i przetestowanymi plikami).
+
+Żeby wykonać każdy przykład skopiuj kod to pliku `main.py` i uruchom `uvicorn` za pomocą:
+
+
+
+## Permitir acesso público
+
+Por padrão, a Deta lidará com a autenticação usando cookies para sua conta.
+
+Mas quando estiver pronto, você pode torná-lo público com:
+
+
+
+## Saiba mais
+
+Em algum momento, você provavelmente desejará armazenar alguns dados para seu aplicativo de uma forma que persista ao longo do tempo. Para isso você pode usar Deta Base, que também tem um generoso **nível gratuito**.
+
+Você também pode ler mais na documentação da Deta.
+
+## Conceitos de implantação
+
+Voltando aos conceitos que discutimos em [Deployments Concepts](./concepts.md){.internal-link target=_blank}, veja como cada um deles seria tratado com a Deta:
+
+* **HTTPS**: Realizado pela Deta, eles fornecerão um subdomínio e lidarão com HTTPS automaticamente.
+* **Executando na inicialização**: Realizado pela Deta, como parte de seu serviço.
+* **Reinicialização**: Realizado pela Deta, como parte de seu serviço.
+* **Replicação**: Realizado pela Deta, como parte de seu serviço.
+* **Memória**: Limite predefinido pela Deta, você pode contatá-los para aumentá-lo.
+* **Etapas anteriores a inicialização**: Não suportado diretamente, você pode fazê-lo funcionar com o sistema Cron ou scripts adicionais.
+
+!!! note "Nota"
+ O Deta foi projetado para facilitar (e gratuitamente) a implantação rápida de aplicativos simples.
+
+ Ele pode simplificar vários casos de uso, mas, ao mesmo tempo, não suporta outros, como o uso de bancos de dados externos (além do próprio sistema de banco de dados NoSQL da Deta), máquinas virtuais personalizadas, etc.
+
+ Você pode ler mais detalhes na documentação da Deta para ver se é a escolha certa para você.
diff --git a/docs/pt/docs/deployment/https.md b/docs/pt/docs/deployment/https.md
new file mode 100644
index 00000000..f85861e9
--- /dev/null
+++ b/docs/pt/docs/deployment/https.md
@@ -0,0 +1,48 @@
+# Sobre HTTPS
+
+É fácil assumir que HTTPS é algo que é apenas "habilitado" ou não.
+
+Mas é bem mais complexo do que isso.
+
+!!! tip "Dica"
+ Se você está com pressa ou não se importa, continue com as seções seguintes para instruções passo a passo para configurar tudo com diferentes técnicas.
+
+Para aprender o básico de HTTPS de uma perspectiva do usuário, verifique https://howhttps.works/pt-br/.
+
+Agora, a partir de uma perspectiva do desenvolvedor, aqui estão algumas coisas para ter em mente ao pensar em HTTPS:
+
+* Para HTTPS, o servidor precisa ter certificados gerados por um terceiro.
+ * Esses certificados são adquiridos de um terceiro, eles não são simplesmente "gerados".
+* Certificados têm um tempo de vida.
+ * Eles expiram.
+ * E então eles precisam ser renovados, adquirindo-os novamente de um terceiro.
+* A criptografia da conexão acontece no nível TCP.
+ * Essa é uma camada abaixo do HTTP.
+ * Portanto, o manuseio do certificado e da criptografia é feito antes do HTTP.
+* O TCP não sabe sobre "domínios". Apenas sobre endereços IP.
+ * As informações sobre o domínio solicitado vão nos dados HTTP.
+* Os certificados HTTPS “certificam” um determinado domínio, mas o protocolo e a encriptação acontecem ao nível do TCP, antes de sabermos de que domínio se trata.
+* Por padrão, isso significa que você só pode ter um certificado HTTPS por endereço IP.
+ * Não importa o tamanho do seu servidor ou quão pequeno cada aplicativo que você tem nele possa ser.
+ * No entanto, existe uma solução para isso.
+* Há uma extensão para o protocolo TLS (aquele que lida com a criptografia no nível TCP, antes do HTTP) chamado SNI.
+ * Esta extensão SNI permite que um único servidor (com um único endereço IP) tenha vários certificados HTTPS e atenda a vários domínios / aplicativos HTTPS.
+ * Para que isso funcione, um único componente (programa) em execução no servidor, ouvindo no endereço IP público, deve ter todos os certificados HTTPS no servidor.
+* Depois de obter uma conexão segura, o protocolo de comunicação ainda é HTTP.
+ * Os conteúdos são criptografados, embora sejam enviados com o protocolo HTTP.
+
+É uma prática comum ter um programa/servidor HTTP em execução no servidor (máquina, host, etc.) e gerenciar todas as partes HTTPS: enviando as solicitações HTTP descriptografadas para o aplicativo HTTP real em execução no mesmo servidor (a aplicação **FastAPI**, neste caso), pegue a resposta HTTP do aplicativo, criptografe-a usando o certificado apropriado e envie-a de volta ao cliente usando HTTPS. Este servidor é frequentemente chamado de TLS Termination Proxy.
+
+## Let's Encrypt
+
+Antes de Let's Encrypt, esses certificados HTTPS eram vendidos por terceiros confiáveis.
+
+O processo de aquisição de um desses certificados costumava ser complicado, exigia bastante papelada e os certificados eram bastante caros.
+
+Mas então Let's Encrypt foi criado.
+
+Ele é um projeto da Linux Foundation que fornece certificados HTTPS gratuitamente. De forma automatizada. Esses certificados usam toda a segurança criptográfica padrão e têm vida curta (cerca de 3 meses), então a segurança é realmente melhor por causa de sua vida útil reduzida.
+
+Os domínios são verificados com segurança e os certificados são gerados automaticamente. Isso também permite automatizar a renovação desses certificados.
+
+A ideia é automatizar a aquisição e renovação desses certificados, para que você tenha HTTPS seguro, de graça e para sempre.
diff --git a/docs/pt/docs/deployment/index.md b/docs/pt/docs/deployment/index.md
new file mode 100644
index 00000000..1ff0e44a
--- /dev/null
+++ b/docs/pt/docs/deployment/index.md
@@ -0,0 +1,7 @@
+# Implantação - Introdução
+
+A implantação de uma aplicação **FastAPI** é relativamente simples.
+
+Existem várias maneiras para fazer isso, dependendo do seu caso específico e das ferramentas que você utiliza.
+
+Você verá mais detalhes para se ter em mente e algumas das técnicas para a implantação nas próximas seções.
diff --git a/docs/pt/docs/deployment/versions.md b/docs/pt/docs/deployment/versions.md
new file mode 100644
index 00000000..77d9bab6
--- /dev/null
+++ b/docs/pt/docs/deployment/versions.md
@@ -0,0 +1,87 @@
+# Sobre as versões do FastAPI
+
+**FastAPI** já está sendo usado em produção em diversas aplicações e sistemas, a cobertura de testes é mantida em 100%, mas seu desenvolvimento está avançando rapidamente.
+
+Novos recursos são adicionados com frequência, bugs são corrigidos regularmente e o código está sempre melhorando.
+
+Esse é o motivo das versões atuais estarem em `0.x.x`, significando que em cada versão pode haver mudanças significativas, tudo isso seguindo as convenções de controle de versão semântica.
+
+Já é possível criar aplicativos de produção com **FastAPI** (e provavelmente você já faz isso há algum tempo), apenas precisando ter certeza de usar uma versão que funcione corretamente com o resto do seu código.
+
+## Fixe a sua versão de `fastapi`
+
+A primeira coisa que você deve fazer é "fixar" a versão do **FastAPI** que você está utilizando na mais atual, na qual você sabe que funciona corretamente para o seu aplicativo.
+
+Por exemplo, supondo que você está usando a versão `0.45.0` em sua aplicação.
+
+Caso você utilize o arquivo `requirements.txt`, você poderia especificar a versão com:
+
+```txt
+fastapi==0.45.0
+```
+
+Isso significa que você conseguiria utilizar a versão exata `0.45.0`.
+
+Ou, você poderia fixá-la com:
+
+```txt
+fastapi>=0.45.0,<0.46.0
+```
+
+isso significa que você iria usar as versões `0.45.0` ou acima, mas inferiores à `0.46.0`, por exemplo, a versão `0.45.2` ainda seria aceita.
+
+Se você usar qualquer outra ferramenta para gerenciar suas instalações, como Poetry, Pipenv ou outras, todas elas têm uma maneira que você pode usar para definir as versões específicas dos seus pacotes.
+
+## Versões disponíveis
+
+Você pode ver as versões disponíveis (por exemplo, para verificar qual é a versão atual) em [Release Notes](../release-notes.md){.internal-link target=\_blank}.
+
+## Sobre versões
+
+Seguindo as convenções de controle de versão semântica, qualquer versão abaixo de `1.0.0` pode adicionar mudanças significativas.
+
+FastAPI também segue a convenção de que qualquer alteração de versão "PATCH" é para correção de bugs e alterações não significativas.
+
+!!! tip "Dica"
+ O "PATCH" é o último número, por exemplo, em `0.2.3`, a versão PATCH é `3`.
+
+Logo, você deveria conseguir fixar a versão, como:
+
+```txt
+fastapi>=0.45.0,<0.46.0
+```
+
+Mudanças significativas e novos recursos são adicionados em versões "MINOR".
+
+!!! tip "Dica"
+ O "MINOR" é o número que está no meio, por exemplo, em `0.2.3`, a versão MINOR é `2`.
+
+## Atualizando as versões do FastAPI
+
+Você deve adicionar testes para a sua aplicação.
+
+Com **FastAPI** isso é muito fácil (graças a Starlette), verifique a documentação: [Testing](../tutorial/testing.md){.internal-link target=\_blank}
+
+Após a criação dos testes, você pode atualizar a sua versão do **FastAPI** para uma mais recente, execute os testes para se certificar de que todo o seu código está funcionando corretamente.
+
+Se tudo estiver funcionando, ou após você realizar as alterações necessárias e todos os testes estiverem passando, então você pode fixar sua versão de `FastAPI` para essa mais nova.
+
+## Sobre Starlette
+
+Não é recomendado fixar a versão de `starlette`.
+
+Versões diferentes de **FastAPI** utilizarão uma versão específica e mais recente de Starlette.
+
+Então, você pode deixar **FastAPI** escolher a versão compatível e correta de Starlette.
+
+## Sobre Pydantic
+
+Pydantic incluí os testes para **FastAPI** em seus próprios testes, então as novas versões de Pydantic (acima da `1.0.0`) sempre serão compatíveis com FastAPI.
+
+Você pode fixar qualquer versão de Pydantic que desejar, desde que seja acima da `1.0.0` e abaixo da `2.0.0`.
+
+Por exemplo:
+
+```txt
+pydantic>=1.2.0,<2.0.0
+```
diff --git a/docs/pt/docs/fastapi-people.md b/docs/pt/docs/fastapi-people.md
new file mode 100644
index 00000000..964cac68
--- /dev/null
+++ b/docs/pt/docs/fastapi-people.md
@@ -0,0 +1,178 @@
+# Pessoas do FastAPI
+
+FastAPI possue uma comunidade incrível que recebe pessoas de todos os níveis.
+
+## Criador - Mantenedor
+
+Ei! 👋
+
+Este sou eu:
+
+{% if people %}
+requests - Necessário se você quiser utilizar o `TestClient`.
-* aiofiles - Necessário se você quiser utilizar o `FileResponse` ou `StaticFiles`.
* jinja2 - Necessário se você quiser utilizar a configuração padrão de templates.
* python-multipart - Necessário se você quiser suporte com "parsing" de formulário, com `request.form()`.
* itsdangerous - Necessário para suporte a `SessionMiddleware`.
diff --git a/docs/pt/docs/tutorial/background-tasks.md b/docs/pt/docs/tutorial/background-tasks.md
new file mode 100644
index 00000000..625fa2b1
--- /dev/null
+++ b/docs/pt/docs/tutorial/background-tasks.md
@@ -0,0 +1,94 @@
+# Tarefas em segundo plano
+
+Você pode definir tarefas em segundo plano a serem executadas _ após _ retornar uma resposta.
+
+Isso é útil para operações que precisam acontecer após uma solicitação, mas que o cliente realmente não precisa esperar a operação ser concluída para receber a resposta.
+
+Isso inclui, por exemplo:
+
+- Envio de notificações por email após a realização de uma ação:
+ - Como conectar-se a um servidor de e-mail e enviar um e-mail tende a ser "lento" (vários segundos), você pode retornar a resposta imediatamente e enviar a notificação por e-mail em segundo plano.
+- Processando dados:
+ - Por exemplo, digamos que você receba um arquivo que deve passar por um processo lento, você pode retornar uma resposta de "Aceito" (HTTP 202) e processá-lo em segundo plano.
+
+## Usando `BackgroundTasks`
+
+Primeiro, importe `BackgroundTasks` e defina um parâmetro em sua _função de operação de caminho_ com uma declaração de tipo de `BackgroundTasks`:
+
+```Python hl_lines="1 13"
+{!../../../docs_src/background_tasks/tutorial001.py!}
+```
+
+O **FastAPI** criará o objeto do tipo `BackgroundTasks` para você e o passará como esse parâmetro.
+
+## Criar uma função de tarefa
+
+Crie uma função a ser executada como tarefa em segundo plano.
+
+É apenas uma função padrão que pode receber parâmetros.
+
+Pode ser uma função `async def` ou `def` normal, o **FastAPI** saberá como lidar com isso corretamente.
+
+Nesse caso, a função de tarefa gravará em um arquivo (simulando o envio de um e-mail).
+
+E como a operação de gravação não usa `async` e `await`, definimos a função com `def` normal:
+
+```Python hl_lines="6-9"
+{!../../../docs_src/background_tasks/tutorial001.py!}
+```
+
+## Adicionar a tarefa em segundo plano
+
+Dentro de sua _função de operação de caminho_, passe sua função de tarefa para o objeto _tarefas em segundo plano_ com o método `.add_task()`:
+
+```Python hl_lines="14"
+{!../../../docs_src/background_tasks/tutorial001.py!}
+```
+
+`.add_task()` recebe como argumentos:
+
+- Uma função de tarefa a ser executada em segundo plano (`write_notification`).
+- Qualquer sequência de argumentos que deve ser passada para a função de tarefa na ordem (`email`).
+- Quaisquer argumentos nomeados que devem ser passados para a função de tarefa (`mensagem = "alguma notificação"`).
+
+## Injeção de dependência
+
+Usar `BackgroundTasks` também funciona com o sistema de injeção de dependência, você pode declarar um parâmetro do tipo `BackgroundTasks` em vários níveis: em uma _função de operação de caminho_, em uma dependência (confiável), em uma subdependência, etc.
+
+O **FastAPI** sabe o que fazer em cada caso e como reutilizar o mesmo objeto, de forma que todas as tarefas em segundo plano sejam mescladas e executadas em segundo plano posteriormente:
+
+```Python hl_lines="13 15 22 25"
+{!../../../docs_src/background_tasks/tutorial002.py!}
+```
+
+Neste exemplo, as mensagens serão gravadas no arquivo `log.txt` _após_ o envio da resposta.
+
+Se houver uma consulta na solicitação, ela será gravada no log em uma tarefa em segundo plano.
+
+E então outra tarefa em segundo plano gerada na _função de operação de caminho_ escreverá uma mensagem usando o parâmetro de caminho `email`.
+
+## Detalhes técnicos
+
+A classe `BackgroundTasks` vem diretamente de `starlette.background`.
+
+Ela é importada/incluída diretamente no FastAPI para que você possa importá-la do `fastapi` e evitar a importação acidental da alternativa `BackgroundTask` (sem o `s` no final) de `starlette.background`.
+
+Usando apenas `BackgroundTasks` (e não `BackgroundTask`), é então possível usá-la como um parâmetro de _função de operação de caminho_ e deixar o **FastAPI** cuidar do resto para você, assim como ao usar o objeto `Request` diretamente.
+
+Ainda é possível usar `BackgroundTask` sozinho no FastAPI, mas você deve criar o objeto em seu código e retornar uma Starlette `Response` incluindo-o.
+
+Você pode ver mais detalhes na documentação oficiais da Starlette para tarefas em segundo plano .
+
+## Ressalva
+
+Se você precisa realizar cálculos pesados em segundo plano e não necessariamente precisa que seja executado pelo mesmo processo (por exemplo, você não precisa compartilhar memória, variáveis, etc), você pode se beneficiar do uso de outras ferramentas maiores, como Celery .
+
+Eles tendem a exigir configurações mais complexas, um gerenciador de fila de mensagens/tarefas, como RabbitMQ ou Redis, mas permitem que você execute tarefas em segundo plano em vários processos e, especialmente, em vários servidores.
+
+Para ver um exemplo, verifique os [Geradores de projeto](../project-generation.md){.internal-link target=\_blank}, todos incluem celery já configurado.
+
+Mas se você precisa acessar variáveis e objetos do mesmo aplicativo **FastAPI**, ou precisa realizar pequenas tarefas em segundo plano (como enviar uma notificação por e-mail), você pode simplesmente usar `BackgroundTasks`.
+
+## Recapitulando
+
+Importe e use `BackgroundTasks` com parâmetros em _funções de operação de caminho_ e dependências para adicionar tarefas em segundo plano.
diff --git a/docs/pt/docs/tutorial/body.md b/docs/pt/docs/tutorial/body.md
new file mode 100644
index 00000000..5891185f
--- /dev/null
+++ b/docs/pt/docs/tutorial/body.md
@@ -0,0 +1,165 @@
+# Corpo da Requisição
+
+Quando você precisa enviar dados de um cliente (como de um navegador web) para sua API, você o envia como um **corpo da requisição**.
+
+O corpo da **requisição** é a informação enviada pelo cliente para sua API. O corpo da **resposta** é a informação que sua API envia para o cliente.
+
+Sua API quase sempre irá enviar um corpo na **resposta**. Mas os clientes não necessariamente precisam enviar um corpo em toda **requisição**.
+
+Para declarar um corpo da **requisição**, você utiliza os modelos do Pydantic com todos os seus poderes e benefícios.
+
+!!! info "Informação"
+ Para enviar dados, você deve usar utilizar um dos métodos: `POST` (Mais comum), `PUT`, `DELETE` ou `PATCH`.
+
+ Enviar um corpo em uma requisição `GET` não tem um comportamento definido nas especificações, porém é suportado pelo FastAPI, apenas para casos de uso bem complexos/extremos.
+
+ Como é desencorajado, a documentação interativa com Swagger UI não irá mostrar a documentação para o corpo da requisição para um `GET`, e proxies que intermediarem podem não suportar o corpo da requisição.
+
+## Importe o `BaseModel` do Pydantic
+
+Primeiro, você precisa importar `BaseModel` do `pydantic`:
+
+```Python hl_lines="4"
+{!../../../docs_src/body/tutorial001.py!}
+```
+
+## Crie seu modelo de dados
+
+Então você declara seu modelo de dados como uma classe que herda `BaseModel`.
+
+Utilize os tipos Python padrão para todos os atributos:
+
+```Python hl_lines="7-11"
+{!../../../docs_src/body/tutorial001.py!}
+```
+
+Assim como quando declaramos parâmetros de consulta, quando um atributo do modelo possui um valor padrão, ele se torna opcional. Caso contrário, se torna obrigatório. Use `None` para torná-lo opcional.
+
+Por exemplo, o modelo acima declara um JSON "`object`" (ou `dict` no Python) como esse:
+
+```JSON
+{
+ "name": "Foo",
+ "description": "Uma descrição opcional",
+ "price": 45.2,
+ "tax": 3.5
+}
+```
+
+...como `description` e `tax` são opcionais (Com um valor padrão de `None`), esse JSON "`object`" também é válido:
+
+```JSON
+{
+ "name": "Foo",
+ "price": 45.2
+}
+```
+
+## Declare como um parâmetro
+
+Para adicionar o corpo na *função de operação de rota*, declare-o da mesma maneira que você declarou parâmetros de rota e consulta:
+
+```Python hl_lines="18"
+{!../../../docs_src/body/tutorial001.py!}
+```
+
+...E declare o tipo como o modelo que você criou, `Item`.
+
+## Resultados
+
+Apenas com esse declaração de tipos do Python, o **FastAPI** irá:
+
+* Ler o corpo da requisição como um JSON.
+* Converter os tipos correspondentes (se necessário).
+* Validar os dados.
+ * Se algum dados for inválido, irá retornar um erro bem claro, indicando exatamente onde e o que está incorreto.
+* Entregar a você a informação recebida no parâmetro `item`.
+ * Como você o declarou na função como do tipo `Item`, você também terá o suporte do editor (completação, etc) para todos os atributos e seus tipos.
+* Gerar um Esquema JSON com as definições do seu modelo, você também pode utilizá-lo em qualquer lugar que quiser, se fizer sentido para seu projeto.
+* Esses esquemas farão parte do esquema OpenAPI, e utilizados nas UIs de documentação automática.
+
+## Documentação automática
+
+Os esquemas JSON dos seus modelos farão parte do esquema OpenAPI gerado para sua aplicação, e aparecerão na documentação interativa da API:
+
+
+
+E também serão utilizados em cada *função de operação de rota* que utilizá-los:
+
+
+
+## Suporte do editor de texto:
+
+No seu editor de texto, dentro da função você receberá dicas de tipos e completação em todo lugar (isso não aconteceria se você recebesse um `dict` em vez de um modelo Pydantic):
+
+
+
+Você também poderá receber verificações de erros para operações de tipos incorretas:
+
+
+
+Isso não é por acaso, todo o framework foi construído em volta deste design.
+
+E foi imensamente testado na fase de design, antes de qualquer implementação, para garantir que funcionaria para todos os editores de texto.
+
+Houveram mudanças no próprio Pydantic para que isso fosse possível.
+
+As capturas de tela anteriores foram capturas no Visual Studio Code.
+
+Mas você terá o mesmo suporte do editor no PyCharm e na maioria dos editores Python:
+
+
+
+!!! tip "Dica"
+ Se você utiliza o PyCharm como editor, você pode utilizar o Plugin do Pydantic para o PyCharm .
+
+ Melhora o suporte do editor para seus modelos Pydantic com::
+
+ * completação automática
+ * verificação de tipos
+ * refatoração
+ * buscas
+ * inspeções
+
+## Use o modelo
+
+Dentro da função, você pode acessar todos os atributos do objeto do modelo diretamente:
+
+```Python hl_lines="21"
+{!../../../docs_src/body/tutorial002.py!}
+```
+
+## Corpo da requisição + parâmetros de rota
+
+Você pode declarar parâmetros de rota e corpo da requisição ao mesmo tempo.
+
+O **FastAPI** irá reconhecer que os parâmetros da função que combinam com parâmetros de rota devem ser **retirados da rota**, e parâmetros da função que são declarados como modelos Pydantic sejam **retirados do corpo da requisição**.
+
+```Python hl_lines="17-18"
+{!../../../docs_src/body/tutorial003.py!}
+```
+
+## Corpo da requisição + parâmetros de rota + parâmetros de consulta
+
+Você também pode declarar parâmetros de **corpo**, **rota** e **consulta**, ao mesmo tempo.
+
+O **FastAPI** irá reconhecer cada um deles e retirar a informação do local correto.
+
+```Python hl_lines="18"
+{!../../../docs_src/body/tutorial004.py!}
+```
+
+Os parâmetros da função serão reconhecidos conforme abaixo:
+
+* Se o parâmetro também é declarado na **rota**, será utilizado como um parâmetro de rota.
+* Se o parâmetro é de um **tipo único** (como `int`, `float`, `str`, `bool`, etc) será interpretado como um parâmetro de **consulta**.
+* Se o parâmetro é declarado como um **modelo Pydantic**, será interpretado como o **corpo** da requisição.
+
+!!! note "Observação"
+ O FastAPI saberá que o valor de `q` não é obrigatório por causa do valor padrão `= None`.
+
+ O `Optional` em `Optional[str]` não é utilizado pelo FastAPI, mas permite ao seu editor de texto lhe dar um suporte melhor e detectar erros.
+
+## Sem o Pydantic
+
+Se você não quer utilizar os modelos Pydantic, você também pode utilizar o parâmetro **Body**. Veja a documentação para [Body - Parâmetros múltiplos: Valores singulares no body](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}.
diff --git a/docs/pt/docs/tutorial/cookie-params.md b/docs/pt/docs/tutorial/cookie-params.md
new file mode 100644
index 00000000..1a60e357
--- /dev/null
+++ b/docs/pt/docs/tutorial/cookie-params.md
@@ -0,0 +1,33 @@
+# Parâmetros de Cookie
+
+Você pode definir parâmetros de Cookie da mesma maneira que define paramêtros com `Query` e `Path`.
+
+## Importe `Cookie`
+
+Primeiro importe `Cookie`:
+
+```Python hl_lines="3"
+{!../../../docs_src/cookie_params/tutorial001.py!}
+```
+
+## Declare parâmetros de `Cookie`
+
+Então declare os paramêtros de cookie usando a mesma estrutura que em `Path` e `Query`.
+
+O primeiro valor é o valor padrão, você pode passar todas as validações adicionais ou parâmetros de anotação:
+
+```Python hl_lines="9"
+{!../../../docs_src/cookie_params/tutorial001.py!}
+```
+
+!!! note "Detalhes Técnicos"
+ `Cookie` é uma classe "irmã" de `Path` e `Query`. Ela também herda da mesma classe em comum `Param`.
+
+ Mas lembre-se que quando você importa `Query`, `Path`, `Cookie` e outras de `fastapi`, elas são na verdade funções que retornam classes especiais.
+
+!!! info "Informação"
+ Para declarar cookies, você precisa usar `Cookie`, caso contrário, os parâmetros seriam interpretados como parâmetros de consulta.
+
+## Recapitulando
+
+Declare cookies com `Cookie`, usando o mesmo padrão comum que utiliza-se em `Query` e `Path`.
diff --git a/docs/pt/docs/tutorial/extra-data-types.md b/docs/pt/docs/tutorial/extra-data-types.md
new file mode 100644
index 00000000..e4b9913d
--- /dev/null
+++ b/docs/pt/docs/tutorial/extra-data-types.md
@@ -0,0 +1,66 @@
+# Tipos de dados extras
+
+Até agora, você tem usado tipos de dados comuns, tais como:
+
+* `int`
+* `float`
+* `str`
+* `bool`
+
+Mas você também pode usar tipos de dados mais complexos.
+
+E você ainda terá os mesmos recursos que viu até agora:
+
+* Ótimo suporte do editor.
+* Conversão de dados das requisições recebidas.
+* Conversão de dados para os dados da resposta.
+* Validação de dados.
+* Anotação e documentação automáticas.
+
+## Outros tipos de dados
+
+Aqui estão alguns dos tipos de dados adicionais que você pode usar:
+
+* `UUID`:
+ * Um "Identificador Universalmente Único" padrão, comumente usado como ID em muitos bancos de dados e sistemas.
+ * Em requisições e respostas será representado como uma `str`.
+* `datetime.datetime`:
+ * O `datetime.datetime` do Python.
+ * Em requisições e respostas será representado como uma `str` no formato ISO 8601, exemplo: `2008-09-15T15:53:00+05:00`.
+* `datetime.date`:
+ * O `datetime.date` do Python.
+ * Em requisições e respostas será representado como uma `str` no formato ISO 8601, exemplo: `2008-09-15`.
+* `datetime.time`:
+ * O `datetime.time` do Python.
+ * Em requisições e respostas será representado como uma `str` no formato ISO 8601, exemplo: `14:23:55.003`.
+* `datetime.timedelta`:
+ * O `datetime.timedelta` do Python.
+ * Em requisições e respostas será representado como um `float` de segundos totais.
+ * O Pydantic também permite representá-lo como uma "codificação ISO 8601 diferença de tempo", cheque a documentação para mais informações.
+* `frozenset`:
+ * Em requisições e respostas, será tratado da mesma forma que um `set`:
+ * Nas requisições, uma lista será lida, eliminando duplicadas e convertendo-a em um `set`.
+ * Nas respostas, o `set` será convertido para uma `list`.
+ * O esquema gerado vai especificar que os valores do `set` são unicos (usando o `uniqueItems` do JSON Schema).
+* `bytes`:
+ * O `bytes` padrão do Python.
+ * Em requisições e respostas será representado como uma `str`.
+ * O esquema gerado vai especificar que é uma `str` com o "formato" `binary`.
+* `Decimal`:
+ * O `Decimal` padrão do Python.
+ * Em requisições e respostas será representado como um `float`.
+* Você pode checar todos os tipos de dados válidos do Pydantic aqui: Tipos de dados do Pydantic.
+
+## Exemplo
+
+Aqui está um exemplo de *operação de rota* com parâmetros utilizando-se de alguns dos tipos acima.
+
+```Python hl_lines="1 3 12-16"
+{!../../../docs_src/extra_data_types/tutorial001.py!}
+```
+
+Note que os parâmetros dentro da função tem seu tipo de dados natural, e você pode, por exemplo, realizar manipulações normais de data, como:
+
+```Python hl_lines="18-19"
+{!../../../docs_src/extra_data_types/tutorial001.py!}
+```
diff --git a/docs/pt/docs/tutorial/path-params.md b/docs/pt/docs/tutorial/path-params.md
new file mode 100644
index 00000000..5de3756e
--- /dev/null
+++ b/docs/pt/docs/tutorial/path-params.md
@@ -0,0 +1,246 @@
+# Parâmetros da rota da URL
+
+Você pode declarar os "parâmetros" ou "variáveis" com a mesma sintaxe utilizada pelo formato de strings do Python:
+
+```Python hl_lines="6-7"
+{!../../../docs_src/path_params/tutorial001.py!}
+```
+
+O valor do parâmetro que foi passado à `item_id` será passado para a sua função como o argumento `item_id`.
+
+Então, se você rodar este exemplo e for até http://127.0.0.1:8000/items/foo, você verá a seguinte resposta:
+
+```JSON
+{"item_id":"foo"}
+```
+
+## Parâmetros da rota com tipos
+
+Você pode declarar o tipo de um parâmetro na função usando as anotações padrões do Python:
+
+```Python hl_lines="7"
+{!../../../docs_src/path_params/tutorial002.py!}
+```
+
+Nesse caso, `item_id` está sendo declarado como um `int`.
+
+!!! Check Verifique
+ Isso vai dar à você suporte do seu editor dentro das funções, com verificações de erros, autocompletar, etc.
+
+## Conversão de dados
+
+Se você rodar esse exemplo e abrir o seu navegador em http://127.0.0.1:8000/items/3, você verá a seguinte resposta:
+
+```JSON
+{"item_id":3}
+```
+
+!!! Verifique
+ Observe que o valor recebido pela função (e também retornado por ela) é `3`, como um Python `int`, não como uma string `"3"`.
+
+ Então, com essa declaração de tipo, o **FastAPI** dá pra você um "parsing" automático no request .
+
+## Validação de dados
+
+Mas se você abrir o seu navegador em http://127.0.0.1:8000/items/foo, você verá um belo erro HTTP:
+
+```JSON
+{
+ "detail": [
+ {
+ "loc": [
+ "path",
+ "item_id"
+ ],
+ "msg": "value is not a valid integer",
+ "type": "type_error.integer"
+ }
+ ]
+}
+```
+
+devido ao parâmetro da rota `item_id` ter um valor `"foo"`, que não é um `int`.
+
+O mesmo erro apareceria se você tivesse fornecido um `float` ao invés de um `int`, como em: http://127.0.0.1:8000/items/4.2
+
+!!! Verifique
+ Então, com a mesma declaração de tipo do Python, o **FastAPI** dá pra você validação de dados.
+
+ Observe que o erro também mostra claramente o ponto exato onde a validação não passou.
+
+ Isso é incrivelmente útil enquanto se desenvolve e debuga o código que interage com a sua API.
+
+## Documentação
+
+Quando você abrir o seu navegador em http://127.0.0.1:8000/docs, você verá de forma automática e interativa a documentação da API como:
+
+
+
+!!! check
+ Novamente, apenas com a mesma declaração de tipo do Python, o **FastAPI** te dá de forma automática e interativa a documentação (integrada com o Swagger UI).
+
+ Veja que o parâmetro de rota está declarado como sendo um inteiro (int).
+
+## Beneficios baseados em padrões, documentação alternativa
+
+Devido ao schema gerado ser o padrão do OpenAPI, existem muitas ferramentas compatíveis.
+
+Por esse motivo, o próprio **FastAPI** fornece uma API alternativa para documentação (utilizando ReDoc), que você pode acessar em http://127.0.0.1:8000/redoc:
+
+
+
+Da mesma forma, existem muitas ferramentas compatíveis. Incluindo ferramentas de geração de código para muitas linguagens.
+
+## Pydantic
+
+Toda a validação de dados é feita por baixo dos panos pelo Pydantic, então você tem todos os benefícios disso. E assim você sabe que está em boas mãos.
+
+Você pode usar as mesmas declarações de tipo com `str`, `float`, `bool` e muitos outros tipos complexos de dados.
+
+Vamos explorar muitos destes tipos nos próximos capítulos do tutorial.
+
+## A ordem importa
+
+Quando você cria operações de rota, você pode se deparar com situações onde você pode ter uma rota fixa.
+
+Algo como `/users/me` por exemplo, digamos que essa rota seja utilizada para pegar dados sobre o usuário atual.
+
+E então você pode ter também uma rota `/users/{user_id}` para pegar dados sobre um usuário específico associado a um ID de usuário.
+
+Porque as operações de rota são avaliadas em ordem, você precisa ter certeza que a rota para `/users/me` está sendo declarado antes da rota `/users/{user_id}`:
+
+```Python hl_lines="6 11"
+{!../../../docs_src/path_params/tutorial003.py!}
+```
+
+Caso contrário, a rota para `/users/{user_id}` coincidiria também para `/users/me`, "pensando" que estaria recebendo o parâmetro `user_id` com o valor de `"me"`.
+
+## Valores predefinidos
+
+Se você tem uma operação de rota que recebe um parâmetro da rota, mas que você queira que esses valores possíveis do parâmetro da rota sejam predefinidos, você pode usar `Enum` padrão do Python.
+
+### Criando uma classe `Enum`
+
+Importe `Enum` e crie uma sub-classe que herde de `str` e de `Enum`.
+
+Por herdar de `str` a documentação da API vai ser capaz de saber que os valores devem ser do tipo `string` e assim ser capaz de mostrar eles corretamente.
+
+Assim, crie atributos de classe com valores fixos, que serão os valores válidos disponíveis.
+
+```Python hl_lines="1 6-9"
+{!../../../docs_src/path_params/tutorial005.py!}
+```
+
+!!! informação
+ Enumerations (ou enums) estão disponíveis no Python desde a versão 3.4.
+
+!!! dica
+ Se você está se perguntando, "AlexNet", "ResNet", e "LeNet" são apenas nomes de modelos de Machine Learning (aprendizado de máquina).
+
+### Declare um *parâmetro de rota*
+
+Logo, crie um *parâmetro de rota* com anotações de tipo usando a classe enum que você criou (`ModelName`):
+
+```Python hl_lines="16"
+{!../../../docs_src/path_params/tutorial005.py!}
+```
+
+### Revise a documentação
+
+Visto que os valores disponíveis para o parâmetro da rota estão predefinidos, a documentação interativa pode mostrar esses valores de uma forma bem legal:
+
+
+
+### Trabalhando com os *enumeration* do Python
+
+O valor do *parâmetro da rota* será um *membro de enumeration*.
+
+#### Compare *membros de enumeration*
+
+Você pode comparar eles com o *membro de enumeration* no enum `ModelName` que você criou:
+
+```Python hl_lines="17"
+{!../../../docs_src/path_params/tutorial005.py!}
+```
+
+#### Obtenha o *valor de enumerate*
+
+Você pode ter o valor exato de enumerate (um `str` nesse caso) usando `model_name.value`, ou em geral, `your_enum_member.value`:
+
+```Python hl_lines="20"
+{!../../../docs_src/path_params/tutorial005.py!}
+```
+
+!!! conselho
+ Você também poderia acessar o valor `"lenet"` com `ModelName.lenet.value`
+
+#### Retorne *membros de enumeration*
+
+Você pode retornar *membros de enum* da sua *rota de operação*, em um corpo JSON aninhado (por exemplo um `dict`).
+
+Eles serão convertidos para o seus valores correspondentes (strings nesse caso) antes de serem retornados ao cliente:
+
+```Python hl_lines="18 21 23"
+{!../../../docs_src/path_params/tutorial005.py!}
+```
+
+No seu cliente você vai obter uma resposta JSON como:
+
+```JSON
+{
+ "model_name": "alexnet",
+ "message": "Deep Learning FTW!"
+}
+```
+
+## Parâmetros de rota que contém caminhos
+
+Digamos que você tenha uma *operação de rota* com uma rota `/files/{file_path}`.
+
+Mas você precisa que o próprio `file_path` contenha uma *rota*, como `home/johndoe/myfile.txt`.
+
+Então, a URL para este arquivo deveria ser algo como: `/files/home/johndoe/myfile.txt`.
+
+### Suporte do OpenAPI
+
+O OpenAPI não suporta uma maneira de declarar um *parâmetro de rota* que contenha uma *rota* dentro, dado que isso poderia levar a cenários que são difíceis de testar e definir.
+
+No entanto, você pode fazer isso no **FastAPI**, usando uma das ferramentas internas do Starlette.
+
+A documentação continuaria funcionando, ainda que não adicionaria nenhuma informação dizendo que o parâmetro deveria conter uma rota.
+
+### Conversor de rota
+
+Usando uma opção direta do Starlette você pode declarar um *parâmetro de rota* contendo uma *rota* usando uma URL como:
+
+```
+/files/{file_path:path}
+```
+
+Nesse caso, o nome do parâmetro é `file_path`, e a última parte, `:path`, diz que o parâmetro deveria coincidir com qualquer *rota*.
+
+Então, você poderia usar ele com:
+
+```Python hl_lines="6"
+{!../../../docs_src/path_params/tutorial004.py!}
+```
+
+!!! dica
+ Você poderia precisar que o parâmetro contivesse `/home/johndoe/myfile.txt`, com uma barra no inicio (`/`).
+
+ Neste caso, a URL deveria ser: `/files//home/johndoe/myfile.txt`, com barra dupla (`//`) entre `files` e `home`.
+
+
+## Recapitulando
+
+Com o **FastAPI**, usando as declarações de tipo do Python, você obtém:
+
+* Suporte no editor: verificação de erros, e opção de autocompletar, etc.
+* Parsing de dados
+* "Parsing" de dados
+* Validação de dados
+* Anotação da API e documentação automática
+
+Você apenas tem que declará-los uma vez.
+
+Essa é provavelmente a vantagem mais visível do **FastAPI** se comparado com frameworks alternativos (além do desempenho puro).
diff --git a/docs/pt/docs/tutorial/query-params-str-validations.md b/docs/pt/docs/tutorial/query-params-str-validations.md
new file mode 100644
index 00000000..baac5f49
--- /dev/null
+++ b/docs/pt/docs/tutorial/query-params-str-validations.md
@@ -0,0 +1,303 @@
+# Parâmetros de consulta e validações de texto
+
+O **FastAPI** permite que você declare informações adicionais e validações aos seus parâmetros.
+
+Vamos utilizar essa aplicação como exemplo:
+
+```Python hl_lines="9"
+{!../../../docs_src/query_params_str_validations/tutorial001.py!}
+```
+
+O parâmetro de consulta `q` é do tipo `Optional[str]`, o que significa que é do tipo `str` mas que também pode ser `None`, e de fato, o valor padrão é `None`, então o FastAPI saberá que não é obrigatório.
+
+!!! note "Observação"
+ O FastAPI saberá que o valor de `q` não é obrigatório por causa do valor padrão `= None`.
+
+ O `Optional` em `Optional[str]` não é usado pelo FastAPI, mas permitirá que seu editor lhe dê um melhor suporte e detecte erros.
+
+## Validação adicional
+
+Nós iremos forçar que mesmo o parâmetro `q` seja opcional, sempre que informado, **seu tamanho não exceda 50 caracteres**.
+
+### Importe `Query`
+
+Para isso, primeiro importe `Query` de `fastapi`:
+
+```Python hl_lines="3"
+{!../../../docs_src/query_params_str_validations/tutorial002.py!}
+```
+
+## Use `Query` como o valor padrão
+
+Agora utilize-o como valor padrão do seu parâmetro, definindo o parâmetro `max_length` para 50:
+
+```Python hl_lines="9"
+{!../../../docs_src/query_params_str_validations/tutorial002.py!}
+```
+
+Note que substituímos o valor padrão de `None` para `Query(None)`, o primeiro parâmetro de `Query` serve para o mesmo propósito: definir o valor padrão do parâmetro.
+
+Então:
+
+```Python
+q: Optional[str] = Query(None)
+```
+
+...Torna o parâmetro opcional, da mesma maneira que:
+
+```Python
+q: Optional[str] = None
+```
+
+Mas o declara explicitamente como um parâmetro de consulta.
+
+!!! info "Informação"
+ Tenha em mente que o FastAPI se preocupa com a parte:
+
+ ```Python
+ = None
+ ```
+
+ Ou com:
+
+ ```Python
+ = Query(None)
+ ```
+
+ E irá utilizar o `None` para detectar que o parâmetro de consulta não é obrigatório.
+
+ O `Optional` é apenas para permitir que seu editor de texto lhe dê um melhor suporte.
+
+Então, podemos passar mais parâmetros para `Query`. Neste caso, o parâmetro `max_length` que se aplica a textos:
+
+```Python
+q: str = Query(None, max_length=50)
+```
+
+Isso irá validar os dados, mostrar um erro claro quando os dados forem inválidos, e documentar o parâmetro na *operação de rota* do esquema OpenAPI..
+
+## Adicionando mais validações
+
+Você também pode incluir um parâmetro `min_length`:
+
+```Python hl_lines="9"
+{!../../../docs_src/query_params_str_validations/tutorial003.py!}
+```
+
+## Adicionando expressões regulares
+
+Você pode definir uma expressão regular que combine com um padrão esperado pelo parâmetro:
+
+```Python hl_lines="10"
+{!../../../docs_src/query_params_str_validations/tutorial004.py!}
+```
+
+Essa expressão regular específica verifica se o valor recebido no parâmetro:
+
+* `^`: Inicia com os seguintes caracteres, ou seja, não contém caracteres anteriores.
+* `fixedquery`: contém o valor exato `fixedquery`.
+* `$`: termina aqui, não contém nenhum caractere após `fixedquery`.
+
+Se você se sente perdido com todo esse assunto de **"expressão regular"**, não se preocupe. Esse é um assunto complicado para a maioria das pessoas. Você ainda pode fazer muitas coisas sem utilizar expressões regulares.
+
+Mas assim que você precisar e já tiver aprendido sobre, saiba que você poderá usá-las diretamente no **FastAPI**.
+
+## Valores padrão
+
+Da mesma maneira que você utiliza `None` como o primeiro argumento para ser utilizado como um valor padrão, você pode usar outros valores.
+
+Vamos dizer que você queira que o parâmetro de consulta `q` tenha um `min_length` de `3`, e um valor padrão de `"fixedquery"`, então declararíamos assim:
+
+```Python hl_lines="7"
+{!../../../docs_src/query_params_str_validations/tutorial005.py!}
+```
+
+!!! note "Observação"
+ O parâmetro torna-se opcional quando possui um valor padrão.
+
+## Torne-o obrigatório
+
+Quando você não necessita de validações ou de metadados adicionais, podemos fazer com que o parâmetro de consulta `q` seja obrigatório por não declarar um valor padrão, dessa forma:
+
+```Python
+q: str
+```
+
+em vez desta:
+
+```Python
+q: Optional[str] = None
+```
+
+Mas agora nós o estamos declarando como `Query`, conforme abaixo:
+
+```Python
+q: Optional[str] = Query(None, min_length=3)
+```
+
+Então, quando você precisa declarar um parâmetro obrigatório utilizando o `Query`, você pode utilizar `...` como o primeiro argumento:
+
+```Python hl_lines="7"
+{!../../../docs_src/query_params_str_validations/tutorial006.py!}
+```
+
+!!! info "Informação"
+ Se você nunca viu os `...` antes: é um valor único especial, faz parte do Python e é chamado "Ellipsis".
+
+Dessa forma o **FastAPI** saberá que o parâmetro é obrigatório.
+
+## Lista de parâmetros de consulta / múltiplos valores
+
+Quando você declara explicitamente um parâmetro com `Query` você pode declará-lo para receber uma lista de valores, ou podemos dizer, que irá receber mais de um valor.
+
+Por exemplo, para declarar que o parâmetro `q` pode aparecer diversas vezes na URL, você escreveria:
+
+```Python hl_lines="9"
+{!../../../docs_src/query_params_str_validations/tutorial011.py!}
+```
+
+Então, com uma URL assim:
+
+```
+http://localhost:8000/items/?q=foo&q=bar
+```
+
+você receberá os múltiplos *parâmetros de consulta* `q` com os valores (`foo` e `bar`) em uma lista (`list`) Python dentro da *função de operação de rota*, no *parâmetro da função* `q`.
+
+Assim, a resposta para essa URL seria:
+
+```JSON
+{
+ "q": [
+ "foo",
+ "bar"
+ ]
+}
+```
+
+!!! tip "Dica"
+ Para declarar um parâmetro de consulta com o tipo `list`, como no exemplo acima, você precisa usar explicitamente o `Query`, caso contrário será interpretado como um corpo da requisição.
+
+A documentação interativa da API irá atualizar de acordo, permitindo múltiplos valores:
+
+
+
+### Lista de parâmetros de consulta / múltiplos valores por padrão
+
+E você também pode definir uma lista (`list`) de valores padrão caso nenhum seja informado:
+
+```Python hl_lines="9"
+{!../../../docs_src/query_params_str_validations/tutorial012.py!}
+```
+
+Se você for até:
+
+```
+http://localhost:8000/items/
+```
+
+O valor padrão de `q` será: `["foo", "bar"]` e sua resposta será:
+
+```JSON
+{
+ "q": [
+ "foo",
+ "bar"
+ ]
+}
+```
+
+#### Usando `list`
+
+Você também pode utilizar o tipo `list` diretamente em vez de `List[str]`:
+
+```Python hl_lines="7"
+{!../../../docs_src/query_params_str_validations/tutorial013.py!}
+```
+
+!!! note "Observação"
+ Tenha em mente que neste caso, o FastAPI não irá validar os conteúdos da lista.
+
+ Por exemplo, um `List[int]` iria validar (e documentar) que os contéudos da lista são números inteiros. Mas apenas `list` não.
+
+## Declarando mais metadados
+
+Você pode adicionar mais informações sobre o parâmetro.
+
+Essa informações serão inclusas no esquema do OpenAPI e utilizado pela documentação interativa e ferramentas externas.
+
+!!! note "Observação"
+ Tenha em mente que cada ferramenta oferece diferentes níveis de suporte ao OpenAPI.
+
+ Algumas delas não exibem todas as informações extras que declaramos, ainda que na maioria dos casos, esses recursos estão planejados para desenvolvimento.
+
+Você pode adicionar um `title`:
+
+```Python hl_lines="10"
+{!../../../docs_src/query_params_str_validations/tutorial007.py!}
+```
+
+E uma `description`:
+
+```Python hl_lines="13"
+{!../../../docs_src/query_params_str_validations/tutorial008.py!}
+```
+
+## Apelidos (alias) de parâmetros
+
+Imagine que você queira que um parâmetro tenha o nome `item-query`.
+
+Desta maneira:
+
+```
+http://127.0.0.1:8000/items/?item-query=foobaritems
+```
+
+Mas o nome `item-query` não é um nome de váriavel válido no Python.
+
+O que mais se aproxima é `item_query`.
+
+Mas ainda você precisa que o nome seja exatamente `item-query`...
+
+Então você pode declarar um `alias`, e esse apelido (alias) que será utilizado para encontrar o valor do parâmetro:
+
+```Python hl_lines="9"
+{!../../../docs_src/query_params_str_validations/tutorial009.py!}
+```
+
+## Parâmetros descontinuados
+
+Agora vamos dizer que você não queria mais utilizar um parâmetro.
+
+Você tem que deixá-lo ativo por um tempo, já que existem clientes o utilizando. Mas você quer que a documentação deixe claro que este parâmetro será descontinuado.
+
+Então você passa o parâmetro `deprecated=True` para `Query`:
+
+```Python hl_lines="18"
+{!../../../docs_src/query_params_str_validations/tutorial010.py!}
+```
+
+Na documentação aparecerá assim:
+
+
+
+## Recapitulando
+
+Você pode adicionar validações e metadados adicionais aos seus parâmetros.
+
+Validações genéricas e metadados:
+
+* `alias`
+* `title`
+* `description`
+* `deprecated`
+
+Validações específicas para textos:
+
+* `min_length`
+* `max_length`
+* `regex`
+
+Nesses exemplos você viu como declarar validações em valores do tipo `str`.
+
+Leia os próximos capítulos para ver como declarar validação de outros tipos, como números.
diff --git a/docs/pt/docs/tutorial/security/index.md b/docs/pt/docs/tutorial/security/index.md
new file mode 100644
index 00000000..70f86404
--- /dev/null
+++ b/docs/pt/docs/tutorial/security/index.md
@@ -0,0 +1,101 @@
+# Introdução à segurança
+
+Há várias formas de lidar segurança, autenticação e autorização.
+
+E isso normalmente é um tópico “difícil” e complexo.
+
+Em muitos frameworks e sistemas, apenas lidar com segurança e autenticação exige muito esforço e código (em muitos casos isso pode ser 50% ou mais de todo o código escrito).
+
+**FastAPI** tem muitas ferramentas para ajudar você com a parte de **Segurança** facilmente, rapidamente, de uma forma padrão, sem ter que estudar e aprender tudo sobre especificações de segurança.
+
+Mas primeiro, vamos verificar alguns pequenos conceitos.
+
+## Está com pressa?
+
+Se você não se importa com qualquer um desses termos e só precisa adicionar segurança com autenticação baseada em usuário e senha _agora_, pule para os próximos capítulos.
+
+## OAuth2
+
+OAuth2 é uma especificação que define várias formas para lidar com autenticação e autorização.
+
+Ela é bastante extensiva na especificação e cobre casos de uso muito complexos.
+
+Ela inclui uma forma para autenticação usando “third party”/aplicações de terceiros.
+
+Isso é o que todos os sistemas com “Login with Facebook, Google, Twitter, GitHub” usam por baixo.
+
+### OAuth 1
+
+Havia um OAuth 1, que é bem diferente do OAuth2, e mais complexo, isso incluía diretamente as especificações de como criptografar a comunicação.
+
+Não é muito popular ou usado nos dias atuais.
+
+OAuth2 não especifica como criptografar a comunicação, ele espera que você tenha sua aplicação em um servidor HTTPS.
+
+!!! tip "Dica"
+ Na seção sobre **deployment** você irá ver como configurar HTTPS de modo gratuito, usando Traefik e Let’s Encrypt.
+
+
+## OpenID Connect
+
+OpenID Connect é outra especificação, baseada em **OAuth2**.
+
+Ela é apenas uma extensão do OAuth2 especificando algumas coisas que são relativamente ambíguas no OAuth2, para tentar torná-lo mais interoperável.
+
+Por exemplo, o login do Google usa OpenID Connect (que por baixo dos panos usa OAuth2).
+
+Mas o login do Facebook não tem suporte para OpenID Connect. Ele tem a própria implementação do OAuth2.
+
+### OpenID (não "OpenID Connect")
+
+Houve também uma especificação “OpenID”. Ela tentou resolver a mesma coisa que a **OpenID Connect**, mas não baseada em OAuth2.
+
+Então, ela foi um sistema adicional completo.
+
+Ela não é muito popular ou usada nos dias de hoje.
+
+## OpenAPI
+
+OpenAPI (anteriormente conhecido como Swagger) é a especificação aberta para a criação de APIs (agora parte da Linux Foundation).
+
+**FastAPI** é baseado no **OpenAPI**.
+
+Isso é o que torna possível ter múltiplas automações interativas de interfaces de documentação, geração de código, etc.
+
+OpenAPI tem uma forma para definir múltiplos “esquemas” de segurança.
+
+Por usá-los, você pode ter vantagens de todas essas ferramentas baseadas nos padrões, incluindo os sistemas de documentação interativa.
+
+OpenAPI define os seguintes esquemas de segurança:
+
+* `apiKey`: uma chave específica de aplicação que pode vir de:
+ * Um parâmetro query.
+ * Um header.
+ * Um cookie.
+* `http`: padrão HTTP de sistemas autenticação, incluindo:
+ * `bearer`: um header de `Authorization` com valor de `Bearer` adicionado de um token. Isso é herança do OAuth2.
+ * HTTP Basic authentication.
+ * HTTP Digest, etc.
+* `oauth2`: todas as formas do OAuth2 para lidar com segurança (chamados "fluxos").
+ * Vários desses fluxos são apropriados para construir um provedor de autenticação OAuth2 (como Google, Facebook, Twitter, GitHub, etc):
+ * `implicit`
+ * `clientCredentials`
+ * `authorizationCode`
+ * Mas existe um “fluxo” específico que pode ser perfeitamente usado para resolver autenticação diretamente na mesma aplicação:
+ * `password`: alguns dos próximos capítulos tratarão disso.
+* `openIdConnect`: tem uma forma para definir como descobrir automaticamente o dado da autenticação OAuth2.
+ * Essa descoberta automática é o que é definido na especificação OpenID Connect.
+
+
+!!! tip "Dica"
+ Integração com outros provedores de autenticação/autorização como Google, Facebook, Twitter, GitHub, etc. é bem possível e relativamente fácil.
+
+ O problema mais complexo é criar um provedor de autenticação/autorização como eles, mas o FastAPI dá a você ferramentas para fazer isso facilmente, enquanto faz o trabalho pesado para você.
+
+## **FastAPI** utilitários
+
+**FastAPI** fornece várias ferramentas para cada um desses esquemas de segurança no módulo `fastapi.security` que simplesmente usa esses mecanismos de segurança.
+
+Nos próximos capítulos você irá ver como adicionar segurança à sua API usando essas ferramentas disponibilizadas pelo **FastAPI**.
+
+E você irá ver também como isso é automaticamente integrado dentro do sistema de documentação interativo.
diff --git a/docs/pt/mkdocs.yml b/docs/pt/mkdocs.yml
index 6102a859..25340080 100644
--- a/docs/pt/mkdocs.yml
+++ b/docs/pt/mkdocs.yml
@@ -9,17 +9,18 @@ theme:
primary: teal
accent: amber
toggle:
- icon: material/lightbulb-outline
+ icon: material/lightbulb
name: Switch to light mode
- scheme: slate
primary: teal
accent: amber
toggle:
- icon: material/lightbulb
+ icon: material/lightbulb-outline
name: Switch to dark mode
features:
- search.suggest
- search.highlight
+ - content.tabs.link
icon:
repo: fontawesome/brands/github-alt
logo: https://fastapi.tiangolo.com/img/icon-white.svg
@@ -28,9 +29,6 @@ theme:
repo_name: tiangolo/fastapi
repo_url: https://github.com/tiangolo/fastapi
edit_uri: ''
-google_analytics:
-- UA-133183413-1
-- auto
plugins:
- search
- markdownextradata:
@@ -39,11 +37,16 @@ nav:
- FastAPI: index.md
- Languages:
- en: /
+ - az: /az/
+ - de: /de/
- es: /es/
+ - fa: /fa/
- fr: /fr/
+ - id: /id/
- it: /it/
- ja: /ja/
- ko: /ko/
+ - nl: /nl/
- pl: /pl/
- pt: /pt/
- ru: /ru/
@@ -52,20 +55,36 @@ nav:
- uk: /uk/
- zh: /zh/
- features.md
+- fastapi-people.md
- Tutorial - Guia de Usuário:
- tutorial/index.md
- tutorial/first-steps.md
+ - tutorial/path-params.md
+ - tutorial/body.md
- tutorial/body-fields.md
+ - tutorial/extra-data-types.md
+ - tutorial/query-params-str-validations.md
+ - tutorial/cookie-params.md
+ - Segurança:
+ - tutorial/security/index.md
+ - Guia de Usuário Avançado:
+ - advanced/index.md
+- Implantação:
+ - deployment/index.md
+ - deployment/versions.md
+ - deployment/https.md
+ - deployment/deta.md
- alternatives.md
- history-design-future.md
- external-links.md
- benchmarks.md
+- help-fastapi.md
markdown_extensions:
- toc:
permalink: true
- markdown.extensions.codehilite:
guess_lang: false
-- markdown_include.include:
+- mdx_include:
base_path: docs
- admonition
- codehilite
@@ -74,16 +93,20 @@ markdown_extensions:
custom_fences:
- name: mermaid
class: mermaid
- format: !!python/name:pymdownx.superfences.fence_div_format ''
-- pymdownx.tabbed
+ format: !!python/name:pymdownx.superfences.fence_code_format ''
+- pymdownx.tabbed:
+ alternate_style: true
extra:
+ analytics:
+ provider: google
+ property: UA-133183413-1
social:
- icon: fontawesome/brands/github-alt
link: https://github.com/tiangolo/fastapi
- icon: fontawesome/brands/discord
link: https://discord.gg/VQjSZaeJmf
- icon: fontawesome/brands/twitter
- link: https://twitter.com/tiangolo
+ link: https://twitter.com/fastapi
- icon: fontawesome/brands/linkedin
link: https://www.linkedin.com/in/tiangolo
- icon: fontawesome/brands/dev
@@ -95,16 +118,26 @@ extra:
alternate:
- link: /
name: en - English
+ - link: /az/
+ name: az
+ - link: /de/
+ name: de
- link: /es/
name: es - español
+ - link: /fa/
+ name: fa
- link: /fr/
name: fr - français
+ - link: /id/
+ name: id
- link: /it/
name: it - italiano
- link: /ja/
name: ja - 日本語
- link: /ko/
name: ko - 한국어
+ - link: /nl/
+ name: nl
- link: /pl/
name: pl
- link: /pt/
@@ -123,6 +156,5 @@ extra_css:
- https://fastapi.tiangolo.com/css/termynal.css
- https://fastapi.tiangolo.com/css/custom.css
extra_javascript:
-- https://unpkg.com/mermaid@8.4.6/dist/mermaid.min.js
- https://fastapi.tiangolo.com/js/termynal.js
- https://fastapi.tiangolo.com/js/custom.js
diff --git a/docs/ru/docs/async.md b/docs/ru/docs/async.md
new file mode 100644
index 00000000..fc5e4447
--- /dev/null
+++ b/docs/ru/docs/async.md
@@ -0,0 +1,505 @@
+# Конкурентность и async / await
+
+Здесь приведена подробная информация об использовании синтаксиса `async def` при написании *функций обработки пути*, а также рассмотрены основы асинхронного программирования, конкурентности и параллелизма.
+
+## Нет времени?
+
+TL;DR:
+
+Допустим, вы используете сторонюю библиотеку, которая требует вызова с ключевым словом `await`:
+
+```Python
+results = await some_library()
+```
+
+В этом случае *функции обработки пути* необходимо объявлять с использованием синтаксиса `async def`:
+
+```Python hl_lines="2"
+@app.get('/')
+async def read_results():
+ results = await some_library()
+ return results
+```
+
+!!! note
+ `await` можно использовать только внутри функций, объявленных с использованием `async def`.
+
+---
+
+Если вы обращаетесь к сторонней библиотеке, которая с чем-то взаимодействует
+(с базой данных, API, файловой системой и т. д.), и не имеет поддержки синтаксиса `await`
+(что относится сейчас к большинству библиотек для работы с базами данных), то
+объявляйте *функции обработки пути* обычным образом с помощью `def`, например:
+
+```Python hl_lines="2"
+@app.get('/')
+def results():
+ results = some_library()
+ return results
+```
+
+---
+
+Если вашему приложению (странным образом) не нужно ни с чем взаимодействовать и, соответственно,
+ожидать ответа, используйте `async def`.
+
+---
+
+Если вы не уверены, используйте обычный синтаксис `def`.
+
+---
+
+**Примечание**: при необходимости можно смешивать `def` и `async def` в *функциях обработки пути*
+и использовать в каждом случае наиболее подходящий синтаксис. А FastAPI сделает с этим всё, что нужно.
+
+В любом из описанных случаев FastAPI работает асинхронно и очень быстро.
+
+Однако придерживаясь указанных советов, можно получить дополнительную оптимизацию производительности.
+
+## Технические подробности
+
+Современные версии Python поддерживают разработку так называемого **"асинхронного кода"** посредством написания **"сопрограмм"** с использованием синтаксиса **`async` и `await`**.
+
+Ниже разберём эту фразу по частям:
+
+* **Асинхронный код**
+* **`async` и `await`**
+* **Сопрограммы**
+
+## Асинхронный код
+
+Асинхронный код означает, что в языке 💬 есть возможность сообщить машине / программе 🤖,
+что в определённой точке кода ей 🤖 нужно будет ожидать завершения выполнения *чего-то ещё* в другом месте. Допустим это *что-то ещё* называется "медленный файл" 📝.
+
+И пока мы ждём завершения работы с "медленным файлом" 📝, компьютер может переключиться для выполнения других задач.
+
+Но при каждой возможности компьютер / программа 🤖 будет возвращаться обратно. Например, если он 🤖 опять окажется в режиме ожидания, или когда закончит всю работу. В этом случае компьютер 🤖 проверяет, не завершена ли какая-нибудь из текущих задач.
+
+Потом он 🤖 берёт первую выполненную задачу (допустим, наш "медленный файл" 📝) и продолжает работу, производя с ней необходимые действия.
+
+Вышеупомянутое "что-то ещё", завершения которого приходится ожидать, обычно относится к достаточно "медленным" операциям I/O (по сравнению со скоростью работы процессора и оперативной памяти), например:
+
+* отправка данных от клиента по сети
+* получение клиентом данных, отправленных вашей программой по сети
+* чтение системой содержимого файла с диска и передача этих данных программе
+* запись на диск данных, которые программа передала системе
+* обращение к удалённому API
+* ожидание завершения операции с базой данных
+* получение результатов запроса к базе данных
+* и т. д.
+
+Поскольку в основном время тратится на ожидание выполнения операций I/O,
+их обычно называют операциями, ограниченными скоростью ввода-вывода.
+
+Код называют "асинхронным", потому что компьютеру / программе не требуется "синхронизироваться" с медленной задачей и,
+будучи в простое, ожидать момента её завершения, с тем чтобы забрать результат и продолжить работу.
+
+Вместо этого в "асинхронной" системе завершённая задача может немного подождать (буквально несколько микросекунд),
+пока компьютер / программа занимается другими важными вещами, с тем чтобы потом вернуться,
+забрать результаты выполнения и начать их обрабатывать.
+
+"Синхронное" исполнение (в противовес "асинхронному") также называют "последовательным",
+потому что компьютер / программа последовательно выполняет все требуемые шаги перед тем, как перейти к следующей задаче,
+даже если в процессе приходится ждать.
+
+### Конкурентность и бургеры
+
+Тот **асинхронный** код, о котором идёт речь выше, иногда называют **"конкурентностью"**. Она отличается от **"параллелизма"**.
+
+Да, **конкурентность** и **параллелизм** подразумевают, что разные вещи происходят примерно в одно время.
+
+Но внутреннее устройство **конкурентности** и **параллелизма** довольно разное.
+
+Чтобы это понять, представьте такую картину:
+
+### Конкурентные бургеры
+
+
+
+Вы идёте со своей возлюбленной 😍 в фастфуд 🍔 и становитесь в очередь, в это время кассир 💁 принимает заказы у посетителей перед вами.
+
+Когда наконец подходит очередь, вы заказываете парочку самых вкусных и навороченных бургеров 🍔, один для своей возлюбленной 😍, а другой себе.
+
+Отдаёте деньги 💸.
+
+Кассир 💁 что-то говорит поварам на кухне 👨🍳, теперь они знают, какие бургеры нужно будет приготовить 🍔
+(но пока они заняты бургерами предыдущих клиентов).
+
+Кассир 💁 отдаёт вам чек с номером заказа.
+
+В ожидании еды вы идёте со своей возлюбленной 😍 выбрать столик, садитесь и довольно продолжительное время общаетесь 😍
+(поскольку ваши бургеры самые навороченные, готовятся они не так быстро ✨🍔✨).
+
+Сидя за столиком с возлюбленной 😍 в ожидании бургеров 🍔, вы отлично проводите время,
+восхищаясь её великолепием, красотой и умом ✨😍✨.
+
+Всё ещё ожидая заказ и болтая со своей возлюбленной 😍, время от времени вы проверяете,
+какой номер горит над прилавком, и не подошла ли уже ваша очередь.
+
+И вот наконец настаёт этот момент, и вы идёте к стойке, чтобы забрать бургеры 🍔 и вернуться за столик.
+
+Вы со своей возлюбленной 😍 едите бургеры 🍔 и отлично проводите время ✨.
+
+---
+
+А теперь представьте, что в этой небольшой истории вы компьютер / программа 🤖.
+
+В очереди вы просто глазеете по сторонам 😴, ждёте и ничего особо "продуктивного" не делаете.
+Но очередь движется довольно быстро, поскольку кассир 💁 только принимает заказы (а не занимается приготовлением еды), так что ничего страшного.
+
+Когда подходит очередь вы наконец предпринимаете "продуктивные" действия 🤓: просматриваете меню, выбираете в нём что-то, узнаёте, что хочет ваша возлюбленная 😍, собираетесь оплатить 💸, смотрите, какую достали карту, проверяете, чтобы с вас списали верную сумму, и что в заказе всё верно и т. д.
+
+И хотя вы всё ещё не получили бургеры 🍔, ваша работа с кассиром 💁 ставится "на паузу" ⏸,
+поскольку теперь нужно ждать 🕙, когда заказ приготовят.
+
+Но отойдя с номерком от прилавка, вы садитесь за столик и можете переключить 🔀 внимание
+на свою возлюбленную 😍 и "работать" ⏯ 🤓 уже над этим. И вот вы снова очень
+"продуктивны" 🤓, мило болтаете вдвоём и всё такое 😍.
+
+В какой-то момент кассир 💁 поместит на табло ваш номер, подразумевая, что бургеры готовы 🍔, но вы не станете подскакивать как умалишённый, лишь только увидев на экране свою очередь. Вы уверены, что ваши бургеры 🍔 никто не утащит, ведь у вас свой номерок, а у других свой.
+
+Поэтому вы подождёте, пока возлюбленная 😍 закончит рассказывать историю (закончите текущую работу ⏯ / задачу в обработке 🤓),
+и мило улыбнувшись, скажете, что идёте забирать заказ ⏸.
+
+И вот вы подходите к стойке 🔀, к первоначальной задаче, которая уже завершена ⏯, берёте бургеры 🍔, говорите спасибо и относите заказ за столик. На этом заканчивается этап / задача взаимодействия с кассой ⏹.
+В свою очередь порождается задача "поедание бургеров" 🔀 ⏯, но предыдущая ("получение бургеров") завершена ⏹.
+
+### Параллельные бургеры
+
+Теперь представим, что вместо бургерной "Конкурентные бургеры" вы решили сходить в "Параллельные бургеры".
+
+И вот вы идёте со своей возлюбленной 😍 отведать параллельного фастфуда 🍔.
+
+Вы становитесь в очередь пока несколько (пусть будет 8) кассиров, которые по совместительству ещё и повары 👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳, принимают заказы у посетителей перед вами.
+
+При этом клиенты не отходят от стойки и ждут 🕙 получения еды, поскольку каждый
+из 8 кассиров идёт на кухню готовить бургеры 🍔, а только потом принимает следующий заказ.
+
+Наконец настаёт ваша очередь, и вы просите два самых навороченных бургера 🍔, один для дамы сердца 😍, а другой себе.
+
+Ни о чём не жалея, расплачиваетесь 💸.
+
+И кассир уходит на кухню 👨🍳.
+
+Вам приходится ждать перед стойкой 🕙, чтобы никто по случайности не забрал ваши бургеры 🍔, ведь никаких номерков у вас нет.
+
+Поскольку вы с возлюбленной 😍 хотите получить заказ вовремя 🕙, и следите за тем, чтобы никто не вклинился в очередь,
+у вас не получается уделять должного внимание своей даме сердца 😞.
+
+Это "синхронная" работа, вы "синхронизированы" с кассиром/поваром 👨🍳. Приходится ждать 🕙 у стойки,
+когда кассир/повар 👨🍳 закончит делать бургеры 🍔 и вручит вам заказ, иначе его случайно может забрать кто-то другой.
+
+Наконец кассир/повар 👨🍳 возвращается с бургерами 🍔 после невыносимо долгого ожидания 🕙 за стойкой.
+
+Вы скорее забираете заказ 🍔 и идёте с возлюбленной 😍 за столик.
+
+Там вы просто едите эти бургеры, и на этом всё 🍔 ⏹.
+
+Вам не особо удалось пообщаться, потому что большую часть времени 🕙 пришлось провести у кассы 😞.
+
+---
+
+В описанном сценарии вы компьютер / программа 🤖 с двумя исполнителями (вы и ваша возлюбленная 😍),
+на протяжении долгого времени 🕙 вы оба уделяете всё внимание ⏯ задаче "ждать на кассе".
+
+В этом ресторане быстрого питания 8 исполнителей (кассиров/поваров) 👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳.
+Хотя в бургерной конкурентного типа было всего два (один кассир и один повар) 💁 👨🍳.
+
+Несмотря на обилие работников, опыт в итоге получился не из лучших 😞.
+
+---
+
+Так бы выглядел аналог истории про бургерную 🍔 в "параллельном" мире.
+
+Вот более реалистичный пример. Представьте себе банк.
+
+До недавних пор в большинстве банков было несколько кассиров 👨💼👨💼👨💼👨💼 и длинные очереди 🕙🕙🕙🕙🕙🕙🕙🕙.
+
+Каждый кассир обслуживал одного клиента, потом следующего 👨💼⏯.
+
+Нужно было долгое время 🕙 стоять перед окошком вместе со всеми, иначе пропустишь свою очередь.
+
+Сомневаюсь, что у вас бы возникло желание прийти с возлюбленной 😍 в банк 🏦 оплачивать налоги.
+
+### Выводы о бургерах
+
+В нашей истории про поход в фастфуд за бургерами приходится много ждать 🕙,
+поэтому имеет смысл организовать конкурентную систему ⏸🔀⏯.
+
+И то же самое с большинством веб-приложений.
+
+Пользователей очень много, но ваш сервер всё равно вынужден ждать 🕙 запросы по их слабому интернет-соединению.
+
+Потом снова ждать 🕙, пока вернётся ответ.
+
+
+Это ожидание 🕙 измеряется микросекундами, но если всё сложить, то набегает довольно много времени.
+
+Вот почему есть смысл использовать асинхронное ⏸🔀⏯ программирование при построении веб-API.
+
+Большинство популярных фреймворков (включая Flask и Django) создавались
+до появления в Python новых возможностей асинхронного программирования. Поэтому
+их можно разворачивать с поддержкой параллельного исполнения или асинхронного
+программирования старого типа, которое не настолько эффективно.
+
+При том, что основная спецификация асинхронного взаимодействия Python с веб-сервером
+(ASGI)
+была разработана командой Django для внедрения поддержки веб-сокетов.
+
+Именно асинхронность сделала NodeJS таким популярным (несмотря на то, что он не параллельный),
+и в этом преимущество Go как языка программирования.
+
+И тот же уровень производительности даёт **FastAPI**.
+
+Поскольку можно использовать преимущества параллелизма и асинхронности вместе,
+вы получаете производительность лучше, чем у большинства протестированных NodeJS фреймворков
+и на уровне с Go, который является компилируемым языком близким к C (всё благодаря Starlette).
+
+### Получается, конкурентность лучше параллелизма?
+
+Нет! Мораль истории совсем не в этом.
+
+Конкурентность отличается от параллелизма. Она лучше в **конкретных** случаях, где много времени приходится на ожидание.
+Вот почему она зачастую лучше параллелизма при разработке веб-приложений. Но это не значит, что конкурентность лучше в любых сценариях.
+
+Давайте посмотрим с другой стороны, представьте такую картину:
+
+> Вам нужно убраться в большом грязном доме.
+
+*Да, это вся история*.
+
+---
+
+Тут не нужно нигде ждать 🕙, просто есть куча работы в разных частях дома.
+
+Можно организовать очередь как в примере с бургерами, сначала гостиная, потом кухня,
+но это ни на что не повлияет, поскольку вы нигде не ждёте 🕙, а просто трёте да моете.
+
+И понадобится одинаковое количество времени с очередью (конкурентностью) и без неё,
+и работы будет сделано тоже одинаковое количество.
+
+Однако в случае, если бы вы могли привести 8 бывших кассиров/поваров, а ныне уборщиков 👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳👩🍳👨🍳,
+и каждый из них (вместе с вами) взялся бы за свой участок дома,
+с такой помощью вы бы закончили намного быстрее, делая всю работу **параллельно**.
+
+В описанном сценарии каждый уборщик (включая вас) был бы исполнителем, занятым на своём участке работы.
+
+И поскольку большую часть времени выполнения занимает реальная работа (а не ожидание),
+а работу в компьютере делает ЦП,
+такие задачи называют ограниченными производительностью процессора.
+
+---
+
+Ограничение по процессору проявляется в операциях, где требуется выполнять сложные математические вычисления.
+
+Например:
+
+* Обработка **звука** или **изображений**.
+* **Компьютерное зрение**: изображение состоит из миллионов пикселей, в каждом пикселе 3 составляющих цвета,
+обработка обычно требует проведения расчётов по всем пикселям сразу.
+* **Машинное обучение**: здесь обычно требуется умножение "матриц" и "векторов".
+Представьте гигантскую таблицу с числами в Экселе, и все их надо одновременно перемножить.
+* **Глубокое обучение**: это область *машинного обучения*, поэтому сюда подходит то же описание.
+Просто у вас будет не одна таблица в Экселе, а множество. В ряде случаев используется
+специальный процессор для создания и / или использования построенных таким образом моделей.
+
+### Конкурентность + параллелизм: Веб + машинное обучение
+
+**FastAPI** предоставляет возможности конкуретного программирования,
+которое очень распространено в веб-разработке (именно этим славится NodeJS).
+
+Кроме того вы сможете использовать все преимущества параллелизма и
+многопроцессорности (когда несколько процессов работают параллельно),
+если рабочая нагрузка предполагает **ограничение по процессору**,
+как, например, в системах машинного обучения.
+
+Необходимо также отметить, что Python является главным языком в области
+**дата-сайенс**,
+машинного обучения и, особенно, глубокого обучения. Всё это делает FastAPI
+отличным вариантом (среди многих других) для разработки веб-API и приложений
+в области дата-сайенс / машинного обучения.
+
+Как добиться такого параллелизма в эксплуатации описано в разделе [Развёртывание](deployment/index.md){.internal-link target=_blank}.
+
+## `async` и `await`
+
+В современных версиях Python разработка асинхронного кода реализована очень интуитивно.
+Он выглядит как обычный "последовательный" код и самостоятельно выполняет "ожидание", когда это необходимо.
+
+Если некая операция требует ожидания перед тем, как вернуть результат, и
+поддерживает современные возможности Python, код можно написать следующим образом:
+
+```Python
+burgers = await get_burgers(2)
+```
+
+Главное здесь слово `await`. Оно сообщает интерпретатору, что необходимо дождаться ⏸
+пока `get_burgers(2)` закончит свои дела 🕙, и только после этого сохранить результат в `burgers`.
+Зная это, Python может пока переключиться на выполнение других задач 🔀 ⏯
+(например получение следующего запроса).
+
+Чтобы ключевое слово `await` сработало, оно должно находиться внутри функции,
+которая поддерживает асинхронность. Для этого вам просто нужно объявить её как `async def`:
+
+```Python hl_lines="1"
+async def get_burgers(number: int):
+ # Готовим бургеры по специальному асинхронному рецепту
+ return burgers
+```
+
+...вместо `def`:
+
+```Python hl_lines="2"
+# Это не асинхронный код
+def get_sequential_burgers(number: int):
+ # Готовим бургеры последовательно по шагам
+ return burgers
+```
+
+Объявление `async def` указывает интерпретатору, что внутри этой функции
+следует ожидать выражений `await`, и что можно поставить выполнение такой функции на "паузу" ⏸ и
+переключиться на другие задачи 🔀, с тем чтобы вернуться сюда позже.
+
+Если вы хотите вызвать функцию с `async def`, вам нужно "ожидать" её.
+Поэтому такое не сработает:
+
+```Python
+# Это не заработает, поскольку get_burgers объявлена с использованием async def
+burgers = get_burgers(2)
+```
+
+---
+
+Если сторонняя библиотека требует вызывать её с ключевым словом `await`,
+необходимо писать *функции обработки пути* с использованием `async def`, например:
+
+```Python hl_lines="2-3"
+@app.get('/burgers')
+async def read_burgers():
+ burgers = await get_burgers(2)
+ return burgers
+```
+
+### Технические подробности
+
+Как вы могли заметить, `await` может применяться только в функциях, объявленных с использованием `async def`.
+
+
+Но выполнение такой функции необходимо "ожидать" с помощью `await`.
+Это означает, что её можно вызвать только из другой функции, которая тоже объявлена с `async def`.
+
+Но как же тогда появилась первая курица? В смысле... как нам вызвать первую асинхронную функцию?
+
+При работе с **FastAPI** просто не думайте об этом, потому что "первой" функцией является ваша *функция обработки пути*,
+и дальше с этим разберётся FastAPI.
+
+Кроме того, если хотите, вы можете использовать синтаксис `async` / `await` и без FastAPI.
+
+### Пишите свой асинхронный код
+
+Starlette (и **FastAPI**) основаны на AnyIO, что делает их совместимыми как со стандартной библиотекой asyncio в Python, так и с Trio.
+
+В частности, вы можете напрямую использовать AnyIO в тех проектах, где требуется более сложная логика работы с конкурентностью.
+
+Даже если вы не используете FastAPI, вы можете писать асинхронные приложения с помощью AnyIO, чтобы они были максимально совместимыми и получали его преимущества (например *структурную конкурентность*).
+
+### Другие виды асинхронного программирования
+
+Стиль написания кода с `async` и `await` появился в языке Python относительно недавно.
+
+Но он сильно облегчает работу с асинхронным кодом.
+
+Ровно такой же синтаксис (ну или почти такой же) недавно был включён в современные версии JavaScript (в браузере и NodeJS).
+
+До этого поддержка асинхронного кода была реализована намного сложнее, и его было труднее воспринимать.
+
+В предыдущих версиях Python для этого использовались потоки или Gevent. Но такой код намного сложнее понимать, отлаживать и мысленно представлять.
+
+Что касается JavaScript (в браузере и NodeJS), раньше там использовали для этой цели
+"обратные вызовы". Что выливалось в
+ад обратных вызовов.
+
+## Сопрограммы
+
+**Корути́на** (или же сопрограмма) — это крутое словечко для именования той сущности,
+которую возвращает функция `async def`. Python знает, что её можно запустить, как и обычную функцию,
+но кроме того сопрограмму можно поставить на паузу ⏸ в том месте, где встретится слово `await`.
+
+Всю функциональность асинхронного программирования с использованием `async` и `await`
+часто обобщают словом "корутины". Они аналогичны "горутинам", ключевой особенности
+языка Go.
+
+## Заключение
+
+В самом начале была такая фраза:
+
+> Современные версии Python поддерживают разработку так называемого
+**"асинхронного кода"** посредством написания **"сопрограмм"** с использованием
+синтаксиса **`async` и `await`**.
+
+Теперь всё должно звучать понятнее. ✨
+
+На этом основана работа FastAPI (посредством Starlette), и именно это
+обеспечивает его высокую производительность.
+
+## Очень технические подробности
+
+!!! warning
+ Этот раздел читать не обязательно.
+
+ Здесь приводятся подробности внутреннего устройства **FastAPI**.
+
+ Но если вы обладаете техническими знаниями (корутины, потоки, блокировка и т. д.)
+ и вам интересно, как FastAPI обрабатывает `async def` в отличие от обычных `def`,
+ читайте дальше.
+
+### Функции обработки пути
+
+Когда вы объявляете *функцию обработки пути* обычным образом с ключевым словом `def`
+вместо `async def`, FastAPI ожидает её выполнения, запустив функцию во внешнем
+пуле потоков, а не напрямую (это бы заблокировало сервер).
+
+Если ранее вы использовали другой асинхронный фреймворк, который работает иначе,
+и привыкли объявлять простые вычислительные *функции* через `def` ради
+незначительного прироста скорости (порядка 100 наносекунд), обратите внимание,
+что с **FastAPI** вы получите противоположный эффект. В таком случае больше подходит
+`async def`, если только *функция обработки пути* не использует код, приводящий
+к блокировке I/O.
+
+
+
+Но в любом случае велика вероятность, что **FastAPI** [окажется быстрее](/#performance){.internal-link target=_blank}
+другого фреймворка (или хотя бы на уровне с ним).
+
+### Зависимости
+
+То же относится к зависимостям. Если это обычная функция `def`, а не `async def`,
+она запускается во внешнем пуле потоков.
+
+### Подзависимости
+
+Вы можете объявить множество ссылающихся друг на друга зависимостей и подзависимостей
+(в виде параметров при определении функции). Какие-то будут созданы с помощью `async def`,
+другие обычным образом через `def`, и такая схема вполне работоспособна. Функции,
+объявленные с помощью `def` будут запускаться на внешнем потоке (из пула),
+а не с помощью `await`.
+
+### Другие служебные функции
+
+Любые другие служебные функции, которые вы вызываете напрямую, можно объявлять
+с использованием `def` или `async def`. FastAPI не будет влиять на то, как вы
+их запускаете.
+
+Этим они отличаются от функций, которые FastAPI вызывает самостоятельно:
+*функции обработки пути* и зависимости.
+
+Если служебная функция объявлена с помощью `def`, она будет вызвана напрямую
+(как вы и написали в коде), а не в отдельном потоке. Если же она объявлена с
+помощью `async def`, её вызов должен осуществляться с ожиданием через `await`.
+
+---
+
+
+Ещё раз повторим, что все эти технические подробности полезны, только если вы специально их искали.
+
+В противном случае просто ознакомьтесь с основными принципами в разделе выше: Нет времени?.
diff --git a/docs/ru/docs/external-links.md b/docs/ru/docs/external-links.md
new file mode 100644
index 00000000..4daf6589
--- /dev/null
+++ b/docs/ru/docs/external-links.md
@@ -0,0 +1,82 @@
+# Внешние ссылки и статьи
+
+**FastAPI** имеет отличное и постоянно растущее сообщество.
+
+Существует множество сообщений, статей, инструментов и проектов, связанных с **FastAPI**.
+
+Вот неполный список некоторых из них.
+
+!!! tip
+ Если у вас есть статья, проект, инструмент или что-либо, связанное с **FastAPI**, что еще не перечислено здесь, создайте Pull Request.
+
+## Статьи
+
+### На английском
+
+{% if external_links %}
+{% for article in external_links.articles.english %}
+
+* {{ article.title }} by {{ article.author }}.
+{% endfor %}
+{% endif %}
+
+### На японском
+
+{% if external_links %}
+{% for article in external_links.articles.japanese %}
+
+* {{ article.title }} by {{ article.author }}.
+{% endfor %}
+{% endif %}
+
+### На вьетнамском
+
+{% if external_links %}
+{% for article in external_links.articles.vietnamese %}
+
+* {{ article.title }} by {{ article.author }}.
+{% endfor %}
+{% endif %}
+
+### На русском
+
+{% if external_links %}
+{% for article in external_links.articles.russian %}
+
+* {{ article.title }} by {{ article.author }}.
+{% endfor %}
+{% endif %}
+
+### На немецком
+
+{% if external_links %}
+{% for article in external_links.articles.german %}
+
+* {{ article.title }} by {{ article.author }}.
+{% endfor %}
+{% endif %}
+
+## Подкасты
+
+{% if external_links %}
+{% for article in external_links.podcasts.english %}
+
+* {{ article.title }} by {{ article.author }}.
+{% endfor %}
+{% endif %}
+
+## Talks
+
+{% if external_links %}
+{% for article in external_links.talks.english %}
+
+* {{ article.title }} by {{ article.author }}.
+{% endfor %}
+{% endif %}
+
+## Проекты
+
+Последние GitHub-проекты с пометкой `fastapi`:
+
+requests - Required if you want to use the `TestClient`.
-* aiofiles - Required if you want to use `FileResponse` or `StaticFiles`.
* jinja2 - Required if you want to use the default template configuration.
* python-multipart - Required if you want to support form "parsing", with `request.form()`.
* itsdangerous - Required for `SessionMiddleware` support.
diff --git a/docs/ru/docs/python-types.md b/docs/ru/docs/python-types.md
new file mode 100644
index 00000000..99670363
--- /dev/null
+++ b/docs/ru/docs/python-types.md
@@ -0,0 +1,314 @@
+# Введение в аннотации типов Python
+
+Python имеет поддержку необязательных аннотаций типов.
+
+**Аннотации типов** являются специальным синтаксисом, который позволяет определять тип переменной.
+
+Объявление типов для переменных позволяет улучшить поддержку вашего кода редакторами и различными инструментами.
+
+Это просто **краткое руководство / напоминание** об аннотациях типов в Python. Оно охватывает только минимум, необходимый для их использования с **FastAPI**... что на самом деле очень мало.
+
+**FastAPI** целиком основан на аннотациях типов, у них много выгод и преимуществ.
+
+Но даже если вы никогда не используете **FastAPI**, вам будет полезно немного узнать о них.
+
+!!! note
+ Если вы являетесь экспертом в Python и уже знаете всё об аннотациях типов, переходите к следующему разделу.
+
+## Мотивация
+
+Давайте начнем с простого примера:
+
+```Python
+{!../../../docs_src/python_types/tutorial001.py!}
+```
+
+Вызов этой программы выводит:
+
+```
+John Doe
+```
+
+Функция делает следующее:
+
+* Принимает `first_name` и `last_name`.
+* Преобразует первую букву содержимого каждой переменной в верхний регистр с `title()`.
+* Соединяет их через пробел.
+
+```Python hl_lines="2"
+{!../../../docs_src/python_types/tutorial001.py!}
+```
+
+### Отредактируем пример
+
+Это очень простая программа.
+
+А теперь представьте, что вы пишете её с нуля.
+
+В какой-то момент вы бы начали определение функции, у вас были бы готовы параметры...
+
+Но затем вы должны вызвать «тот метод, который преобразует первую букву в верхний регистр».
+
+Было это `upper`? Или `uppercase`? `first_uppercase`? `capitalize`?
+
+Тогда вы попробуете с давним другом программиста: автодополнением редактора.
+
+Вы вводите первый параметр функции, `first_name`, затем точку (`.`), а затем нажимаете `Ctrl+Space`, чтобы запустить дополнение.
+
+Но, к сожалению, ничего полезного не выходит:
+
+
+
+### Добавим типы
+
+Давайте изменим одну строчку в предыдущей версии.
+
+Мы изменим именно этот фрагмент, параметры функции, с:
+
+```Python
+ first_name, last_name
+```
+
+на:
+
+```Python
+ first_name: str, last_name: str
+```
+
+Вот и все.
+
+Это аннотации типов:
+
+```Python hl_lines="1"
+{!../../../docs_src/python_types/tutorial002.py!}
+```
+
+Это не то же самое, что объявление значений по умолчанию, например:
+
+```Python
+ first_name="john", last_name="doe"
+```
+
+Это другая вещь.
+
+Мы используем двоеточия (`:`), а не равно (`=`).
+
+И добавление аннотаций типов обычно не меняет происходящего по сравнению с тем, что произошло бы без неё.
+
+Но теперь представьте, что вы снова находитесь в процессе создания этой функции, но уже с аннотациями типов.
+
+В тот же момент вы пытаетесь запустить автодополнение с помощью `Ctrl+Space` и вы видите:
+
+
+
+При этом вы можете просматривать варианты, пока не найдёте подходящий:
+
+
+
+## Больше мотивации
+
+Проверьте эту функцию, она уже имеет аннотации типов:
+
+```Python hl_lines="1"
+{!../../../docs_src/python_types/tutorial003.py!}
+```
+
+Поскольку редактор знает типы переменных, вы получаете не только дополнение, но и проверки ошибок:
+
+
+
+Теперь вы знаете, что вам нужно исправить, преобразовав `age` в строку с `str(age)`:
+
+```Python hl_lines="2"
+{!../../../docs_src/python_types/tutorial004.py!}
+```
+
+## Объявление типов
+
+Вы только что видели основное место для объявления подсказок типов. В качестве параметров функции.
+
+Это также основное место, где вы можете использовать их с **FastAPI**.
+
+### Простые типы
+
+Вы можете объявить все стандартные типы Python, а не только `str`.
+
+Вы можете использовать, к примеру:
+
+* `int`
+* `float`
+* `bool`
+* `bytes`
+
+```Python hl_lines="1"
+{!../../../docs_src/python_types/tutorial005.py!}
+```
+
+### Generic-типы с параметрами типов
+
+Существуют некоторые структуры данных, которые могут содержать другие значения, например, `dict`, `list`, `set` и `tuple`. И внутренние значения тоже могут иметь свой тип.
+
+Чтобы объявить эти типы и внутренние типы, вы можете использовать стандартный Python-модуль `typing`.
+
+Он существует специально для поддержки подсказок этих типов.
+
+#### `List`
+
+Например, давайте определим переменную как `list`, состоящий из `str`.
+
+Импортируйте `List` из `typing` (с заглавной `L`):
+
+```Python hl_lines="1"
+{!../../../docs_src/python_types/tutorial006.py!}
+```
+
+Объявите переменную с тем же синтаксисом двоеточия (`:`).
+
+В качестве типа укажите `List`.
+
+Поскольку список является типом, содержащим некоторые внутренние типы, вы помещаете их в квадратные скобки:
+
+```Python hl_lines="4"
+{!../../../docs_src/python_types/tutorial006.py!}
+```
+
+!!! tip
+ Эти внутренние типы в квадратных скобках называются «параметрами типов».
+
+ В этом случае `str` является параметром типа, передаваемым в `List`.
+
+Это означает: "переменная `items` является `list`, и каждый из элементов этого списка является `str`".
+
+Если вы будете так поступать, редактор может оказывать поддержку даже при обработке элементов списка:
+
+
+
+Без типов добиться этого практически невозможно.
+
+Обратите внимание, что переменная `item` является одним из элементов списка `items`.
+
+И все же редактор знает, что это `str`, и поддерживает это.
+
+#### `Tuple` и `Set`
+
+Вы бы сделали то же самое, чтобы объявить `tuple` и `set`:
+
+```Python hl_lines="1 4"
+{!../../../docs_src/python_types/tutorial007.py!}
+```
+
+Это означает:
+
+* Переменная `items_t` является `tuple` с 3 элементами: `int`, другим `int` и `str`.
+* Переменная `items_s` является `set` и каждый элемент имеет тип `bytes`.
+
+#### `Dict`
+
+Чтобы определить `dict`, вы передаёте 2 параметра типов, разделённых запятыми.
+
+Первый параметр типа предназначен для ключей `dict`.
+
+Второй параметр типа предназначен для значений `dict`:
+
+```Python hl_lines="1 4"
+{!../../../docs_src/python_types/tutorial008.py!}
+```
+
+Это означает:
+
+* Переменная `prices` является `dict`:
+ * Ключи этого `dict` имеют тип `str` (скажем, название каждого элемента).
+ * Значения этого `dict` имеют тип `float` (скажем, цена каждой позиции).
+
+#### `Optional`
+
+Вы также можете использовать `Optional`, чтобы объявить, что переменная имеет тип, например, `str`, но это является «необязательным», что означает, что она также может быть `None`:
+
+```Python hl_lines="1 4"
+{!../../../docs_src/python_types/tutorial009.py!}
+```
+
+Использование `Optional[str]` вместо просто `str` позволит редактору помочь вам в обнаружении ошибок, в которых вы могли бы предположить, что значение всегда является `str`, хотя на самом деле это может быть и `None`.
+
+#### Generic-типы
+
+Эти типы принимают параметры в квадратных скобках:
+
+* `List`
+* `Tuple`
+* `Set`
+* `Dict`
+* `Optional`
+* ...и др.
+
+называются **Generic-типами** или **Generics**.
+
+### Классы как типы
+
+Вы также можете объявить класс как тип переменной.
+
+Допустим, у вас есть класс `Person` с полем `name`:
+
+```Python hl_lines="1-3"
+{!../../../docs_src/python_types/tutorial010.py!}
+```
+
+Тогда вы можете объявить переменную типа `Person`:
+
+```Python hl_lines="6"
+{!../../../docs_src/python_types/tutorial010.py!}
+```
+
+И снова вы получаете полную поддержку редактора:
+
+
+
+## Pydantic-модели
+
+Pydantic является Python-библиотекой для выполнения валидации данных.
+
+Вы объявляете «форму» данных как классы с атрибутами.
+
+И каждый атрибут имеет тип.
+
+Затем вы создаете экземпляр этого класса с некоторыми значениями, и он проверяет значения, преобразует их в соответствующий тип (если все верно) и предоставляет вам объект со всеми данными.
+
+И вы получаете полную поддержку редактора для этого итогового объекта.
+
+Взято из официальной документации Pydantic:
+
+```Python
+{!../../../docs_src/python_types/tutorial011.py!}
+```
+
+!!! info
+ Чтобы узнать больше о Pydantic, читайте его документацию.
+
+**FastAPI** целиком основан на Pydantic.
+
+Вы увидите намного больше всего этого на практике в [Руководстве пользователя](tutorial/index.md){.internal-link target=_blank}.
+
+## Аннотации типов в **FastAPI**
+
+**FastAPI** получает преимущества аннотаций типов для выполнения определённых задач.
+
+С **FastAPI** вы объявляете параметры с аннотациями типов и получаете:
+
+* **Поддержку редактора**.
+* **Проверки типов**.
+
+...и **FastAPI** использует тот же механизм для:
+
+* **Определения требований**: из параметров пути запроса, параметров запроса, заголовков, зависимостей и т.д.
+* **Преобразования данных**: от запроса к нужному типу.
+* **Валидации данных**: исходя из каждого запроса:
+ * Генерации **автоматических ошибок**, возвращаемых клиенту, когда данные не являются корректными.
+* **Документирования** API с использованием OpenAPI:
+ * который затем используется пользовательскими интерфейсами автоматической интерактивной документации.
+
+Всё это может показаться абстрактным. Не волнуйтесь. Вы увидите всё это в действии в [Руководстве пользователя](tutorial/index.md){.internal-link target=_blank}.
+
+Важно то, что при использовании стандартных типов Python в одном месте (вместо добавления дополнительных классов, декораторов и т.д.) **FastAPI** сделает за вас большую часть работы.
+
+!!! info
+ Если вы уже прошли всё руководство и вернулись, чтобы узнать больше о типах, хорошим ресурсом является «шпаргалка» от `mypy`.
diff --git a/docs/ru/mkdocs.yml b/docs/ru/mkdocs.yml
index b2b1b9aa..0f8f0041 100644
--- a/docs/ru/mkdocs.yml
+++ b/docs/ru/mkdocs.yml
@@ -9,17 +9,18 @@ theme:
primary: teal
accent: amber
toggle:
- icon: material/lightbulb-outline
+ icon: material/lightbulb
name: Switch to light mode
- scheme: slate
primary: teal
accent: amber
toggle:
- icon: material/lightbulb
+ icon: material/lightbulb-outline
name: Switch to dark mode
features:
- search.suggest
- search.highlight
+ - content.tabs.link
icon:
repo: fontawesome/brands/github-alt
logo: https://fastapi.tiangolo.com/img/icon-white.svg
@@ -28,9 +29,6 @@ theme:
repo_name: tiangolo/fastapi
repo_url: https://github.com/tiangolo/fastapi
edit_uri: ''
-google_analytics:
-- UA-133183413-1
-- auto
plugins:
- search
- markdownextradata:
@@ -39,11 +37,16 @@ nav:
- FastAPI: index.md
- Languages:
- en: /
+ - az: /az/
+ - de: /de/
- es: /es/
+ - fa: /fa/
- fr: /fr/
+ - id: /id/
- it: /it/
- ja: /ja/
- ko: /ko/
+ - nl: /nl/
- pl: /pl/
- pt: /pt/
- ru: /ru/
@@ -51,12 +54,13 @@ nav:
- tr: /tr/
- uk: /uk/
- zh: /zh/
+- async.md
markdown_extensions:
- toc:
permalink: true
- markdown.extensions.codehilite:
guess_lang: false
-- markdown_include.include:
+- mdx_include:
base_path: docs
- admonition
- codehilite
@@ -65,16 +69,20 @@ markdown_extensions:
custom_fences:
- name: mermaid
class: mermaid
- format: !!python/name:pymdownx.superfences.fence_div_format ''
-- pymdownx.tabbed
+ format: !!python/name:pymdownx.superfences.fence_code_format ''
+- pymdownx.tabbed:
+ alternate_style: true
extra:
+ analytics:
+ provider: google
+ property: UA-133183413-1
social:
- icon: fontawesome/brands/github-alt
link: https://github.com/tiangolo/fastapi
- icon: fontawesome/brands/discord
link: https://discord.gg/VQjSZaeJmf
- icon: fontawesome/brands/twitter
- link: https://twitter.com/tiangolo
+ link: https://twitter.com/fastapi
- icon: fontawesome/brands/linkedin
link: https://www.linkedin.com/in/tiangolo
- icon: fontawesome/brands/dev
@@ -86,16 +94,26 @@ extra:
alternate:
- link: /
name: en - English
+ - link: /az/
+ name: az
+ - link: /de/
+ name: de
- link: /es/
name: es - español
+ - link: /fa/
+ name: fa
- link: /fr/
name: fr - français
+ - link: /id/
+ name: id
- link: /it/
name: it - italiano
- link: /ja/
name: ja - 日本語
- link: /ko/
name: ko - 한국어
+ - link: /nl/
+ name: nl
- link: /pl/
name: pl
- link: /pt/
@@ -114,6 +132,5 @@ extra_css:
- https://fastapi.tiangolo.com/css/termynal.css
- https://fastapi.tiangolo.com/css/custom.css
extra_javascript:
-- https://unpkg.com/mermaid@8.4.6/dist/mermaid.min.js
- https://fastapi.tiangolo.com/js/termynal.js
- https://fastapi.tiangolo.com/js/custom.js
diff --git a/docs/sq/docs/index.md b/docs/sq/docs/index.md
index edc19fa4..a7af1478 100644
--- a/docs/sq/docs/index.md
+++ b/docs/sq/docs/index.md
@@ -44,13 +44,16 @@ The key features are:
* estimation based on tests on an internal development team, building production applications.
-## Gold Sponsors
+## Sponsors
{% if sponsors %}
{% for sponsor in sponsors.gold -%}
-requests - Required if you want to use the `TestClient`.
-* aiofiles - Required if you want to use `FileResponse` or `StaticFiles`.
* jinja2 - Required if you want to use the default template configuration.
* python-multipart - Required if you want to support form "parsing", with `request.form()`.
* itsdangerous - Required for `SessionMiddleware` support.
diff --git a/docs/sq/mkdocs.yml b/docs/sq/mkdocs.yml
index 9b255718..a61f49bc 100644
--- a/docs/sq/mkdocs.yml
+++ b/docs/sq/mkdocs.yml
@@ -9,17 +9,18 @@ theme:
primary: teal
accent: amber
toggle:
- icon: material/lightbulb-outline
+ icon: material/lightbulb
name: Switch to light mode
- scheme: slate
primary: teal
accent: amber
toggle:
- icon: material/lightbulb
+ icon: material/lightbulb-outline
name: Switch to dark mode
features:
- search.suggest
- search.highlight
+ - content.tabs.link
icon:
repo: fontawesome/brands/github-alt
logo: https://fastapi.tiangolo.com/img/icon-white.svg
@@ -28,9 +29,6 @@ theme:
repo_name: tiangolo/fastapi
repo_url: https://github.com/tiangolo/fastapi
edit_uri: ''
-google_analytics:
-- UA-133183413-1
-- auto
plugins:
- search
- markdownextradata:
@@ -39,11 +37,16 @@ nav:
- FastAPI: index.md
- Languages:
- en: /
+ - az: /az/
+ - de: /de/
- es: /es/
+ - fa: /fa/
- fr: /fr/
+ - id: /id/
- it: /it/
- ja: /ja/
- ko: /ko/
+ - nl: /nl/
- pl: /pl/
- pt: /pt/
- ru: /ru/
@@ -56,7 +59,7 @@ markdown_extensions:
permalink: true
- markdown.extensions.codehilite:
guess_lang: false
-- markdown_include.include:
+- mdx_include:
base_path: docs
- admonition
- codehilite
@@ -65,16 +68,20 @@ markdown_extensions:
custom_fences:
- name: mermaid
class: mermaid
- format: !!python/name:pymdownx.superfences.fence_div_format ''
-- pymdownx.tabbed
+ format: !!python/name:pymdownx.superfences.fence_code_format ''
+- pymdownx.tabbed:
+ alternate_style: true
extra:
+ analytics:
+ provider: google
+ property: UA-133183413-1
social:
- icon: fontawesome/brands/github-alt
link: https://github.com/tiangolo/fastapi
- icon: fontawesome/brands/discord
link: https://discord.gg/VQjSZaeJmf
- icon: fontawesome/brands/twitter
- link: https://twitter.com/tiangolo
+ link: https://twitter.com/fastapi
- icon: fontawesome/brands/linkedin
link: https://www.linkedin.com/in/tiangolo
- icon: fontawesome/brands/dev
@@ -86,16 +93,26 @@ extra:
alternate:
- link: /
name: en - English
+ - link: /az/
+ name: az
+ - link: /de/
+ name: de
- link: /es/
name: es - español
+ - link: /fa/
+ name: fa
- link: /fr/
name: fr - français
+ - link: /id/
+ name: id
- link: /it/
name: it - italiano
- link: /ja/
name: ja - 日本語
- link: /ko/
name: ko - 한국어
+ - link: /nl/
+ name: nl
- link: /pl/
name: pl
- link: /pt/
@@ -114,6 +131,5 @@ extra_css:
- https://fastapi.tiangolo.com/css/termynal.css
- https://fastapi.tiangolo.com/css/custom.css
extra_javascript:
-- https://unpkg.com/mermaid@8.4.6/dist/mermaid.min.js
- https://fastapi.tiangolo.com/js/termynal.js
- https://fastapi.tiangolo.com/js/custom.js
diff --git a/docs/tr/docs/benchmarks.md b/docs/tr/docs/benchmarks.md
new file mode 100644
index 00000000..1ce3c758
--- /dev/null
+++ b/docs/tr/docs/benchmarks.md
@@ -0,0 +1,34 @@
+# Kıyaslamalar
+
+Bağımsız TechEmpower kıyaslamaları gösteriyor ki Uvicorn'la beraber çalışan **FastAPI** uygulamaları Python'un en hızlı frameworklerinden birisi , sadece Starlette ve Uvicorn'dan daha düşük sıralamada (FastAPI bu frameworklerin üzerine kurulu). (*)
+
+Fakat kıyaslamaları ve karşılaştırmaları incelerken şunları aklınızda bulundurmalısınız.
+
+## Kıyaslamalar ve hız
+
+Kıyaslamaları incelediğinizde, farklı özelliklere sahip birçok araçların eşdeğer olarak karşılaştırıldığını görmek yaygındır.
+
+Özellikle, Uvicorn, Starlette ve FastAPI'ın birlikte karşılaştırıldığını görmek için (diğer birçok araç arasında).
+
+Araç tarafından çözülen sorun ne kadar basitse, o kadar iyi performans alacaktır. Ve kıyaslamaların çoğu, araç tarafından sağlanan ek özellikleri test etmez.
+
+Hiyerarşi şöyledir:
+
+* **Uvicorn**: bir ASGI sunucusu
+ * **Starlette**: (Uvicorn'u kullanır) bir web microframeworkü
+ * **FastAPI**: (Starlette'i kullanır) data validation vb. ile API'lar oluşturmak için çeşitli ek özelliklere sahip bir API frameworkü
+
+* **Uvicorn**:
+ * Sunucunun kendisi dışında ekstra bir kod içermediği için en iyi performansa sahip olacaktır
+ * Direkt olarak Uvicorn'da bir uygulama yazmazsınız. Bu, en azından Starlette tarafından sağlanan tüm kodu (veya **FastAPI**) az çok içermesi gerektiği anlamına gelir. Ve eğer bunu yaptıysanız, son uygulamanız bir framework kullanmak ve uygulama kodlarını ve bugları en aza indirmekle aynı ek yüke sahip olacaktır.
+ * Eğer Uvicorn'u karşılaştırıyorsanız, Daphne, Hypercorn, uWSGI, vb. uygulama sunucuları ile karşılaştırın.
+* **Starlette**:
+ * Uvicorn'dan sonraki en iyi performansa sahip olacak. Aslında, Starlette çalışmak için Uvicorn'u kullanıyor. Dolayısıyla, muhtemelen daha fazla kod çalıştırmak zorunda kaldığında Uvicorn'dan sadece "daha yavaş" olabilir.
+ * Ancak routing based on paths ile vb. basit web uygulamaları oluşturmak için araçlar sağlar.
+ * Eğer Starlette'i karşılaştırıyorsanız, Sanic, Flask, Django, vb. frameworkler (veya microframeworkler) ile karşılaştırın.
+* **FastAPI**:
+ * Starlette'in Uvicorn'u kullandığı ve ondan daha hızlı olamayacağı gibi, **FastAPI** da Starlette'i kullanır, bu yüzden ondan daha hızlı olamaz.
+ * FastAPI, Starlette'e ek olarak daha fazla özellik sunar. Data validation ve serialization gibi API'lar oluştururken neredeyse ve her zaman ihtiyaç duyduğunuz özellikler. Ve bunu kullanarak, ücretsiz olarak otomatik dokümantasyon elde edersiniz (otomatik dokümantasyon çalışan uygulamalara ek yük getirmez, başlangıçta oluşturulur).
+ * FastAPI'ı kullanmadıysanız ve Starlette'i doğrudan kullandıysanız (veya başka bir araç, Sanic, Flask, Responder, vb.) tüm data validation'ı ve serialization'ı kendiniz sağlamanız gerekir. Dolayısıyla, son uygulamanız FastAPI kullanılarak oluşturulmuş gibi hâlâ aynı ek yüke sahip olacaktır. Çoğu durumda, uygulamalarda yazılan kodun büyük çoğunluğunu data validation ve serialization oluşturur.
+ * Dolayısıyla, FastAPI'ı kullanarak geliştirme süresinden, buglardan, kod satırlarından tasarruf edersiniz ve muhtemelen kullanmasaydınız aynı performansı (veya daha iyisini) elde edersiniz. (hepsini kodunuza uygulamak zorunda kalacağınız gibi)
+ * Eğer FastAPI'ı karşılaştırıyorsanız, Flask-apispec, NestJS, Molten, vb. gibi data validation, serialization ve dokümantasyon sağlayan bir web uygulaması frameworkü ile (veya araç setiyle) karşılaştırın. Entegre otomatik data validation, serialization ve dokümantasyon içeren frameworkler.
diff --git a/docs/tr/docs/fastapi-people.md b/docs/tr/docs/fastapi-people.md
new file mode 100644
index 00000000..3e459036
--- /dev/null
+++ b/docs/tr/docs/fastapi-people.md
@@ -0,0 +1,178 @@
+# FastAPI Topluluğu
+
+FastAPI, her kökenden insanı ağırlayan harika bir topluluğa sahip.
+
+## Yazan - Geliştiren
+
+Hey! 👋
+
+İşte bu benim:
+
+{% if people %}
+
- FastAPI framework, high performance, easy to learn, fast to code, ready for production + FastAPI framework, yüksek performanslı, öğrenmesi kolay, geliştirmesi hızlı, kullanıma sunulmaya hazır.
@@ -22,35 +22,38 @@
---
-**Documentation**: https://fastapi.tiangolo.com
+**dokümantasyon**: https://fastapi.tiangolo.com
-**Source Code**: https://github.com/tiangolo/fastapi
+**Kaynak kodu**: https://github.com/tiangolo/fastapi
---
-FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints.
+FastAPI, Python 3.6+'nın standart type hintlerine dayanan modern ve hızlı (yüksek performanslı) API'lar oluşturmak için kullanılabilecek web framework'ü.
-The key features are:
+Ana özellikleri:
-* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance).
+* **Hızlı**: çok yüksek performanslı, **NodeJS** ve **Go** ile eşdeğer seviyede performans sağlıyor, (Starlette ve Pydantic sayesinde.) [Python'un en hızlı frameworklerinden bir tanesi.](#performans).
+* **Kodlaması hızlı**: Yeni özellikler geliştirmek neredeyse %200 - %300 daha hızlı. *
+* **Daha az bug**: Geliştirici (insan) kaynaklı hatalar neredeyse %40 azaltıldı. *
+* **Sezgileri güçlü**: Editor (otomatik-tamamlama) desteği harika. Otomatik tamamlama her yerde. Debuglamak ile daha az zaman harcayacaksınız.
+* **Kolay**: Öğrenmesi ve kullanması kolay olacak şekilde. Doküman okumak için harcayacağınız süre azaltıldı.
+* **Kısa**: Kod tekrarını minimuma indirdik. Fonksiyon parametrelerinin tiplerini belirtmede farklı yollar sunarak karşılaşacağınız bug'ları azalttık.
+* **Güçlü**: Otomatik dokümantasyon ile beraber, kullanıma hazır kod yaz.
-* **Fast to code**: Increase the speed to develop features by about 200% to 300%. *
-* **Fewer bugs**: Reduce about 40% of human (developer) induced errors. *
-* **Intuitive**: Great editor support. Completion everywhere. Less time debugging.
-* **Easy**: Designed to be easy to use and learn. Less time reading docs.
-* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs.
-* **Robust**: Get production-ready code. With automatic interactive documentation.
-* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: OpenAPI (previously known as Swagger) and JSON Schema.
+* **Standartlar belirli**: Tamamiyle API'ların açık standartlara bağlı ve (tam uyumlululuk içerisinde); OpenAPI (eski adıyla Swagger) ve JSON Schema.
-* estimation based on tests on an internal development team, building production applications.
+* Bahsi geçen rakamsal ifadeler tamamiyle, geliştirme takımının kendi sundukları ürünü geliştirirken yaptıkları testlere dayanmakta.
-## Gold Sponsors
+## Sponsors
{% if sponsors %}
{% for sponsor in sponsors.gold -%}
-
+
+{% endfor -%}
+{%- for sponsor in sponsors.silver -%}
+
{% endfor %}
{% endif %}
@@ -58,64 +61,72 @@ The key features are:
Other sponsors
-## Opinions
+## Görüşler
-"_[...] I'm using **FastAPI** a ton these days. [...] I'm actually planning to use it for all of my team's **ML services at Microsoft**. Some of them are getting integrated into the core **Windows** product and some **Office** products._"
+
+"_[...] Bugünlerde **FastAPI**'ı çok fazla kullanıyorum [...] Aslına bakarsanız **Microsoft'taki Machine Learning servislerimizin** hepsinde kullanmayı düşünüyorum. FastAPI ile geliştirdiğimiz servislerin bazıları çoktan **Windows**'un ana ürünlerine ve **Office** ürünlerine entegre edilmeye başlandı bile._"
async def...async def...uvicorn main:app --reload...uvicorn main:app --reload hakkında...ujson - for faster JSON "parsing".
-* email_validator - for email validation.
+* ujson - daha hızlı JSON "dönüşümü" için.
+* email_validator - email doğrulaması için.
-Used by Starlette:
+Starlette tarafında kullanılan:
-* requests - Required if you want to use the `TestClient`.
-* aiofiles - Required if you want to use `FileResponse` or `StaticFiles`.
-* jinja2 - Required if you want to use the default template configuration.
-* python-multipart - Required if you want to support form "parsing", with `request.form()`.
-* itsdangerous - Required for `SessionMiddleware` support.
-* pyyaml - Required for Starlette's `SchemaGenerator` support (you probably don't need it with FastAPI).
-* graphene - Required for `GraphQLApp` support.
-* ujson - Required if you want to use `UJSONResponse`.
+* requests - Eğer `TestClient` kullanmak istiyorsan gerekli.
+* jinja2 - Eğer kendine ait template konfigürasyonu oluşturmak istiyorsan gerekli
+* python-multipart - Form kullanmak istiyorsan gerekli ("dönüşümü").
+* itsdangerous - `SessionMiddleware` desteği için gerekli.
+* pyyaml - `SchemaGenerator` desteği için gerekli (Muhtemelen FastAPI kullanırken ihtiyacınız olmaz).
+* graphene - `GraphQLApp` desteği için gerekli.
+* ujson - `UJSONResponse` kullanmak istiyorsan gerekli.
-Used by FastAPI / Starlette:
+Hem FastAPI hem de Starlette tarafından kullanılan:
-* uvicorn - for the server that loads and serves your application.
-* orjson - Required if you want to use `ORJSONResponse`.
+* uvicorn - oluşturduğumuz uygulamayı bir web sunucusuna servis etmek için gerekli
+* orjson - `ORJSONResponse` kullanmak istiyor isen gerekli.
-You can install all of these with `pip install fastapi[all]`.
+Bunların hepsini `pip install fastapi[all]` ile yükleyebilirsin.
-## License
+## Lisans
-This project is licensed under the terms of the MIT license.
+Bu proje, MIT lisansı şartlarına göre lisanslanmıştır.
diff --git a/docs/tr/docs/python-types.md b/docs/tr/docs/python-types.md
new file mode 100644
index 00000000..7e46bd03
--- /dev/null
+++ b/docs/tr/docs/python-types.md
@@ -0,0 +1,314 @@
+# Python Veri Tiplerine Giriş
+
+Python isteğe bağlı olarak "tip belirteçlerini" destekler.
+
+ **"Tip belirteçleri"** bir değişkenin tipinin belirtilmesine olanak sağlayan özel bir sözdizimidir.
+
+Değişkenlerin tiplerini belirterek editör ve araçlardan daha fazla destek alabilirsiniz.
+
+Bu pythonda tip belirteçleri için **hızlı bir başlangıç / bilgi tazeleme** rehberidir . Bu rehber **FastAPI** kullanmak için gereken minimum konuyu kapsar ki bu da çok az bir miktardır.
+
+**FastAPI' nin** tamamı bu tür tip belirteçleri ile donatılmıştır ve birçok avantaj sağlamaktadır.
+
+**FastAPI** kullanmayacak olsanız bile tür belirteçleri hakkında bilgi edinmenizde fayda var.
+
+!!! not
+ Python uzmanıysanız ve tip belirteçleri ilgili her şeyi zaten biliyorsanız, sonraki bölüme geçin.
+
+## Motivasyon
+
+Basit bir örnek ile başlayalım:
+
+```Python
+{!../../../docs_src/python_types/tutorial001.py!}
+```
+
+Programın çıktısı:
+
+```
+John Doe
+```
+
+Fonksiyon sırayla şunları yapar:
+
+* `first_name` ve `last_name` değerlerini alır.
+* `title()` ile değişkenlerin ilk karakterlerini büyütür.
+* Değişkenleri aralarında bir boşlukla beraber Birleştirir.
+
+```Python hl_lines="2"
+{!../../../docs_src/python_types/tutorial001.py!}
+```
+
+### Düzenle
+
+Bu çok basit bir program.
+
+Ama şimdi sıfırdan yazdığınızı hayal edin.
+
+Bir noktada fonksiyonun tanımına başlayacaktınız, parametreleri hazır hale getirdiniz...
+
+Ama sonra "ilk harfi büyük harfe dönüştüren yöntemi" çağırmanız gerekir.
+
+ `upper` mıydı ? Yoksa `uppercase`' mi? `first_uppercase`? `capitalize`?
+
+Ardından, programcıların en iyi arkadaşı olan otomatik tamamlama ile denediniz.
+
+'first_name', ardından bir nokta ('.') yazıp otomatik tamamlamayı tetiklemek için 'Ctrl+Space' tuşlarına bastınız.
+
+Ancak, ne yazık ki, yararlı hiçbir şey elde edemediniz:
+
+
+
+### Tipleri ekle
+
+Önceki sürümden sadece bir satırı değiştirelim.
+
+Tam olarak bu parçayı, işlevin parametrelerini değiştireceğiz:
+
+```Python
+ first_name, last_name
+```
+
+ve bu hale getireceğiz:
+
+```Python
+ first_name: str, last_name: str
+```
+
+Bu kadar.
+
+İşte bunlar "tip belirteçleri":
+
+```Python hl_lines="1"
+{!../../../docs_src/python_types/tutorial002.py!}
+```
+
+Bu, aşağıdaki gibi varsayılan değerleri bildirmekle aynı şey değildir:
+
+```Python
+ first_name="john", last_name="doe"
+```
+
+Bu tamamen farklı birşey
+
+İki nokta üst üste (`:`) kullanıyoruz , eşittir (`=`) değil.
+
+Normalde tip belirteçleri eklemek, kod üzerinde olacakları değiştirmez.
+
+Şimdi programı sıfırdan birdaha yazdığınızı hayal edin.
+
+Aynı noktada, `Ctrl+Space` ile otomatik tamamlamayı tetiklediniz ve şunu görüyorsunuz:
+
+
+
+Aradığınızı bulana kadar seçenekleri kaydırabilirsiniz:
+
+
+
+## Daha fazla motivasyon
+
+Bu fonksiyon, zaten tür belirteçlerine sahip:
+
+```Python hl_lines="1"
+{!../../../docs_src/python_types/tutorial003.py!}
+```
+
+Editör değişkenlerin tiplerini bildiğinden, yalnızca otomatik tamamlama değil, hata kontrolleri de sağlar:
+
+
+
+Artık `age` değişkenini `str(age)` olarak kullanmanız gerektiğini biliyorsunuz:
+
+```Python hl_lines="2"
+{!../../../docs_src/python_types/tutorial004.py!}
+```
+
+## Tip bildirme
+
+Az önce tip belirteçlerinin en çok kullanıldığı yeri gördünüz.
+
+ **FastAPI**ile çalışırken tip belirteçlerini en çok kullanacağımız yer yine fonksiyonlardır.
+
+### Basit tipler
+
+Yalnızca `str` değil, tüm standart Python tiplerinin bildirebilirsiniz.
+
+Örneğin şunları kullanabilirsiniz:
+
+* `int`
+* `float`
+* `bool`
+* `bytes`
+
+```Python hl_lines="1"
+{!../../../docs_src/python_types/tutorial005.py!}
+```
+
+### Tip parametreleri ile Generic tipler
+
+"dict", "list", "set" ve "tuple" gibi diğer değerleri içerebilen bazı veri yapıları vardır. Ve dahili değerlerinin de tip belirtecleri olabilir.
+
+Bu tipleri ve dahili tpileri bildirmek için standart Python modülünü "typing" kullanabilirsiniz.
+
+Bu tür tip belirteçlerini desteklemek için özel olarak mevcuttur.
+
+#### `List`
+
+Örneğin `str` değerlerden oluşan bir `list` tanımlayalım.
+
+From `typing`, import `List` (büyük harf olan `L` ile):
+
+```Python hl_lines="1"
+{!../../../docs_src/python_types/tutorial006.py!}
+```
+
+Değişkenin tipini yine iki nokta üstüste (`:`) ile belirleyin.
+
+tip olarak `List` kullanın.
+
+Liste, bazı dahili tipleri içeren bir tür olduğundan, bunları köşeli parantez içine alırsınız:
+
+```Python hl_lines="4"
+{!../../../docs_src/python_types/tutorial006.py!}
+```
+
+!!! ipucu
+ Köşeli parantez içindeki bu dahili tiplere "tip parametreleri" denir.
+
+ Bu durumda `str`, `List`e iletilen tür parametresidir.
+
+Bunun anlamı şudur: "`items` değişkeni bir `list`tir ve bu listedeki öğelerin her biri bir `str`dir".
+
+Bunu yaparak, düzenleyicinizin listedeki öğeleri işlerken bile destek sağlamasını sağlayabilirsiniz:
+
+
+
+Tip belirteçleri olmadan, bunu başarmak neredeyse imkansızdır.
+
+`item` değişkeninin `items` listesindeki öğelerden biri olduğuna dikkat edin.
+
+Ve yine, editör bunun bir `str` olduğunu biliyor ve bunun için destek sağlıyor.
+
+#### `Tuple` ve `Set`
+
+`Tuple` ve `set`lerin tiplerini bildirmek için de aynısını yapıyoruz:
+
+```Python hl_lines="1 4"
+{!../../../docs_src/python_types/tutorial007.py!}
+```
+
+Bu şu anlama geliyor:
+
+* `items_t` değişkeni sırasıyla `int`, `int`, ve `str` tiplerinden oluşan bir `tuple` türündedir .
+* `items_s` ise her öğesi `bytes` türünde olan bir `set` örneğidir.
+
+#### `Dict`
+
+Bir `dict` tanımlamak için virgülle ayrılmış iki parametre verebilirsiniz.
+
+İlk tip parametresi `dict` değerinin `key` değeri içindir.
+
+İkinci parametre ise `dict` değerinin `value` değeri içindir:
+
+```Python hl_lines="1 4"
+{!../../../docs_src/python_types/tutorial008.py!}
+```
+
+Bu şu anlama gelir:
+
+* `prices` değişkeni `dict` tipindedir:
+ * `dict` değişkeninin `key` değeri `str` tipindedir (herbir item'ın "name" değeri).
+ * `dict` değişkeninin `value` değeri `float` tipindedir (lherbir item'ın "price" değeri).
+
+#### `Optional`
+
+`Optional` bir değişkenin `str`gibi bir tipi olabileceğini ama isteğe bağlı olarak tipinin `None` olabileceğini belirtir:
+
+```Python hl_lines="1 4"
+{!../../../docs_src/python_types/tutorial009.py!}
+```
+
+`str` yerine `Optional[str]` kullanmak editorün bu değerin her zaman `str` tipinde değil bazen `None` tipinde de olabileceğini belirtir ve hataları tespit etmemizde yardımcı olur.
+
+#### Generic tipler
+
+Köşeli parantez içinde tip parametreleri alan bu türler, örneğin:
+
+* `List`
+* `Tuple`
+* `Set`
+* `Dict`
+* `Optional`
+* ...and others.
+
+**Generic types** yada **Generics** olarak adlandırılır.
+
+### Tip olarak Sınıflar
+
+Bir değişkenin tipini bir sınıf ile bildirebilirsiniz.
+
+Diyelim ki `name` değerine sahip `Person` sınıfınız var:
+
+```Python hl_lines="1-3"
+{!../../../docs_src/python_types/tutorial010.py!}
+```
+
+Sonra bir değişkeni 'Person' tipinde tanımlayabilirsiniz:
+
+```Python hl_lines="6"
+{!../../../docs_src/python_types/tutorial010.py!}
+```
+
+Ve yine bütün editör desteğini alırsınız:
+
+
+
+## Pydantic modelleri
+
+Pydantic veri doğrulaması yapmak için bir Python kütüphanesidir.
+
+Verilerin "biçimini" niteliklere sahip sınıflar olarak düzenlersiniz.
+
+Ve her niteliğin bir türü vardır.
+
+Sınıfın bazı değerlerle bir örneğini oluşturursunuz ve değerleri doğrular, bunları uygun türe dönüştürür ve size tüm verileri içeren bir nesne verir.
+
+Ve ortaya çıkan nesne üzerindeki bütün editör desteğini alırsınız.
+
+Resmi Pydantic dokümanlarından alınmıştır:
+
+```Python
+{!../../../docs_src/python_types/tutorial011.py!}
+```
+
+!!! info
+ Daha fazla şey öğrenmek için Pydantic'i takip edin.
+
+**FastAPI** tamamen Pydantic'e dayanmaktadır.
+
+Daha fazlasini görmek için [Tutorial - User Guide](tutorial/index.md){.internal-link target=_blank}.
+
+## **FastAPI** tip belirteçleri
+
+**FastAPI** birkaç şey yapmak için bu tür tip belirteçlerinden faydalanır.
+
+**FastAPI** ile parametre tiplerini bildirirsiniz ve şunları elde edersiniz:
+
+* **Editor desteği**.
+* **Tip kontrolü**.
+
+...ve **FastAPI** aynı belirteçleri şunlar için de kullanıyor:
+
+* **Gereksinimleri tanımlama**: request path parameters, query parameters, headers, bodies, dependencies, ve benzeri gereksinimlerden
+* **Verileri çevirme**: Gönderilen veri tipinden istenilen veri tipine çevirme.
+* **Verileri doğrulama**: Her gönderilen verinin:
+ * doğrulanması ve geçersiz olduğunda **otomatik hata** oluşturma.
+* OpenAPI kullanarak apinizi **Belgeleyin** :
+ * bu daha sonra otomatik etkileşimli dokümantasyon kullanıcı arayüzü tarafından kullanılır.
+
+Bütün bunlar kulağa soyut gelebilir. Merak etme. Tüm bunları çalışırken göreceksiniz. [Tutorial - User Guide](tutorial/index.md){.internal-link target=_blank}.
+
+Önemli olan, standart Python türlerini tek bir yerde kullanarak (daha fazla sınıf, dekoratör vb. eklemek yerine), **FastAPI**'nin bizim için işi yapmasını sağlamak.
+
+!!! info
+ Tüm öğreticiyi zaten okuduysanız ve türler hakkında daha fazla bilgi için geri döndüyseniz, iyi bir kaynak: the "cheat sheet" from `mypy`.
diff --git a/docs/tr/mkdocs.yml b/docs/tr/mkdocs.yml
index b6508844..dd52d7fc 100644
--- a/docs/tr/mkdocs.yml
+++ b/docs/tr/mkdocs.yml
@@ -9,17 +9,18 @@ theme:
primary: teal
accent: amber
toggle:
- icon: material/lightbulb-outline
+ icon: material/lightbulb
name: Switch to light mode
- scheme: slate
primary: teal
accent: amber
toggle:
- icon: material/lightbulb
+ icon: material/lightbulb-outline
name: Switch to dark mode
features:
- search.suggest
- search.highlight
+ - content.tabs.link
icon:
repo: fontawesome/brands/github-alt
logo: https://fastapi.tiangolo.com/img/icon-white.svg
@@ -28,9 +29,6 @@ theme:
repo_name: tiangolo/fastapi
repo_url: https://github.com/tiangolo/fastapi
edit_uri: ''
-google_analytics:
-- UA-133183413-1
-- auto
plugins:
- search
- markdownextradata:
@@ -39,11 +37,16 @@ nav:
- FastAPI: index.md
- Languages:
- en: /
+ - az: /az/
+ - de: /de/
- es: /es/
+ - fa: /fa/
- fr: /fr/
+ - id: /id/
- it: /it/
- ja: /ja/
- ko: /ko/
+ - nl: /nl/
- pl: /pl/
- pt: /pt/
- ru: /ru/
@@ -51,12 +54,15 @@ nav:
- tr: /tr/
- uk: /uk/
- zh: /zh/
+- features.md
+- fastapi-people.md
+- python-types.md
markdown_extensions:
- toc:
permalink: true
- markdown.extensions.codehilite:
guess_lang: false
-- markdown_include.include:
+- mdx_include:
base_path: docs
- admonition
- codehilite
@@ -65,16 +71,20 @@ markdown_extensions:
custom_fences:
- name: mermaid
class: mermaid
- format: !!python/name:pymdownx.superfences.fence_div_format ''
-- pymdownx.tabbed
+ format: !!python/name:pymdownx.superfences.fence_code_format ''
+- pymdownx.tabbed:
+ alternate_style: true
extra:
+ analytics:
+ provider: google
+ property: UA-133183413-1
social:
- icon: fontawesome/brands/github-alt
link: https://github.com/tiangolo/fastapi
- icon: fontawesome/brands/discord
link: https://discord.gg/VQjSZaeJmf
- icon: fontawesome/brands/twitter
- link: https://twitter.com/tiangolo
+ link: https://twitter.com/fastapi
- icon: fontawesome/brands/linkedin
link: https://www.linkedin.com/in/tiangolo
- icon: fontawesome/brands/dev
@@ -86,16 +96,26 @@ extra:
alternate:
- link: /
name: en - English
+ - link: /az/
+ name: az
+ - link: /de/
+ name: de
- link: /es/
name: es - español
+ - link: /fa/
+ name: fa
- link: /fr/
name: fr - français
+ - link: /id/
+ name: id
- link: /it/
name: it - italiano
- link: /ja/
name: ja - 日本語
- link: /ko/
name: ko - 한국어
+ - link: /nl/
+ name: nl
- link: /pl/
name: pl
- link: /pt/
@@ -114,6 +134,5 @@ extra_css:
- https://fastapi.tiangolo.com/css/termynal.css
- https://fastapi.tiangolo.com/css/custom.css
extra_javascript:
-- https://unpkg.com/mermaid@8.4.6/dist/mermaid.min.js
- https://fastapi.tiangolo.com/js/termynal.js
- https://fastapi.tiangolo.com/js/custom.js
diff --git a/docs/uk/docs/index.md b/docs/uk/docs/index.md
index edc19fa4..a7af1478 100644
--- a/docs/uk/docs/index.md
+++ b/docs/uk/docs/index.md
@@ -44,13 +44,16 @@ The key features are:
* estimation based on tests on an internal development team, building production applications.
-## Gold Sponsors
+## Sponsors
{% if sponsors %}
{% for sponsor in sponsors.gold -%}
-requests - Required if you want to use the `TestClient`.
-* aiofiles - Required if you want to use `FileResponse` or `StaticFiles`.
* jinja2 - Required if you want to use the default template configuration.
* python-multipart - Required if you want to support form "parsing", with `request.form()`.
* itsdangerous - Required for `SessionMiddleware` support.
diff --git a/docs/uk/mkdocs.yml b/docs/uk/mkdocs.yml
index cecd2444..971a182d 100644
--- a/docs/uk/mkdocs.yml
+++ b/docs/uk/mkdocs.yml
@@ -9,17 +9,18 @@ theme:
primary: teal
accent: amber
toggle:
- icon: material/lightbulb-outline
+ icon: material/lightbulb
name: Switch to light mode
- scheme: slate
primary: teal
accent: amber
toggle:
- icon: material/lightbulb
+ icon: material/lightbulb-outline
name: Switch to dark mode
features:
- search.suggest
- search.highlight
+ - content.tabs.link
icon:
repo: fontawesome/brands/github-alt
logo: https://fastapi.tiangolo.com/img/icon-white.svg
@@ -28,9 +29,6 @@ theme:
repo_name: tiangolo/fastapi
repo_url: https://github.com/tiangolo/fastapi
edit_uri: ''
-google_analytics:
-- UA-133183413-1
-- auto
plugins:
- search
- markdownextradata:
@@ -39,11 +37,16 @@ nav:
- FastAPI: index.md
- Languages:
- en: /
+ - az: /az/
+ - de: /de/
- es: /es/
+ - fa: /fa/
- fr: /fr/
+ - id: /id/
- it: /it/
- ja: /ja/
- ko: /ko/
+ - nl: /nl/
- pl: /pl/
- pt: /pt/
- ru: /ru/
@@ -56,7 +59,7 @@ markdown_extensions:
permalink: true
- markdown.extensions.codehilite:
guess_lang: false
-- markdown_include.include:
+- mdx_include:
base_path: docs
- admonition
- codehilite
@@ -65,16 +68,20 @@ markdown_extensions:
custom_fences:
- name: mermaid
class: mermaid
- format: !!python/name:pymdownx.superfences.fence_div_format ''
-- pymdownx.tabbed
+ format: !!python/name:pymdownx.superfences.fence_code_format ''
+- pymdownx.tabbed:
+ alternate_style: true
extra:
+ analytics:
+ provider: google
+ property: UA-133183413-1
social:
- icon: fontawesome/brands/github-alt
link: https://github.com/tiangolo/fastapi
- icon: fontawesome/brands/discord
link: https://discord.gg/VQjSZaeJmf
- icon: fontawesome/brands/twitter
- link: https://twitter.com/tiangolo
+ link: https://twitter.com/fastapi
- icon: fontawesome/brands/linkedin
link: https://www.linkedin.com/in/tiangolo
- icon: fontawesome/brands/dev
@@ -86,16 +93,26 @@ extra:
alternate:
- link: /
name: en - English
+ - link: /az/
+ name: az
+ - link: /de/
+ name: de
- link: /es/
name: es - español
+ - link: /fa/
+ name: fa
- link: /fr/
name: fr - français
+ - link: /id/
+ name: id
- link: /it/
name: it - italiano
- link: /ja/
name: ja - 日本語
- link: /ko/
name: ko - 한국어
+ - link: /nl/
+ name: nl
- link: /pl/
name: pl
- link: /pt/
@@ -114,6 +131,5 @@ extra_css:
- https://fastapi.tiangolo.com/css/termynal.css
- https://fastapi.tiangolo.com/css/custom.css
extra_javascript:
-- https://unpkg.com/mermaid@8.4.6/dist/mermaid.min.js
- https://fastapi.tiangolo.com/js/termynal.js
- https://fastapi.tiangolo.com/js/custom.js
diff --git a/docs/zh/docs/advanced/custom-response.md b/docs/zh/docs/advanced/custom-response.md
index b67ef6c2..5f1a74e9 100644
--- a/docs/zh/docs/advanced/custom-response.md
+++ b/docs/zh/docs/advanced/custom-response.md
@@ -183,7 +183,7 @@ FastAPI(实际上是 Starlette)将自动包含 Content-Length 的头。它
包括许多与云存储,视频处理等交互的库。
-```Python hl_lines="2 10 11"
+```Python hl_lines="2 10-12 14"
{!../../../docs_src/custom_response/tutorial008.py!}
```
diff --git a/docs/zh/docs/deployment.md b/docs/zh/docs/deployment.md
deleted file mode 100644
index 4dac57be..00000000
--- a/docs/zh/docs/deployment.md
+++ /dev/null
@@ -1,392 +0,0 @@
-# 部署
-
-部署 **FastAPI** 应用相对比较简单。
-
-根据特定使用情况和使用工具有几种不同的部署方式。
-
-接下来的章节,你将了解到一些关于部署方式的内容。
-
-## FastAPI 版本
-
-许多应用和系统已经在生产环境使用 **FastAPI**。其测试覆盖率保持在 100%。但该项目仍在快速开发。
-
-我们会经常加入新的功能,定期错误修复,同时也在不断的优化项目代码。
-
-这也是为什么当前版本仍然是 `0.x.x`,我们以此表明每个版本都可能有重大改变。
-
-现在就可以使用 **FastAPI** 创建生产应用(你可能已经使用一段时间了)。你只需要确保使用的版本和代码其他部分能够正常兼容。
-
-### 指定你的 `FastAPI` 版本
-
-你应该做的第一件事情,是为你正在使用的 **FastAPI** 指定一个能够正确运行你的应用的最新版本。
-
-例如,假设你的应用中正在使用版本 `0.45.0`。
-
-如果你使用 `requirements.txt` 文件,你可以这样指定版本:
-
-```txt
-fastapi==0.45.0
-```
-
-这表明你将使用 `0.45.0` 版本的 `FastAPI`。
-
-或者你也可以这样指定:
-
-```txt
-fastapi>=0.45.0,<0.46.0
-```
-
-这表明你将使用 `0.45.0` 及以上,但低于 `0.46.0` 的版本,例如,`0.45.2` 依然可以接受。
-
-如果使用其他工具管理你的安装,比如 Poetry,Pipenv,或者其他工具,它们都有各自指定包的版本的方式。
-
-### 可用版本
-
-你可以在 [发行说明](release-notes.md){.internal-link target=_blank} 中查看可用的版本(比如:检查最新版本是什么)。
-
-
-### 关于版本
-
-FastAPI 遵循语义版本控制约定,`1.0.0` 以下的任何版本都可能加入重大变更。
-
-FastAPI 也遵循这样的约定:任何 ”PATCH“ 版本变更都是用来修复 bug 和向下兼容的变更。
-
-!!! tip
- "PATCH" 是指版本号的最后一个数字,例如,在 `0.2.3` 中,PATCH 版本是 `3`。
-
-所以,你应该像这样指定版本:
-
-```txt
-fastapi>=0.45.0,<0.46.0
-```
-
-不兼容变更和新特性在 "MINOR" 版本中添加。
-
-!!! tip
- "MINOR" 是版本号中间的数字,例如,在 `0.2.3` 中,MINOR 版本是 `2`。
-
-### 更新 FaseAPI 版本
-
-你应该为你的应用添加测试。
-
-使用 **FastAPI** 测试应用非常容易(归功于 Starlette),查看文档:[测试](tutorial/testing.md){.internal-link target=_blank}
-
-有了测试之后,就可以将 **FastAPI** 更新到最近的一个的版本,然后通过运行测试来确定你所有代码都可以正确工作。
-
-如果一切正常,或者做了必要的修改之后,所有的测试都通过了,就可以把 `FastAPI` 版本指定为那个比较新的版本了。
-
-### 关于 Starlette
-
-不要指定 `starlette` 的版本。
-
-不同版本的 **FastAPI** 会使用特定版本的 Starlette。
-
-所以你只要让 **FastAPI** 自行选择正确的 Starlette 版本。
-
-### 关于 Pydantic
-
-Pydantic 自身的测试中已经包含了 **FastAPI** 的测试,所以最新版本的 Pydantic (`1.0.0` 以上版本)总是兼容 **FastAPI**。
-
-你可以指定 Pydantic 为任意一个高于 `1.0.0` 且低于的 `2.0.0` 的版本。
-
-例如:
-
-```txt
-pydantic>=1.2.0,<2.0.0
-```
-
-## Docker
-
-这部分,你将通过指引和链接了解到:
-
-* 如何将你的 **FastAPI** 应用制作成最高性能的 **Docker** 映像/容器。约需五分钟。
-* (可选)理解作为一个开发者需要知道的 HTTPS 相关知识。
-* 使用自动化 HTTPS 设置一个 Docker Swarm 模式的集群,即使是在一个简单的 $5 USD/month 的服务器上。约需要 20 分钟。
-* 使用 Docker Swarm 集群以及 HTTP 等等,生成和部署一个完整的 **FastAPI** 应用。约需 10 分钟。
-
-可以使用 **Docker** 进行部署。它具有安全性、可复制性、开发简单性等优点。
-
-如果你正在使用 Docker,你可以使用官方 Docker 镜像:
-
-### tiangolo/uvicorn-gunicorn-fastapi
-
-该映像包含一个「自动调优」的机制,这样就可以仅仅添加代码就能自动获得超高性能,而不用做出牺牲。
-
-不过你仍然可以使用环境变量或配置文件更改和更新所有配置。
-
-!!! tip
- 查看全部配置和选项,请移步 Docker 镜像页面:tiangolo/uvicorn-gunicorn-fastapi。
-
-### 创建 `Dockerfile`
-
-* 进入你的项目目录。
-* 使用如下命令创建一个 `Dockerfile`:
-
-```Dockerfile
-FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7
-
-COPY ./app /app
-```
-
-#### 大型应用
-
-如果遵循创建 [多文件大型应用](tutorial/bigger-applications.md){.internal-link target=_blank} 的章节,你的 Dockerfile 可能看起来是这样:
-
-```Dockerfile
-FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7
-
-COPY ./app /app/app
-```
-
-#### 树莓派以及其他架构
-
-如果你在树莓派或者任何其他架构中运行 Docker,可以基于 Python 基础镜像(它是多架构的)从头创建一个 `Dockerfile` 并单独使用 Uvicorn。
-
-这种情况下,你的 `Dockerfile` 可能是这样的:
-
-```Dockerfile
-FROM python:3.7
-
-RUN pip install fastapi uvicorn
-
-EXPOSE 80
-
-COPY ./app /app
-
-CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
-```
-
-### 创建 **FastAPI** 代码
-
-* 创建一个 `app` 目录并进入该目录。
-* 创建一个 `main.py` 文件,内容如下:
-
-```Python
-from typing import Optional
-
-from fastapi import FastAPI
-
-app = FastAPI()
-
-
-@app.get("/")
-def read_root():
- return {"Hello": "World"}
-
-
-@app.get("/items/{item_id}")
-def read_item(item_id: int, q: Optional[str] = None):
- return {"item_id": item_id, "q": q}
-```
-
-* 现在目录结构如下:
-
-```
-.
-├── app
-│ └── main.py
-└── Dockerfile
-```
-
-### 构建 Docker 镜像
-
-* 进入项目目录(在 `Dockerfile` 所在的位置,包含 `app` 目录)
-* 构建 **FastAPI** 镜像
-
-requests - 使用 `TestClient` 时安装。
-* aiofiles - 使用 `FileResponse` 或 `StaticFiles` 时安装。
* jinja2 - 使用默认模板配置时安装。
* python-multipart - 需要通过 `request.form()` 对表单进行「解析」时安装。
* itsdangerous - 需要 `SessionMiddleware` 支持时安装。
diff --git a/docs/zh/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md b/docs/zh/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md
new file mode 100644
index 00000000..e36e33c4
--- /dev/null
+++ b/docs/zh/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md
@@ -0,0 +1,73 @@
+# 路径操作装饰器依赖项
+
+有时,我们并不需要在*路径操作函数*中使用依赖项的返回值。
+
+或者说,有些依赖项不返回值。
+
+但仍要执行或解析该依赖项。
+
+对于这种情况,不必在声明*路径操作函数*的参数时使用 `Depends`,而是可以在*路径操作装饰器*中添加一个由 `dependencies` 组成的 `list`。
+
+## 在*路径操作装饰器*中添加 `dependencies` 参数
+
+*路径操作装饰器*支持可选参数 ~ `dependencies`。
+
+该参数的值是由 `Depends()` 组成的 `list`:
+
+```Python hl_lines="17"
+{!../../../docs_src/dependencies/tutorial006.py!}
+```
+
+路径操作装饰器依赖项(以下简称为**“路径装饰器依赖项”**)的执行或解析方式和普通依赖项一样,但就算这些依赖项会返回值,它们的值也不会传递给*路径操作函数*。
+
+!!! tip "提示"
+
+ 有些编辑器会检查代码中没使用过的函数参数,并显示错误提示。
+
+ 在*路径操作装饰器*中使用 `dependencies` 参数,可以确保在执行依赖项的同时,避免编辑器显示错误提示。
+
+ 使用路径装饰器依赖项还可以避免开发新人误会代码中包含无用的未使用参数。
+
+!!! info "说明"
+
+ 本例中,使用的是自定义响应头 `X-Key` 和 `X-Token`。
+
+ 但实际开发中,尤其是在实现安全措施时,最好使用 FastAPI 内置的[安全工具](../security/index.md){.internal-link target=_blank}(详见下一章)。
+
+## 依赖项错误和返回值
+
+路径装饰器依赖项也可以使用普通的依赖项*函数*。
+
+### 依赖项的需求项
+
+路径装饰器依赖项可以声明请求的需求项(比如响应头)或其他子依赖项:
+
+```Python hl_lines="6 11"
+{!../../../docs_src/dependencies/tutorial006.py!}
+```
+
+### 触发异常
+
+路径装饰器依赖项与正常的依赖项一样,可以 `raise` 异常:
+
+```Python hl_lines="8 13"
+{!../../../docs_src/dependencies/tutorial006.py!}
+```
+
+### 返回值
+
+无论路径装饰器依赖项是否返回值,路径操作都不会使用这些值。
+
+因此,可以复用在其他位置使用过的、(能返回值的)普通依赖项,即使没有使用这个值,也会执行该依赖项:
+
+```Python hl_lines="9 14"
+{!../../../docs_src/dependencies/tutorial006.py!}
+```
+
+## 为一组路径操作定义依赖项
+
+稍后,[大型应用 - 多文件](../../tutorial/bigger-applications.md){.internal-link target=\_blank}一章中会介绍如何使用多个文件创建大型应用程序,在这一章中,您将了解到如何为一组*路径操作*声明单个 `dependencies` 参数。
+
+## 全局依赖项
+
+接下来,我们将学习如何为 `FastAPI` 应用程序添加全局依赖项,创建应用于每个*路径操作*的依赖项。
diff --git a/docs/zh/docs/tutorial/dependencies/global-dependencies.md b/docs/zh/docs/tutorial/dependencies/global-dependencies.md
new file mode 100644
index 00000000..5c367ff0
--- /dev/null
+++ b/docs/zh/docs/tutorial/dependencies/global-dependencies.md
@@ -0,0 +1,18 @@
+# 全局依赖项
+
+有时,我们要为整个应用添加依赖项。
+
+通过与定义[*路径装饰器依赖项*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} 类似的方式,可以把依赖项添加至整个 `FastAPI` 应用。
+
+这样一来,就可以为所有*路径操作*应用该依赖项:
+
+```Python hl_lines="15"
+{!../../../docs_src/dependencies/tutorial012.py!}
+```
+
+[*路径装饰器依赖项*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} 一章的思路均适用于全局依赖项, 在本例中,这些依赖项可以用于应用中的所有*路径操作*。
+
+## 为一组路径操作定义依赖项
+
+稍后,[大型应用 - 多文件](../../tutorial/bigger-applications.md){.internal-link target=_blank}一章中会介绍如何使用多个文件创建大型应用程序,在这一章中,您将了解到如何为一组*路径操作*声明单个 `dependencies` 参数。
+
diff --git a/docs/zh/docs/tutorial/dependencies/index.md b/docs/zh/docs/tutorial/dependencies/index.md
new file mode 100644
index 00000000..82e9f536
--- /dev/null
+++ b/docs/zh/docs/tutorial/dependencies/index.md
@@ -0,0 +1,212 @@
+# 依赖项 - 第一步
+
+FastAPI 提供了简单易用,但功能强大的**依赖注入**系统。
+
+这个依赖系统设计的简单易用,可以让开发人员轻松地把组件集成至 **FastAPI**。
+
+## 什么是「依赖注入」
+
+编程中的**「依赖注入」**是声明代码(本文中为*路径操作函数* )运行所需的,或要使用的「依赖」的一种方式。
+
+然后,由系统(本文中为 **FastAPI**)负责执行任意需要的逻辑,为代码提供这些依赖(「注入」依赖项)。
+
+依赖注入常用于以下场景:
+
+* 共享业务逻辑(复用相同的代码逻辑)
+* 共享数据库连接
+* 实现安全、验证、角色权限
+* 等……
+
+上述场景均可以使用**依赖注入**,将代码重复最小化。
+
+## 第一步
+
+接下来,我们学习一个非常简单的例子,尽管它过于简单,不是很实用。
+
+但通过这个例子,您可以初步了解「依赖注入」的工作机制。
+
+### 创建依赖项
+
+首先,要关注的是依赖项。
+
+依赖项就是一个函数,且可以使用与*路径操作函数*相同的参数:
+
+```Python hl_lines="8-9"
+{!../../../docs_src/dependencies/tutorial001.py!}
+```
+
+大功告成。
+
+只用了**2 行**代码。
+
+依赖项函数的形式和结构与*路径操作函数*一样。
+
+因此,可以把依赖项当作没有「装饰器」(即,没有 `@app.get("/some-path")` )的路径操作函数。
+
+依赖项可以返回各种内容。
+
+本例中的依赖项预期接收如下参数:
+
+* 类型为 `str` 的可选查询参数 `q`
+* 类型为 `int` 的可选查询参数 `skip`,默认值是 `0`
+* 类型为 `int` 的可选查询参数 `limit`,默认值是 `100`
+
+然后,依赖项函数返回包含这些值的 `dict`。
+
+### 导入 `Depends`
+
+```Python hl_lines="3"
+{!../../../docs_src/dependencies/tutorial001.py!}
+```
+
+### 声明依赖项
+
+与在*路径操作函数*参数中使用 `Body`、`Query` 的方式相同,声明依赖项需要使用 `Depends` 和一个新的参数:
+
+```Python hl_lines="13 18"
+{!../../../docs_src/dependencies/tutorial001.py!}
+```
+
+虽然,在路径操作函数的参数中使用 `Depends` 的方式与 `Body`、`Query` 相同,但 `Depends` 的工作方式略有不同。
+
+这里只能传给 Depends 一个参数。
+
+且该参数必须是可调用对象,比如函数。
+
+该函数接收的参数和*路径操作函数*的参数一样。
+
+!!! tip "提示"
+
+ 下一章介绍,除了函数还有哪些「对象」可以用作依赖项。
+
+接收到新的请求时,**FastAPI** 执行如下操作:
+
+* 用正确的参数调用依赖项函数(「可依赖项」)
+* 获取函数返回的结果
+* 把函数返回的结果赋值给*路径操作函数*的参数
+
+```mermaid
+graph TB
+
+common_parameters(["common_parameters"])
+read_items["/items/"]
+read_users["/users/"]
+
+common_parameters --> read_items
+common_parameters --> read_users
+```
+
+这样,只编写一次代码,**FastAPI** 就可以为多个*路径操作*共享这段代码 。
+
+!!! check "检查"
+
+ 注意,无需创建专门的类,并将之传递给 **FastAPI** 以进行「注册」或执行类似的操作。
+
+ 只要把它传递给 `Depends`,**FastAPI** 就知道该如何执行后续操作。
+
+## 要不要使用 `async`?
+
+**FastAPI** 调用依赖项的方式与*路径操作函数*一样,因此,定义依赖项函数,也要应用与路径操作函数相同的规则。
+
+即,既可以使用异步的 `async def`,也可以使用普通的 `def` 定义依赖项。
+
+在普通的 `def` *路径操作函数*中,可以声明异步的 `async def` 依赖项;也可以在异步的 `async def` *路径操作函数*中声明普通的 `def` 依赖项。
+
+上述这些操作都是可行的,**FastAPI** 知道该怎么处理。
+
+!!! note "笔记"
+
+ 如里不了解异步,请参阅[异步:*“着急了?”*](../../async.md){.internal-link target=_blank} 一章中 `async` 和 `await` 的内容。
+
+## 与 OpenAPI 集成
+
+依赖项及子依赖项的所有请求声明、验证和需求都可以集成至同一个 OpenAPI 概图。
+
+所以,交互文档里也会显示依赖项的所有信息:
+
+
+
+## 简单用法
+
+观察一下就会发现,只要*路径* 和*操作*匹配,就可以使用声明的路径操作函数。然后,**FastAPI** 会用正确的参数调用函数,并提取请求中的数据。
+
+实际上,所有(或大多数)网络框架的工作方式都是这样的。
+
+开发人员永远都不需要直接调用这些函数,这些函数是由框架(在此为 **FastAPI** )调用的。
+
+通过依赖注入系统,只要告诉 **FastAPI** *路径操作函数* 还要「依赖」其他在*路径操作函数*之前执行的内容,**FastAPI** 就会执行函数代码,并「注入」函数返回的结果。
+
+其他与「依赖注入」概念相同的术语为:
+
+* 资源(Resource)
+* 提供方(Provider)
+* 服务(Service)
+* 可注入(Injectable)
+* 组件(Component)
+
+## **FastAPI** 插件
+
+**依赖注入**系统支持构建集成和「插件」。但实际上,FastAPI 根本**不需要创建「插件」**,因为使用依赖项可以声明不限数量的、可用于*路径操作函数*的集成与交互。
+
+创建依赖项非常简单、直观,并且还支持导入 Python 包。毫不夸张地说,只要几行代码就可以把需要的 Python 包与 API 函数集成在一起。
+
+下一章将详细介绍在关系型数据库、NoSQL 数据库、安全等方面使用依赖项的例子。
+
+## **FastAPI** 兼容性
+
+依赖注入系统如此简洁的特性,让 **FastAPI** 可以与下列系统兼容:
+
+* 关系型数据库
+* NoSQL 数据库
+* 外部支持库
+* 外部 API
+* 认证和鉴权系统
+* API 使用监控系统
+* 响应数据注入系统
+* 等等……
+
+## 简单而强大
+
+虽然,**层级式依赖注入系统**的定义与使用十分简单,但它却非常强大。
+
+比如,可以定义依赖其他依赖项的依赖项。
+
+最后,依赖项层级树构建后,**依赖注入系统**会处理所有依赖项及其子依赖项,并为每一步操作提供(注入)结果。
+
+比如,下面有 4 个 API 路径操作(*端点*):
+
+* `/items/public/`
+* `/items/private/`
+* `/users/{user_id}/activate`
+* `/items/pro/`
+
+开发人员可以使用依赖项及其子依赖项为这些路径操作添加不同的权限:
+
+```mermaid
+graph TB
+
+current_user(["current_user"])
+active_user(["active_user"])
+admin_user(["admin_user"])
+paying_user(["paying_user"])
+
+public["/items/public/"]
+private["/items/private/"]
+activate_user["/users/{user_id}/activate"]
+pro_items["/items/pro/"]
+
+current_user --> active_user
+active_user --> admin_user
+active_user --> paying_user
+
+current_user --> public
+active_user --> private
+admin_user --> activate_user
+paying_user --> pro_items
+```
+
+## 与 **OpenAPI** 集成
+
+在声明需求时,所有这些依赖项还会把参数、验证等功能添加至路径操作。
+
+**FastAPI** 负责把上述内容全部添加到 OpenAPI 概图,并显示在交互文档中。
\ No newline at end of file
diff --git a/docs/zh/docs/tutorial/dependencies/sub-dependencies.md b/docs/zh/docs/tutorial/dependencies/sub-dependencies.md
new file mode 100644
index 00000000..76d3e9f4
--- /dev/null
+++ b/docs/zh/docs/tutorial/dependencies/sub-dependencies.md
@@ -0,0 +1,88 @@
+# 子依赖项
+
+FastAPI 支持创建含**子依赖项**的依赖项。
+
+并且,可以按需声明任意**深度**的子依赖项嵌套层级。
+
+**FastAPI** 负责处理解析不同深度的子依赖项。
+
+### 第一层依赖项
+
+下列代码创建了第一层依赖项:
+
+```Python hl_lines="8-9"
+{!../../../docs_src/dependencies/tutorial005.py!}
+```
+
+这段代码声明了类型为 `str` 的可选查询参数 `q`,然后返回这个查询参数。
+
+这个函数很简单(不过也没什么用),但却有助于让我们专注于了解子依赖项的工作方式。
+
+### 第二层依赖项
+
+接下来,创建另一个依赖项函数,并同时用该依赖项自身再声明一个依赖项(所以这也是一个「依赖项」):
+
+```Python hl_lines="13"
+{!../../../docs_src/dependencies/tutorial005.py!}
+```
+
+这里重点说明一下声明的参数:
+
+* 尽管该函数自身是依赖项,但还声明了另一个依赖项(它「依赖」于其他对象)
+ * 该函数依赖 `query_extractor`, 并把 `query_extractor` 的返回值赋给参数 `q`
+* 同时,该函数还声明了类型是 `str` 的可选 cookie(`last_query`)
+ * 用户未提供查询参数 `q` 时,则使用上次使用后保存在 cookie 中的查询
+
+### 使用依赖项
+
+接下来,就可以使用依赖项:
+
+```Python hl_lines="21"
+{!../../../docs_src/dependencies/tutorial005.py!}
+```
+
+!!! info "信息"
+
+ 注意,这里在*路径操作函数*中只声明了一个依赖项,即 `query_or_cookie_extractor` 。
+
+ 但 **FastAPI** 必须先处理 `query_extractor`,以便在调用 `query_or_cookie_extractor` 时使用 `query_extractor` 返回的结果。
+
+```mermaid
+graph TB
+
+query_extractor(["query_extractor"])
+query_or_cookie_extractor(["query_or_cookie_extractor"])
+
+read_query["/items/"]
+
+query_extractor --> query_or_cookie_extractor --> read_query
+```
+
+## 多次使用同一个依赖项
+
+如果在同一个*路径操作* 多次声明了同一个依赖项,例如,多个依赖项共用一个子依赖项,**FastAPI** 在处理同一请求时,只调用一次该子依赖项。
+
+FastAPI 不会为同一个请求多次调用同一个依赖项,而是把依赖项的返回值进行「缓存」,并把它传递给同一请求中所有需要使用该返回值的「依赖项」。
+
+在高级使用场景中,如果不想使用「缓存」值,而是为需要在同一请求的每一步操作(多次)中都实际调用依赖项,可以把 `Depends` 的参数 `use_cache` 的值设置为 `False` :
+
+```Python hl_lines="1"
+async def needy_dependency(fresh_value: str = Depends(get_value, use_cache=False)):
+ return {"fresh_value": fresh_value}
+```
+
+## 小结
+
+千万别被本章里这些花里胡哨的词藻吓倒了,其实**依赖注入**系统非常简单。
+
+依赖注入无非是与*路径操作函数*一样的函数罢了。
+
+但它依然非常强大,能够声明任意嵌套深度的「图」或树状的依赖结构。
+
+!!! tip "提示"
+
+ 这些简单的例子现在看上去虽然没有什么实用价值,
+
+ 但在**安全**一章中,您会了解到这些例子的用途,
+
+ 以及这些例子所能节省的代码量。
\ No newline at end of file
diff --git a/docs/zh/docs/tutorial/path-operation-configuration.md b/docs/zh/docs/tutorial/path-operation-configuration.md
new file mode 100644
index 00000000..ad0e08d3
--- /dev/null
+++ b/docs/zh/docs/tutorial/path-operation-configuration.md
@@ -0,0 +1,101 @@
+# 路径操作配置
+
+*路径操作装饰器*支持多种配置参数。
+
+!!! warning "警告"
+
+ 注意:以下参数应直接传递给**路径操作装饰器**,不能传递给*路径操作函数*。
+
+## `status_code` 状态码
+
+`status_code` 用于定义*路径操作*响应中的 HTTP 状态码。
+
+可以直接传递 `int` 代码, 比如 `404`。
+
+如果记不住数字码的涵义,也可以用 `status` 的快捷常量:
+
+```Python hl_lines="3 17"
+{!../../../docs_src/path_operation_configuration/tutorial001.py!}
+```
+
+状态码在响应中使用,并会被添加到 OpenAPI 概图。
+
+!!! note "技术细节"
+
+ 也可以使用 `from starlette import status` 导入状态码。
+
+ **FastAPI** 的`fastapi.status` 和 `starlette.status` 一样,只是快捷方式。实际上,`fastapi.status` 直接继承自 Starlette。
+
+## `tags` 参数
+
+`tags` 参数的值是由 `str` 组成的 `list` (一般只有一个 `str` ),`tags` 用于为*路径操作*添加标签:
+
+```Python hl_lines="17 22 27"
+{!../../../docs_src/path_operation_configuration/tutorial002.py!}
+```
+
+OpenAPI 概图会自动添加标签,供 API 文档接口使用:
+
+
+
+## `summary` 和 `description` 参数
+
+路径装饰器还支持 `summary` 和 `description` 这两个参数:
+
+```Python hl_lines="20-21"
+{!../../../docs_src/path_operation_configuration/tutorial003.py!}
+```
+
+## 文档字符串(`docstring`)
+
+描述内容比较长且占用多行时,可以在函数的 docstring 中声明*路径操作*的描述,**FastAPI** 支持从文档字符串中读取描述内容。
+
+文档字符串支持 Markdown,能正确解析和显示 Markdown 的内容,但要注意文档字符串的缩进。
+
+```Python hl_lines="19-27"
+{!../../../docs_src/path_operation_configuration/tutorial004.py!}
+```
+
+下图为 Markdown 文本在 API 文档中的显示效果:
+
+
+
+## 响应描述
+
+`response_description` 参数用于定义响应的描述说明:
+
+```Python hl_lines="21"
+{!../../../docs_src/path_operation_configuration/tutorial005.py!}
+```
+
+!!! info "说明"
+
+ 注意,`response_description` 只用于描述响应,`description` 一般则用于描述*路径操作*。
+
+!!! check "检查"
+
+ OpenAPI 规定每个*路径操作*都要有响应描述。
+
+ 如果没有定义响应描述,**FastAPI** 则自动生成内容为 "Successful response" 的响应描述。
+
+
+
+## 弃用*路径操作*
+
+`deprecated` 参数可以把*路径操作*标记为弃用,无需直接删除:
+
+```Python hl_lines="16"
+{!../../../docs_src/path_operation_configuration/tutorial006.py!}
+```
+
+API 文档会把该路径操作标记为弃用:
+
+
+
+下图显示了正常*路径操作*与弃用*路径操作* 的区别:
+
+
+
+## 小结
+
+通过传递参数给*路径操作装饰器* ,即可轻松地配置*路径操作*、添加元数据。
diff --git a/docs/zh/docs/tutorial/query-params-str-validations.md b/docs/zh/docs/tutorial/query-params-str-validations.md
index 2a1d41a8..1d1d383d 100644
--- a/docs/zh/docs/tutorial/query-params-str-validations.md
+++ b/docs/zh/docs/tutorial/query-params-str-validations.md
@@ -26,7 +26,7 @@
现在,将 `Query` 用作查询参数的默认值,并将它的 `max_length` 参数设置为 50:
-```Python hl_lines="7"
+```Python hl_lines="9"
{!../../../docs_src/query_params_str_validations/tutorial002.py!}
```
@@ -58,7 +58,7 @@ q: str = Query(None, max_length=50)
你还可以添加 `min_length` 参数:
-```Python hl_lines="7"
+```Python hl_lines="9"
{!../../../docs_src/query_params_str_validations/tutorial003.py!}
```
@@ -66,7 +66,7 @@ q: str = Query(None, max_length=50)
你可以定义一个参数值必须匹配的正则表达式:
-```Python hl_lines="8"
+```Python hl_lines="10"
{!../../../docs_src/query_params_str_validations/tutorial004.py!}
```
@@ -211,13 +211,13 @@ http://localhost:8000/items/
你可以添加 `title`:
-```Python hl_lines="7"
+```Python hl_lines="10"
{!../../../docs_src/query_params_str_validations/tutorial007.py!}
```
以及 `description`:
-```Python hl_lines="11"
+```Python hl_lines="13"
{!../../../docs_src/query_params_str_validations/tutorial008.py!}
```
@@ -239,7 +239,7 @@ http://127.0.0.1:8000/items/?item-query=foobaritems
这时你可以用 `alias` 参数声明一个别名,该别名将用于在 URL 中查找查询参数值:
-```Python hl_lines="7"
+```Python hl_lines="9"
{!../../../docs_src/query_params_str_validations/tutorial009.py!}
```
@@ -251,7 +251,7 @@ http://127.0.0.1:8000/items/?item-query=foobaritems
那么将参数 `deprecated=True` 传入 `Query`:
-```Python hl_lines="16"
+```Python hl_lines="18"
{!../../../docs_src/query_params_str_validations/tutorial010.py!}
```
diff --git a/docs/zh/mkdocs.yml b/docs/zh/mkdocs.yml
index 85e470e5..40816648 100644
--- a/docs/zh/mkdocs.yml
+++ b/docs/zh/mkdocs.yml
@@ -9,17 +9,18 @@ theme:
primary: teal
accent: amber
toggle:
- icon: material/lightbulb-outline
+ icon: material/lightbulb
name: Switch to light mode
- scheme: slate
primary: teal
accent: amber
toggle:
- icon: material/lightbulb
+ icon: material/lightbulb-outline
name: Switch to dark mode
features:
- search.suggest
- search.highlight
+ - content.tabs.link
icon:
repo: fontawesome/brands/github-alt
logo: https://fastapi.tiangolo.com/img/icon-white.svg
@@ -28,9 +29,6 @@ theme:
repo_name: tiangolo/fastapi
repo_url: https://github.com/tiangolo/fastapi
edit_uri: ''
-google_analytics:
-- UA-133183413-1
-- auto
plugins:
- search
- markdownextradata:
@@ -39,11 +37,16 @@ nav:
- FastAPI: index.md
- Languages:
- en: /
+ - az: /az/
+ - de: /de/
- es: /es/
+ - fa: /fa/
- fr: /fr/
+ - id: /id/
- it: /it/
- ja: /ja/
- ko: /ko/
+ - nl: /nl/
- pl: /pl/
- pt: /pt/
- ru: /ru/
@@ -77,7 +80,13 @@ nav:
- tutorial/request-files.md
- tutorial/request-forms-and-files.md
- tutorial/handling-errors.md
+ - tutorial/path-operation-configuration.md
- tutorial/body-updates.md
+ - 依赖项:
+ - tutorial/dependencies/index.md
+ - tutorial/dependencies/sub-dependencies.md
+ - tutorial/dependencies/dependencies-in-path-operation-decorators.md
+ - tutorial/dependencies/global-dependencies.md
- 安全性:
- tutorial/security/index.md
- tutorial/security/get-current-user.md
@@ -93,7 +102,6 @@ nav:
- advanced/additional-status-codes.md
- advanced/response-directly.md
- advanced/custom-response.md
-- deployment.md
- contributing.md
- help-fastapi.md
- benchmarks.md
@@ -102,7 +110,7 @@ markdown_extensions:
permalink: true
- markdown.extensions.codehilite:
guess_lang: false
-- markdown_include.include:
+- mdx_include:
base_path: docs
- admonition
- codehilite
@@ -111,16 +119,20 @@ markdown_extensions:
custom_fences:
- name: mermaid
class: mermaid
- format: !!python/name:pymdownx.superfences.fence_div_format ''
-- pymdownx.tabbed
+ format: !!python/name:pymdownx.superfences.fence_code_format ''
+- pymdownx.tabbed:
+ alternate_style: true
extra:
+ analytics:
+ provider: google
+ property: UA-133183413-1
social:
- icon: fontawesome/brands/github-alt
link: https://github.com/tiangolo/fastapi
- icon: fontawesome/brands/discord
link: https://discord.gg/VQjSZaeJmf
- icon: fontawesome/brands/twitter
- link: https://twitter.com/tiangolo
+ link: https://twitter.com/fastapi
- icon: fontawesome/brands/linkedin
link: https://www.linkedin.com/in/tiangolo
- icon: fontawesome/brands/dev
@@ -132,16 +144,26 @@ extra:
alternate:
- link: /
name: en - English
+ - link: /az/
+ name: az
+ - link: /de/
+ name: de
- link: /es/
name: es - español
+ - link: /fa/
+ name: fa
- link: /fr/
name: fr - français
+ - link: /id/
+ name: id
- link: /it/
name: it - italiano
- link: /ja/
name: ja - 日本語
- link: /ko/
name: ko - 한국어
+ - link: /nl/
+ name: nl
- link: /pl/
name: pl
- link: /pt/
@@ -160,6 +182,5 @@ extra_css:
- https://fastapi.tiangolo.com/css/termynal.css
- https://fastapi.tiangolo.com/css/custom.css
extra_javascript:
-- https://unpkg.com/mermaid@8.4.6/dist/mermaid.min.js
- https://fastapi.tiangolo.com/js/termynal.js
- https://fastapi.tiangolo.com/js/custom.js
diff --git a/docs_src/app_testing/app_b/__init__.py b/docs_src/app_testing/app_b/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/docs_src/app_testing/main_b.py b/docs_src/app_testing/app_b/main.py
similarity index 100%
rename from docs_src/app_testing/main_b.py
rename to docs_src/app_testing/app_b/main.py
diff --git a/docs_src/app_testing/test_main_b.py b/docs_src/app_testing/app_b/test_main.py
similarity index 98%
rename from docs_src/app_testing/test_main_b.py
rename to docs_src/app_testing/app_b/test_main.py
index 83cc7d25..d186b8ec 100644
--- a/docs_src/app_testing/test_main_b.py
+++ b/docs_src/app_testing/app_b/test_main.py
@@ -1,6 +1,6 @@
from fastapi.testclient import TestClient
-from .main_b import app
+from .main import app
client = TestClient(app)
diff --git a/docs_src/app_testing/app_b_py310/__init__.py b/docs_src/app_testing/app_b_py310/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/docs_src/app_testing/app_b_py310/main.py b/docs_src/app_testing/app_b_py310/main.py
new file mode 100644
index 00000000..d44ab9e7
--- /dev/null
+++ b/docs_src/app_testing/app_b_py310/main.py
@@ -0,0 +1,36 @@
+from fastapi import FastAPI, Header, HTTPException
+from pydantic import BaseModel
+
+fake_secret_token = "coneofsilence"
+
+fake_db = {
+ "foo": {"id": "foo", "title": "Foo", "description": "There goes my hero"},
+ "bar": {"id": "bar", "title": "Bar", "description": "The bartenders"},
+}
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ id: str
+ title: str
+ description: str | None = None
+
+
+@app.get("/items/{item_id}", response_model=Item)
+async def read_main(item_id: str, x_token: str = Header(...)):
+ if x_token != fake_secret_token:
+ raise HTTPException(status_code=400, detail="Invalid X-Token header")
+ if item_id not in fake_db:
+ raise HTTPException(status_code=404, detail="Item not found")
+ return fake_db[item_id]
+
+
+@app.post("/items/", response_model=Item)
+async def create_item(item: Item, x_token: str = Header(...)):
+ if x_token != fake_secret_token:
+ raise HTTPException(status_code=400, detail="Invalid X-Token header")
+ if item.id in fake_db:
+ raise HTTPException(status_code=400, detail="Item already exists")
+ fake_db[item.id] = item
+ return item
diff --git a/docs_src/app_testing/app_b_py310/test_main.py b/docs_src/app_testing/app_b_py310/test_main.py
new file mode 100644
index 00000000..d186b8ec
--- /dev/null
+++ b/docs_src/app_testing/app_b_py310/test_main.py
@@ -0,0 +1,65 @@
+from fastapi.testclient import TestClient
+
+from .main import app
+
+client = TestClient(app)
+
+
+def test_read_item():
+ response = client.get("/items/foo", headers={"X-Token": "coneofsilence"})
+ assert response.status_code == 200
+ assert response.json() == {
+ "id": "foo",
+ "title": "Foo",
+ "description": "There goes my hero",
+ }
+
+
+def test_read_item_bad_token():
+ response = client.get("/items/foo", headers={"X-Token": "hailhydra"})
+ assert response.status_code == 400
+ assert response.json() == {"detail": "Invalid X-Token header"}
+
+
+def test_read_inexistent_item():
+ response = client.get("/items/baz", headers={"X-Token": "coneofsilence"})
+ assert response.status_code == 404
+ assert response.json() == {"detail": "Item not found"}
+
+
+def test_create_item():
+ response = client.post(
+ "/items/",
+ headers={"X-Token": "coneofsilence"},
+ json={"id": "foobar", "title": "Foo Bar", "description": "The Foo Barters"},
+ )
+ assert response.status_code == 200
+ assert response.json() == {
+ "id": "foobar",
+ "title": "Foo Bar",
+ "description": "The Foo Barters",
+ }
+
+
+def test_create_item_bad_token():
+ response = client.post(
+ "/items/",
+ headers={"X-Token": "hailhydra"},
+ json={"id": "bazz", "title": "Bazz", "description": "Drop the bazz"},
+ )
+ assert response.status_code == 400
+ assert response.json() == {"detail": "Invalid X-Token header"}
+
+
+def test_create_existing_item():
+ response = client.post(
+ "/items/",
+ headers={"X-Token": "coneofsilence"},
+ json={
+ "id": "foo",
+ "title": "The Foo ID Stealers",
+ "description": "There goes my stealer",
+ },
+ )
+ assert response.status_code == 400
+ assert response.json() == {"detail": "Item already exists"}
diff --git a/docs_src/async_tests/test_main.py b/docs_src/async_tests/test_main.py
index c141d86c..9f1527d5 100644
--- a/docs_src/async_tests/test_main.py
+++ b/docs_src/async_tests/test_main.py
@@ -4,7 +4,7 @@ from httpx import AsyncClient
from .main import app
-@pytest.mark.asyncio
+@pytest.mark.anyio
async def test_root():
async with AsyncClient(app=app, base_url="http://test") as ac:
response = await ac.get("/")
diff --git a/docs_src/background_tasks/tutorial002_py310.py b/docs_src/background_tasks/tutorial002_py310.py
new file mode 100644
index 00000000..626af135
--- /dev/null
+++ b/docs_src/background_tasks/tutorial002_py310.py
@@ -0,0 +1,24 @@
+from fastapi import BackgroundTasks, Depends, FastAPI
+
+app = FastAPI()
+
+
+def write_log(message: str):
+ with open("log.txt", mode="a") as log:
+ log.write(message)
+
+
+def get_query(background_tasks: BackgroundTasks, q: str | None = None):
+ if q:
+ message = f"found query: {q}\n"
+ background_tasks.add_task(write_log, message)
+ return q
+
+
+@app.post("/send-notification/{email}")
+async def send_notification(
+ email: str, background_tasks: BackgroundTasks, q: str = Depends(get_query)
+):
+ message = f"message to {email}\n"
+ background_tasks.add_task(write_log, message)
+ return {"message": "Message sent"}
diff --git a/docs_src/body/tutorial001_py310.py b/docs_src/body/tutorial001_py310.py
new file mode 100644
index 00000000..b71a52b6
--- /dev/null
+++ b/docs_src/body/tutorial001_py310.py
@@ -0,0 +1,17 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+
+
+app = FastAPI()
+
+
+@app.post("/items/")
+async def create_item(item: Item):
+ return item
diff --git a/docs_src/body/tutorial002_py310.py b/docs_src/body/tutorial002_py310.py
new file mode 100644
index 00000000..8928b72b
--- /dev/null
+++ b/docs_src/body/tutorial002_py310.py
@@ -0,0 +1,21 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+
+
+app = FastAPI()
+
+
+@app.post("/items/")
+async def create_item(item: Item):
+ item_dict = item.dict()
+ if item.tax:
+ price_with_tax = item.price + item.tax
+ item_dict.update({"price_with_tax": price_with_tax})
+ return item_dict
diff --git a/docs_src/body/tutorial003_py310.py b/docs_src/body/tutorial003_py310.py
new file mode 100644
index 00000000..a936f28f
--- /dev/null
+++ b/docs_src/body/tutorial003_py310.py
@@ -0,0 +1,17 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+
+
+app = FastAPI()
+
+
+@app.put("/items/{item_id}")
+async def create_item(item_id: int, item: Item):
+ return {"item_id": item_id, **item.dict()}
diff --git a/docs_src/body/tutorial004_py310.py b/docs_src/body/tutorial004_py310.py
new file mode 100644
index 00000000..60cfd961
--- /dev/null
+++ b/docs_src/body/tutorial004_py310.py
@@ -0,0 +1,20 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+
+
+app = FastAPI()
+
+
+@app.put("/items/{item_id}")
+async def create_item(item_id: int, item: Item, q: str | None = None):
+ result = {"item_id": item_id, **item.dict()}
+ if q:
+ result.update({"q": q})
+ return result
diff --git a/docs_src/body_fields/tutorial001_py310.py b/docs_src/body_fields/tutorial001_py310.py
new file mode 100644
index 00000000..01e02a05
--- /dev/null
+++ b/docs_src/body_fields/tutorial001_py310.py
@@ -0,0 +1,19 @@
+from fastapi import Body, FastAPI
+from pydantic import BaseModel, Field
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = Field(
+ None, title="The description of the item", max_length=300
+ )
+ price: float = Field(..., gt=0, description="The price must be greater than zero")
+ tax: float | None = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Item = Body(..., embed=True)):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/body_multiple_params/tutorial001_py310.py b/docs_src/body_multiple_params/tutorial001_py310.py
new file mode 100644
index 00000000..b08d397b
--- /dev/null
+++ b/docs_src/body_multiple_params/tutorial001_py310.py
@@ -0,0 +1,26 @@
+from fastapi import FastAPI, Path
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(
+ *,
+ item_id: int = Path(..., title="The ID of the item to get", ge=0, le=1000),
+ q: str | None = None,
+ item: Item | None = None,
+):
+ results = {"item_id": item_id}
+ if q:
+ results.update({"q": q})
+ if item:
+ results.update({"item": item})
+ return results
diff --git a/docs_src/body_multiple_params/tutorial002_py310.py b/docs_src/body_multiple_params/tutorial002_py310.py
new file mode 100644
index 00000000..2c4eb824
--- /dev/null
+++ b/docs_src/body_multiple_params/tutorial002_py310.py
@@ -0,0 +1,22 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+
+
+class User(BaseModel):
+ username: str
+ full_name: str | None = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Item, user: User):
+ results = {"item_id": item_id, "item": item, "user": user}
+ return results
diff --git a/docs_src/body_multiple_params/tutorial003_py310.py b/docs_src/body_multiple_params/tutorial003_py310.py
new file mode 100644
index 00000000..9ddbda3f
--- /dev/null
+++ b/docs_src/body_multiple_params/tutorial003_py310.py
@@ -0,0 +1,24 @@
+from fastapi import Body, FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+
+
+class User(BaseModel):
+ username: str
+ full_name: str | None = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(
+ item_id: int, item: Item, user: User, importance: int = Body(...)
+):
+ results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
+ return results
diff --git a/docs_src/body_multiple_params/tutorial004_py310.py b/docs_src/body_multiple_params/tutorial004_py310.py
new file mode 100644
index 00000000..77321300
--- /dev/null
+++ b/docs_src/body_multiple_params/tutorial004_py310.py
@@ -0,0 +1,31 @@
+from fastapi import Body, FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+
+
+class User(BaseModel):
+ username: str
+ full_name: str | None = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(
+ *,
+ item_id: int,
+ item: Item,
+ user: User,
+ importance: int = Body(..., gt=0),
+ q: str | None = None
+):
+ results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/body_multiple_params/tutorial005_py310.py b/docs_src/body_multiple_params/tutorial005_py310.py
new file mode 100644
index 00000000..97b213b1
--- /dev/null
+++ b/docs_src/body_multiple_params/tutorial005_py310.py
@@ -0,0 +1,17 @@
+from fastapi import Body, FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Item = Body(..., embed=True)):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/body_nested_models/tutorial001_py310.py b/docs_src/body_nested_models/tutorial001_py310.py
new file mode 100644
index 00000000..d89be35d
--- /dev/null
+++ b/docs_src/body_nested_models/tutorial001_py310.py
@@ -0,0 +1,18 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+ tags: list = []
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Item):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/body_nested_models/tutorial002_py310.py b/docs_src/body_nested_models/tutorial002_py310.py
new file mode 100644
index 00000000..71340e9f
--- /dev/null
+++ b/docs_src/body_nested_models/tutorial002_py310.py
@@ -0,0 +1,18 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+ tags: list[str] = []
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Item):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/body_nested_models/tutorial002_py39.py b/docs_src/body_nested_models/tutorial002_py39.py
new file mode 100644
index 00000000..af523a74
--- /dev/null
+++ b/docs_src/body_nested_models/tutorial002_py39.py
@@ -0,0 +1,20 @@
+from typing import Optional
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Optional[str] = None
+ price: float
+ tax: Optional[float] = None
+ tags: list[str] = []
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Item):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/body_nested_models/tutorial003_py310.py b/docs_src/body_nested_models/tutorial003_py310.py
new file mode 100644
index 00000000..194f2dc2
--- /dev/null
+++ b/docs_src/body_nested_models/tutorial003_py310.py
@@ -0,0 +1,18 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+ tags: set[str] = set()
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Item):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/body_nested_models/tutorial003_py39.py b/docs_src/body_nested_models/tutorial003_py39.py
new file mode 100644
index 00000000..931d92f8
--- /dev/null
+++ b/docs_src/body_nested_models/tutorial003_py39.py
@@ -0,0 +1,20 @@
+from typing import Optional
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Optional[str] = None
+ price: float
+ tax: Optional[float] = None
+ tags: set[str] = set()
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Item):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/body_nested_models/tutorial004_py310.py b/docs_src/body_nested_models/tutorial004_py310.py
new file mode 100644
index 00000000..ab0b63db
--- /dev/null
+++ b/docs_src/body_nested_models/tutorial004_py310.py
@@ -0,0 +1,24 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Image(BaseModel):
+ url: str
+ name: str
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+ tags: set[str] = []
+ image: Image | None = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Item):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/body_nested_models/tutorial004_py39.py b/docs_src/body_nested_models/tutorial004_py39.py
new file mode 100644
index 00000000..19985ec7
--- /dev/null
+++ b/docs_src/body_nested_models/tutorial004_py39.py
@@ -0,0 +1,26 @@
+from typing import Optional
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Image(BaseModel):
+ url: str
+ name: str
+
+
+class Item(BaseModel):
+ name: str
+ description: Optional[str] = None
+ price: float
+ tax: Optional[float] = None
+ tags: set[str] = []
+ image: Optional[Image] = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Item):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/body_nested_models/tutorial005_py310.py b/docs_src/body_nested_models/tutorial005_py310.py
new file mode 100644
index 00000000..00962845
--- /dev/null
+++ b/docs_src/body_nested_models/tutorial005_py310.py
@@ -0,0 +1,24 @@
+from fastapi import FastAPI
+from pydantic import BaseModel, HttpUrl
+
+app = FastAPI()
+
+
+class Image(BaseModel):
+ url: HttpUrl
+ name: str
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+ tags: set[str] = set()
+ image: Image | None = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Item):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/body_nested_models/tutorial005_py39.py b/docs_src/body_nested_models/tutorial005_py39.py
new file mode 100644
index 00000000..50455188
--- /dev/null
+++ b/docs_src/body_nested_models/tutorial005_py39.py
@@ -0,0 +1,26 @@
+from typing import Optional
+
+from fastapi import FastAPI
+from pydantic import BaseModel, HttpUrl
+
+app = FastAPI()
+
+
+class Image(BaseModel):
+ url: HttpUrl
+ name: str
+
+
+class Item(BaseModel):
+ name: str
+ description: Optional[str] = None
+ price: float
+ tax: Optional[float] = None
+ tags: set[str] = set()
+ image: Optional[Image] = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Item):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/body_nested_models/tutorial006_py310.py b/docs_src/body_nested_models/tutorial006_py310.py
new file mode 100644
index 00000000..c87cda3c
--- /dev/null
+++ b/docs_src/body_nested_models/tutorial006_py310.py
@@ -0,0 +1,24 @@
+from fastapi import FastAPI
+from pydantic import BaseModel, HttpUrl
+
+app = FastAPI()
+
+
+class Image(BaseModel):
+ url: HttpUrl
+ name: str
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+ tags: set[str] = set()
+ images: list[Image] | None = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Item):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/body_nested_models/tutorial006_py39.py b/docs_src/body_nested_models/tutorial006_py39.py
new file mode 100644
index 00000000..61898178
--- /dev/null
+++ b/docs_src/body_nested_models/tutorial006_py39.py
@@ -0,0 +1,26 @@
+from typing import Optional
+
+from fastapi import FastAPI
+from pydantic import BaseModel, HttpUrl
+
+app = FastAPI()
+
+
+class Image(BaseModel):
+ url: HttpUrl
+ name: str
+
+
+class Item(BaseModel):
+ name: str
+ description: Optional[str] = None
+ price: float
+ tax: Optional[float] = None
+ tags: set[str] = set()
+ images: Optional[list[Image]] = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Item):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/body_nested_models/tutorial007_py310.py b/docs_src/body_nested_models/tutorial007_py310.py
new file mode 100644
index 00000000..665ac56e
--- /dev/null
+++ b/docs_src/body_nested_models/tutorial007_py310.py
@@ -0,0 +1,30 @@
+from fastapi import FastAPI
+from pydantic import BaseModel, HttpUrl
+
+app = FastAPI()
+
+
+class Image(BaseModel):
+ url: HttpUrl
+ name: str
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+ tags: set[str] = set()
+ images: list[Image] | None = None
+
+
+class Offer(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ items: list[Item]
+
+
+@app.post("/offers/")
+async def create_offer(offer: Offer):
+ return offer
diff --git a/docs_src/body_nested_models/tutorial007_py39.py b/docs_src/body_nested_models/tutorial007_py39.py
new file mode 100644
index 00000000..0c7d32fb
--- /dev/null
+++ b/docs_src/body_nested_models/tutorial007_py39.py
@@ -0,0 +1,32 @@
+from typing import Optional
+
+from fastapi import FastAPI
+from pydantic import BaseModel, HttpUrl
+
+app = FastAPI()
+
+
+class Image(BaseModel):
+ url: HttpUrl
+ name: str
+
+
+class Item(BaseModel):
+ name: str
+ description: Optional[str] = None
+ price: float
+ tax: Optional[float] = None
+ tags: set[str] = set()
+ images: Optional[list[Image]] = None
+
+
+class Offer(BaseModel):
+ name: str
+ description: Optional[str] = None
+ price: float
+ items: list[Item]
+
+
+@app.post("/offers/")
+async def create_offer(offer: Offer):
+ return offer
diff --git a/docs_src/body_nested_models/tutorial008_py39.py b/docs_src/body_nested_models/tutorial008_py39.py
new file mode 100644
index 00000000..854a7a5a
--- /dev/null
+++ b/docs_src/body_nested_models/tutorial008_py39.py
@@ -0,0 +1,14 @@
+from fastapi import FastAPI
+from pydantic import BaseModel, HttpUrl
+
+app = FastAPI()
+
+
+class Image(BaseModel):
+ url: HttpUrl
+ name: str
+
+
+@app.post("/images/multiple/")
+async def create_multiple_images(images: list[Image]):
+ return images
diff --git a/docs_src/body_nested_models/tutorial009_py39.py b/docs_src/body_nested_models/tutorial009_py39.py
new file mode 100644
index 00000000..59c1e508
--- /dev/null
+++ b/docs_src/body_nested_models/tutorial009_py39.py
@@ -0,0 +1,8 @@
+from fastapi import FastAPI
+
+app = FastAPI()
+
+
+@app.post("/index-weights/")
+async def create_index_weights(weights: dict[int, float]):
+ return weights
diff --git a/docs_src/body_updates/tutorial001_py310.py b/docs_src/body_updates/tutorial001_py310.py
new file mode 100644
index 00000000..d275d7f8
--- /dev/null
+++ b/docs_src/body_updates/tutorial001_py310.py
@@ -0,0 +1,32 @@
+from fastapi import FastAPI
+from fastapi.encoders import jsonable_encoder
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str | None = None
+ description: str | None = None
+ price: float | None = None
+ tax: float = 10.5
+ tags: list[str] = []
+
+
+items = {
+ "foo": {"name": "Foo", "price": 50.2},
+ "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
+ "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
+}
+
+
+@app.get("/items/{item_id}", response_model=Item)
+async def read_item(item_id: str):
+ return items[item_id]
+
+
+@app.put("/items/{item_id}", response_model=Item)
+async def update_item(item_id: str, item: Item):
+ update_item_encoded = jsonable_encoder(item)
+ items[item_id] = update_item_encoded
+ return update_item_encoded
diff --git a/docs_src/body_updates/tutorial001_py39.py b/docs_src/body_updates/tutorial001_py39.py
new file mode 100644
index 00000000..5d5388b5
--- /dev/null
+++ b/docs_src/body_updates/tutorial001_py39.py
@@ -0,0 +1,34 @@
+from typing import Optional
+
+from fastapi import FastAPI
+from fastapi.encoders import jsonable_encoder
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: Optional[str] = None
+ description: Optional[str] = None
+ price: Optional[float] = None
+ tax: float = 10.5
+ tags: list[str] = []
+
+
+items = {
+ "foo": {"name": "Foo", "price": 50.2},
+ "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
+ "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
+}
+
+
+@app.get("/items/{item_id}", response_model=Item)
+async def read_item(item_id: str):
+ return items[item_id]
+
+
+@app.put("/items/{item_id}", response_model=Item)
+async def update_item(item_id: str, item: Item):
+ update_item_encoded = jsonable_encoder(item)
+ items[item_id] = update_item_encoded
+ return update_item_encoded
diff --git a/docs_src/body_updates/tutorial002_py310.py b/docs_src/body_updates/tutorial002_py310.py
new file mode 100644
index 00000000..34984149
--- /dev/null
+++ b/docs_src/body_updates/tutorial002_py310.py
@@ -0,0 +1,35 @@
+from fastapi import FastAPI
+from fastapi.encoders import jsonable_encoder
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str | None = None
+ description: str | None = None
+ price: float | None = None
+ tax: float = 10.5
+ tags: list[str] = []
+
+
+items = {
+ "foo": {"name": "Foo", "price": 50.2},
+ "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
+ "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
+}
+
+
+@app.get("/items/{item_id}", response_model=Item)
+async def read_item(item_id: str):
+ return items[item_id]
+
+
+@app.patch("/items/{item_id}", response_model=Item)
+async def update_item(item_id: str, item: Item):
+ stored_item_data = items[item_id]
+ stored_item_model = Item(**stored_item_data)
+ update_data = item.dict(exclude_unset=True)
+ updated_item = stored_item_model.copy(update=update_data)
+ items[item_id] = jsonable_encoder(updated_item)
+ return updated_item
diff --git a/docs_src/body_updates/tutorial002_py39.py b/docs_src/body_updates/tutorial002_py39.py
new file mode 100644
index 00000000..ab85bd5a
--- /dev/null
+++ b/docs_src/body_updates/tutorial002_py39.py
@@ -0,0 +1,37 @@
+from typing import Optional
+
+from fastapi import FastAPI
+from fastapi.encoders import jsonable_encoder
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: Optional[str] = None
+ description: Optional[str] = None
+ price: Optional[float] = None
+ tax: float = 10.5
+ tags: list[str] = []
+
+
+items = {
+ "foo": {"name": "Foo", "price": 50.2},
+ "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
+ "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
+}
+
+
+@app.get("/items/{item_id}", response_model=Item)
+async def read_item(item_id: str):
+ return items[item_id]
+
+
+@app.patch("/items/{item_id}", response_model=Item)
+async def update_item(item_id: str, item: Item):
+ stored_item_data = items[item_id]
+ stored_item_model = Item(**stored_item_data)
+ update_data = item.dict(exclude_unset=True)
+ updated_item = stored_item_model.copy(update=update_data)
+ items[item_id] = jsonable_encoder(updated_item)
+ return updated_item
diff --git a/docs_src/cookie_params/tutorial001_py310.py b/docs_src/cookie_params/tutorial001_py310.py
new file mode 100644
index 00000000..d0b00463
--- /dev/null
+++ b/docs_src/cookie_params/tutorial001_py310.py
@@ -0,0 +1,8 @@
+from fastapi import Cookie, FastAPI
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(ads_id: str | None = Cookie(None)):
+ return {"ads_id": ads_id}
diff --git a/docs_src/custom_response/tutorial006.py b/docs_src/custom_response/tutorial006.py
index 1c55568c..332f8f87 100644
--- a/docs_src/custom_response/tutorial006.py
+++ b/docs_src/custom_response/tutorial006.py
@@ -5,5 +5,5 @@ app = FastAPI()
@app.get("/typer")
-async def read_typer():
+async def redirect_typer():
return RedirectResponse("https://typer.tiangolo.com")
diff --git a/docs_src/custom_response/tutorial006b.py b/docs_src/custom_response/tutorial006b.py
new file mode 100644
index 00000000..03a7be39
--- /dev/null
+++ b/docs_src/custom_response/tutorial006b.py
@@ -0,0 +1,9 @@
+from fastapi import FastAPI
+from fastapi.responses import RedirectResponse
+
+app = FastAPI()
+
+
+@app.get("/fastapi", response_class=RedirectResponse)
+async def redirect_fastapi():
+ return "https://fastapi.tiangolo.com"
diff --git a/docs_src/custom_response/tutorial006c.py b/docs_src/custom_response/tutorial006c.py
new file mode 100644
index 00000000..db87a938
--- /dev/null
+++ b/docs_src/custom_response/tutorial006c.py
@@ -0,0 +1,9 @@
+from fastapi import FastAPI
+from fastapi.responses import RedirectResponse
+
+app = FastAPI()
+
+
+@app.get("/pydantic", response_class=RedirectResponse, status_code=302)
+async def redirect_pydantic():
+ return "https://pydantic-docs.helpmanual.io/"
diff --git a/docs_src/custom_response/tutorial008.py b/docs_src/custom_response/tutorial008.py
index d3327367..fc071cbe 100644
--- a/docs_src/custom_response/tutorial008.py
+++ b/docs_src/custom_response/tutorial008.py
@@ -7,5 +7,8 @@ app = FastAPI()
@app.get("/")
def main():
- file_like = open(some_file_path, mode="rb")
- return StreamingResponse(file_like, media_type="video/mp4")
+ def iterfile(): # (1)
+ with open(some_file_path, mode="rb") as file_like: # (2)
+ yield from file_like # (3)
+
+ return StreamingResponse(iterfile(), media_type="video/mp4")
diff --git a/docs_src/custom_response/tutorial009b.py b/docs_src/custom_response/tutorial009b.py
new file mode 100644
index 00000000..27200ee4
--- /dev/null
+++ b/docs_src/custom_response/tutorial009b.py
@@ -0,0 +1,10 @@
+from fastapi import FastAPI
+from fastapi.responses import FileResponse
+
+some_file_path = "large-video-file.mp4"
+app = FastAPI()
+
+
+@app.get("/", response_class=FileResponse)
+async def main():
+ return some_file_path
diff --git a/docs_src/dataclasses/tutorial001.py b/docs_src/dataclasses/tutorial001.py
new file mode 100644
index 00000000..43015eb2
--- /dev/null
+++ b/docs_src/dataclasses/tutorial001.py
@@ -0,0 +1,20 @@
+from dataclasses import dataclass
+from typing import Optional
+
+from fastapi import FastAPI
+
+
+@dataclass
+class Item:
+ name: str
+ price: float
+ description: Optional[str] = None
+ tax: Optional[float] = None
+
+
+app = FastAPI()
+
+
+@app.post("/items/")
+async def create_item(item: Item):
+ return item
diff --git a/docs_src/dataclasses/tutorial002.py b/docs_src/dataclasses/tutorial002.py
new file mode 100644
index 00000000..aaa7b879
--- /dev/null
+++ b/docs_src/dataclasses/tutorial002.py
@@ -0,0 +1,26 @@
+from dataclasses import dataclass, field
+from typing import List, Optional
+
+from fastapi import FastAPI
+
+
+@dataclass
+class Item:
+ name: str
+ price: float
+ tags: List[str] = field(default_factory=list)
+ description: Optional[str] = None
+ tax: Optional[float] = None
+
+
+app = FastAPI()
+
+
+@app.get("/items/next", response_model=Item)
+async def read_next_item():
+ return {
+ "name": "Island In The Moon",
+ "price": 12.99,
+ "description": "A place to be be playin' and havin' fun",
+ "tags": ["breater"],
+ }
diff --git a/docs_src/dataclasses/tutorial003.py b/docs_src/dataclasses/tutorial003.py
new file mode 100644
index 00000000..2c1fccdd
--- /dev/null
+++ b/docs_src/dataclasses/tutorial003.py
@@ -0,0 +1,55 @@
+from dataclasses import field # (1)
+from typing import List, Optional
+
+from fastapi import FastAPI
+from pydantic.dataclasses import dataclass # (2)
+
+
+@dataclass
+class Item:
+ name: str
+ description: Optional[str] = None
+
+
+@dataclass
+class Author:
+ name: str
+ items: List[Item] = field(default_factory=list) # (3)
+
+
+app = FastAPI()
+
+
+@app.post("/authors/{author_id}/items/", response_model=Author) # (4)
+async def create_author_items(author_id: str, items: List[Item]): # (5)
+ return {"name": author_id, "items": items} # (6)
+
+
+@app.get("/authors/", response_model=List[Author]) # (7)
+def get_authors(): # (8)
+ return [ # (9)
+ {
+ "name": "Breaters",
+ "items": [
+ {
+ "name": "Island In The Moon",
+ "description": "A place to be be playin' and havin' fun",
+ },
+ {"name": "Holy Buddies"},
+ ],
+ },
+ {
+ "name": "System of an Up",
+ "items": [
+ {
+ "name": "Salt",
+ "description": "The kombucha mushroom people's favorite",
+ },
+ {"name": "Pad Thai"},
+ {
+ "name": "Lonely Night",
+ "description": "The mostests lonliest nightiest of allest",
+ },
+ ],
+ },
+ ]
diff --git a/docs_src/dependencies/tutorial001_py310.py b/docs_src/dependencies/tutorial001_py310.py
new file mode 100644
index 00000000..662af9b1
--- /dev/null
+++ b/docs_src/dependencies/tutorial001_py310.py
@@ -0,0 +1,17 @@
+from fastapi import Depends, FastAPI
+
+app = FastAPI()
+
+
+async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
+ return {"q": q, "skip": skip, "limit": limit}
+
+
+@app.get("/items/")
+async def read_items(commons: dict = Depends(common_parameters)):
+ return commons
+
+
+@app.get("/users/")
+async def read_users(commons: dict = Depends(common_parameters)):
+ return commons
diff --git a/docs_src/dependencies/tutorial002_py310.py b/docs_src/dependencies/tutorial002_py310.py
new file mode 100644
index 00000000..23c048ab
--- /dev/null
+++ b/docs_src/dependencies/tutorial002_py310.py
@@ -0,0 +1,23 @@
+from fastapi import Depends, FastAPI
+
+app = FastAPI()
+
+
+fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
+
+
+class CommonQueryParams:
+ def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
+ self.q = q
+ self.skip = skip
+ self.limit = limit
+
+
+@app.get("/items/")
+async def read_items(commons: CommonQueryParams = Depends(CommonQueryParams)):
+ response = {}
+ if commons.q:
+ response.update({"q": commons.q})
+ items = fake_items_db[commons.skip : commons.skip + commons.limit]
+ response.update({"items": items})
+ return response
diff --git a/docs_src/dependencies/tutorial003_py310.py b/docs_src/dependencies/tutorial003_py310.py
new file mode 100644
index 00000000..10d3771c
--- /dev/null
+++ b/docs_src/dependencies/tutorial003_py310.py
@@ -0,0 +1,23 @@
+from fastapi import Depends, FastAPI
+
+app = FastAPI()
+
+
+fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
+
+
+class CommonQueryParams:
+ def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
+ self.q = q
+ self.skip = skip
+ self.limit = limit
+
+
+@app.get("/items/")
+async def read_items(commons=Depends(CommonQueryParams)):
+ response = {}
+ if commons.q:
+ response.update({"q": commons.q})
+ items = fake_items_db[commons.skip : commons.skip + commons.limit]
+ response.update({"items": items})
+ return response
diff --git a/docs_src/dependencies/tutorial004_py310.py b/docs_src/dependencies/tutorial004_py310.py
new file mode 100644
index 00000000..5245bb0f
--- /dev/null
+++ b/docs_src/dependencies/tutorial004_py310.py
@@ -0,0 +1,23 @@
+from fastapi import Depends, FastAPI
+
+app = FastAPI()
+
+
+fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
+
+
+class CommonQueryParams:
+ def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
+ self.q = q
+ self.skip = skip
+ self.limit = limit
+
+
+@app.get("/items/")
+async def read_items(commons: CommonQueryParams = Depends()):
+ response = {}
+ if commons.q:
+ response.update({"q": commons.q})
+ items = fake_items_db[commons.skip : commons.skip + commons.limit]
+ response.update({"items": items})
+ return response
diff --git a/docs_src/dependencies/tutorial005_py310.py b/docs_src/dependencies/tutorial005_py310.py
new file mode 100644
index 00000000..5e1d7e0e
--- /dev/null
+++ b/docs_src/dependencies/tutorial005_py310.py
@@ -0,0 +1,20 @@
+from fastapi import Cookie, Depends, FastAPI
+
+app = FastAPI()
+
+
+def query_extractor(q: str | None = None):
+ return q
+
+
+def query_or_cookie_extractor(
+ q: str = Depends(query_extractor), last_query: str | None = Cookie(None)
+):
+ if not q:
+ return last_query
+ return q
+
+
+@app.get("/items/")
+async def read_query(query_or_default: str = Depends(query_or_cookie_extractor)):
+ return {"q_or_cookie": query_or_default}
diff --git a/docs_src/encoder/tutorial001_py310.py b/docs_src/encoder/tutorial001_py310.py
new file mode 100644
index 00000000..5baf1362
--- /dev/null
+++ b/docs_src/encoder/tutorial001_py310.py
@@ -0,0 +1,22 @@
+from datetime import datetime
+
+from fastapi import FastAPI
+from fastapi.encoders import jsonable_encoder
+from pydantic import BaseModel
+
+fake_db = {}
+
+
+class Item(BaseModel):
+ title: str
+ timestamp: datetime
+ description: str | None = None
+
+
+app = FastAPI()
+
+
+@app.put("/items/{id}")
+def update_item(id: str, item: Item):
+ json_compatible_item_data = jsonable_encoder(item)
+ fake_db[id] = json_compatible_item_data
diff --git a/docs_src/extending_openapi/tutorial003.py b/docs_src/extending_openapi/tutorial003.py
new file mode 100644
index 00000000..6c24ce75
--- /dev/null
+++ b/docs_src/extending_openapi/tutorial003.py
@@ -0,0 +1,8 @@
+from fastapi import FastAPI
+
+app = FastAPI(swagger_ui_parameters={"syntaxHighlight": False})
+
+
+@app.get("/users/{username}")
+async def read_user(username: str):
+ return {"message": f"Hello {username}"}
diff --git a/docs_src/extending_openapi/tutorial004.py b/docs_src/extending_openapi/tutorial004.py
new file mode 100644
index 00000000..cc569ce4
--- /dev/null
+++ b/docs_src/extending_openapi/tutorial004.py
@@ -0,0 +1,8 @@
+from fastapi import FastAPI
+
+app = FastAPI(swagger_ui_parameters={"syntaxHighlight.theme": "obsidian"})
+
+
+@app.get("/users/{username}")
+async def read_user(username: str):
+ return {"message": f"Hello {username}"}
diff --git a/docs_src/extending_openapi/tutorial005.py b/docs_src/extending_openapi/tutorial005.py
new file mode 100644
index 00000000..b4449f5c
--- /dev/null
+++ b/docs_src/extending_openapi/tutorial005.py
@@ -0,0 +1,8 @@
+from fastapi import FastAPI
+
+app = FastAPI(swagger_ui_parameters={"deepLinking": False})
+
+
+@app.get("/users/{username}")
+async def read_user(username: str):
+ return {"message": f"Hello {username}"}
diff --git a/docs_src/extra_data_types/tutorial001_py310.py b/docs_src/extra_data_types/tutorial001_py310.py
new file mode 100644
index 00000000..4a33481b
--- /dev/null
+++ b/docs_src/extra_data_types/tutorial001_py310.py
@@ -0,0 +1,27 @@
+from datetime import datetime, time, timedelta
+from uuid import UUID
+
+from fastapi import Body, FastAPI
+
+app = FastAPI()
+
+
+@app.put("/items/{item_id}")
+async def read_items(
+ item_id: UUID,
+ start_datetime: datetime | None = Body(None),
+ end_datetime: datetime | None = Body(None),
+ repeat_at: time | None = Body(None),
+ process_after: timedelta | None = Body(None),
+):
+ start_process = start_datetime + process_after
+ duration = end_datetime - start_process
+ return {
+ "item_id": item_id,
+ "start_datetime": start_datetime,
+ "end_datetime": end_datetime,
+ "repeat_at": repeat_at,
+ "process_after": process_after,
+ "start_process": start_process,
+ "duration": duration,
+ }
diff --git a/docs_src/extra_models/tutorial001_py310.py b/docs_src/extra_models/tutorial001_py310.py
new file mode 100644
index 00000000..669386ae
--- /dev/null
+++ b/docs_src/extra_models/tutorial001_py310.py
@@ -0,0 +1,41 @@
+from fastapi import FastAPI
+from pydantic import BaseModel, EmailStr
+
+app = FastAPI()
+
+
+class UserIn(BaseModel):
+ username: str
+ password: str
+ email: EmailStr
+ full_name: str | None = None
+
+
+class UserOut(BaseModel):
+ username: str
+ email: EmailStr
+ full_name: str | None = None
+
+
+class UserInDB(BaseModel):
+ username: str
+ hashed_password: str
+ email: EmailStr
+ full_name: str | None = None
+
+
+def fake_password_hasher(raw_password: str):
+ return "supersecret" + raw_password
+
+
+def fake_save_user(user_in: UserIn):
+ hashed_password = fake_password_hasher(user_in.password)
+ user_in_db = UserInDB(**user_in.dict(), hashed_password=hashed_password)
+ print("User saved! ..not really")
+ return user_in_db
+
+
+@app.post("/user/", response_model=UserOut)
+async def create_user(user_in: UserIn):
+ user_saved = fake_save_user(user_in)
+ return user_saved
diff --git a/docs_src/extra_models/tutorial002_py310.py b/docs_src/extra_models/tutorial002_py310.py
new file mode 100644
index 00000000..5b8ed7de
--- /dev/null
+++ b/docs_src/extra_models/tutorial002_py310.py
@@ -0,0 +1,39 @@
+from fastapi import FastAPI
+from pydantic import BaseModel, EmailStr
+
+app = FastAPI()
+
+
+class UserBase(BaseModel):
+ username: str
+ email: EmailStr
+ full_name: str | None = None
+
+
+class UserIn(UserBase):
+ password: str
+
+
+class UserOut(UserBase):
+ pass
+
+
+class UserInDB(UserBase):
+ hashed_password: str
+
+
+def fake_password_hasher(raw_password: str):
+ return "supersecret" + raw_password
+
+
+def fake_save_user(user_in: UserIn):
+ hashed_password = fake_password_hasher(user_in.password)
+ user_in_db = UserInDB(**user_in.dict(), hashed_password=hashed_password)
+ print("User saved! ..not really")
+ return user_in_db
+
+
+@app.post("/user/", response_model=UserOut)
+async def create_user(user_in: UserIn):
+ user_saved = fake_save_user(user_in)
+ return user_saved
diff --git a/docs_src/extra_models/tutorial003_py310.py b/docs_src/extra_models/tutorial003_py310.py
new file mode 100644
index 00000000..065439ac
--- /dev/null
+++ b/docs_src/extra_models/tutorial003_py310.py
@@ -0,0 +1,35 @@
+from typing import Union
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class BaseItem(BaseModel):
+ description: str
+ type: str
+
+
+class CarItem(BaseItem):
+ type = "car"
+
+
+class PlaneItem(BaseItem):
+ type = "plane"
+ size: int
+
+
+items = {
+ "item1": {"description": "All my friends drive a low rider", "type": "car"},
+ "item2": {
+ "description": "Music is my aeroplane, it's my aeroplane",
+ "type": "plane",
+ "size": 5,
+ },
+}
+
+
+@app.get("/items/{item_id}", response_model=Union[PlaneItem, CarItem])
+async def read_item(item_id: str):
+ return items[item_id]
diff --git a/docs_src/extra_models/tutorial004_py39.py b/docs_src/extra_models/tutorial004_py39.py
new file mode 100644
index 00000000..28cacde4
--- /dev/null
+++ b/docs_src/extra_models/tutorial004_py39.py
@@ -0,0 +1,20 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str
+
+
+items = [
+ {"name": "Foo", "description": "There comes my hero"},
+ {"name": "Red", "description": "It's my aeroplane"},
+]
+
+
+@app.get("/items/", response_model=list[Item])
+async def read_items():
+ return items
diff --git a/docs_src/extra_models/tutorial005_py39.py b/docs_src/extra_models/tutorial005_py39.py
new file mode 100644
index 00000000..9da2a0a0
--- /dev/null
+++ b/docs_src/extra_models/tutorial005_py39.py
@@ -0,0 +1,8 @@
+from fastapi import FastAPI
+
+app = FastAPI()
+
+
+@app.get("/keyword-weights/", response_model=dict[str, float])
+async def read_keyword_weights():
+ return {"foo": 2.3, "bar": 3.4}
diff --git a/docs_src/generate_clients/tutorial001.py b/docs_src/generate_clients/tutorial001.py
new file mode 100644
index 00000000..2d1f91bc
--- /dev/null
+++ b/docs_src/generate_clients/tutorial001.py
@@ -0,0 +1,28 @@
+from typing import List
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ price: float
+
+
+class ResponseMessage(BaseModel):
+ message: str
+
+
+@app.post("/items/", response_model=ResponseMessage)
+async def create_item(item: Item):
+ return {"message": "item received"}
+
+
+@app.get("/items/", response_model=List[Item])
+async def get_items():
+ return [
+ {"name": "Plumbus", "price": 3},
+ {"name": "Portal Gun", "price": 9001},
+ ]
diff --git a/docs_src/generate_clients/tutorial001_py39.py b/docs_src/generate_clients/tutorial001_py39.py
new file mode 100644
index 00000000..6a5ae232
--- /dev/null
+++ b/docs_src/generate_clients/tutorial001_py39.py
@@ -0,0 +1,26 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ price: float
+
+
+class ResponseMessage(BaseModel):
+ message: str
+
+
+@app.post("/items/", response_model=ResponseMessage)
+async def create_item(item: Item):
+ return {"message": "item received"}
+
+
+@app.get("/items/", response_model=list[Item])
+async def get_items():
+ return [
+ {"name": "Plumbus", "price": 3},
+ {"name": "Portal Gun", "price": 9001},
+ ]
diff --git a/docs_src/generate_clients/tutorial002.py b/docs_src/generate_clients/tutorial002.py
new file mode 100644
index 00000000..bd80449a
--- /dev/null
+++ b/docs_src/generate_clients/tutorial002.py
@@ -0,0 +1,38 @@
+from typing import List
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ price: float
+
+
+class ResponseMessage(BaseModel):
+ message: str
+
+
+class User(BaseModel):
+ username: str
+ email: str
+
+
+@app.post("/items/", response_model=ResponseMessage, tags=["items"])
+async def create_item(item: Item):
+ return {"message": "Item received"}
+
+
+@app.get("/items/", response_model=List[Item], tags=["items"])
+async def get_items():
+ return [
+ {"name": "Plumbus", "price": 3},
+ {"name": "Portal Gun", "price": 9001},
+ ]
+
+
+@app.post("/users/", response_model=ResponseMessage, tags=["users"])
+async def create_user(user: User):
+ return {"message": "User received"}
diff --git a/docs_src/generate_clients/tutorial002_py39.py b/docs_src/generate_clients/tutorial002_py39.py
new file mode 100644
index 00000000..83309760
--- /dev/null
+++ b/docs_src/generate_clients/tutorial002_py39.py
@@ -0,0 +1,36 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ price: float
+
+
+class ResponseMessage(BaseModel):
+ message: str
+
+
+class User(BaseModel):
+ username: str
+ email: str
+
+
+@app.post("/items/", response_model=ResponseMessage, tags=["items"])
+async def create_item(item: Item):
+ return {"message": "Item received"}
+
+
+@app.get("/items/", response_model=list[Item], tags=["items"])
+async def get_items():
+ return [
+ {"name": "Plumbus", "price": 3},
+ {"name": "Portal Gun", "price": 9001},
+ ]
+
+
+@app.post("/users/", response_model=ResponseMessage, tags=["users"])
+async def create_user(user: User):
+ return {"message": "User received"}
diff --git a/docs_src/generate_clients/tutorial003.py b/docs_src/generate_clients/tutorial003.py
new file mode 100644
index 00000000..49eab73a
--- /dev/null
+++ b/docs_src/generate_clients/tutorial003.py
@@ -0,0 +1,44 @@
+from typing import List
+
+from fastapi import FastAPI
+from fastapi.routing import APIRoute
+from pydantic import BaseModel
+
+
+def custom_generate_unique_id(route: APIRoute):
+ return f"{route.tags[0]}-{route.name}"
+
+
+app = FastAPI(generate_unique_id_function=custom_generate_unique_id)
+
+
+class Item(BaseModel):
+ name: str
+ price: float
+
+
+class ResponseMessage(BaseModel):
+ message: str
+
+
+class User(BaseModel):
+ username: str
+ email: str
+
+
+@app.post("/items/", response_model=ResponseMessage, tags=["items"])
+async def create_item(item: Item):
+ return {"message": "Item received"}
+
+
+@app.get("/items/", response_model=List[Item], tags=["items"])
+async def get_items():
+ return [
+ {"name": "Plumbus", "price": 3},
+ {"name": "Portal Gun", "price": 9001},
+ ]
+
+
+@app.post("/users/", response_model=ResponseMessage, tags=["users"])
+async def create_user(user: User):
+ return {"message": "User received"}
diff --git a/docs_src/generate_clients/tutorial003_py39.py b/docs_src/generate_clients/tutorial003_py39.py
new file mode 100644
index 00000000..40722cf1
--- /dev/null
+++ b/docs_src/generate_clients/tutorial003_py39.py
@@ -0,0 +1,42 @@
+from fastapi import FastAPI
+from fastapi.routing import APIRoute
+from pydantic import BaseModel
+
+
+def custom_generate_unique_id(route: APIRoute):
+ return f"{route.tags[0]}-{route.name}"
+
+
+app = FastAPI(generate_unique_id_function=custom_generate_unique_id)
+
+
+class Item(BaseModel):
+ name: str
+ price: float
+
+
+class ResponseMessage(BaseModel):
+ message: str
+
+
+class User(BaseModel):
+ username: str
+ email: str
+
+
+@app.post("/items/", response_model=ResponseMessage, tags=["items"])
+async def create_item(item: Item):
+ return {"message": "Item received"}
+
+
+@app.get("/items/", response_model=list[Item], tags=["items"])
+async def get_items():
+ return [
+ {"name": "Plumbus", "price": 3},
+ {"name": "Portal Gun", "price": 9001},
+ ]
+
+
+@app.post("/users/", response_model=ResponseMessage, tags=["users"])
+async def create_user(user: User):
+ return {"message": "User received"}
diff --git a/docs_src/generate_clients/tutorial004.py b/docs_src/generate_clients/tutorial004.py
new file mode 100644
index 00000000..894dc7f8
--- /dev/null
+++ b/docs_src/generate_clients/tutorial004.py
@@ -0,0 +1,15 @@
+import json
+from pathlib import Path
+
+file_path = Path("./openapi.json")
+openapi_content = json.loads(file_path.read_text())
+
+for path_data in openapi_content["paths"].values():
+ for operation in path_data.values():
+ tag = operation["tags"][0]
+ operation_id = operation["operationId"]
+ to_remove = f"{tag}-"
+ new_operation_id = operation_id[len(to_remove) :]
+ operation["operationId"] = new_operation_id
+
+file_path.write_text(json.dumps(openapi_content))
diff --git a/docs_src/graphql/tutorial001.py b/docs_src/graphql/tutorial001.py
index 41da361d..3b4ca99c 100644
--- a/docs_src/graphql/tutorial001.py
+++ b/docs_src/graphql/tutorial001.py
@@ -1,14 +1,26 @@
-import graphene
+import strawberry
from fastapi import FastAPI
-from starlette.graphql import GraphQLApp
+from strawberry.asgi import GraphQL
-class Query(graphene.ObjectType):
- hello = graphene.String(name=graphene.String(default_value="stranger"))
+@strawberry.type
+class User:
+ name: str
+ age: int
- def resolve_hello(self, info, name):
- return "Hello " + name
+@strawberry.type
+class Query:
+ @strawberry.field
+ def user(self) -> User:
+ return User(name="Patrick", age=100)
+
+
+schema = strawberry.Schema(query=Query)
+
+
+graphql_app = GraphQL(schema)
app = FastAPI()
-app.add_route("/", GraphQLApp(schema=graphene.Schema(query=Query)))
+app.add_route("/graphql", graphql_app)
+app.add_websocket_route("/graphql", graphql_app)
diff --git a/docs_src/header_params/tutorial001_py310.py b/docs_src/header_params/tutorial001_py310.py
new file mode 100644
index 00000000..b2846334
--- /dev/null
+++ b/docs_src/header_params/tutorial001_py310.py
@@ -0,0 +1,8 @@
+from fastapi import FastAPI, Header
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(user_agent: str | None = Header(None)):
+ return {"User-Agent": user_agent}
diff --git a/docs_src/header_params/tutorial002_py310.py b/docs_src/header_params/tutorial002_py310.py
new file mode 100644
index 00000000..98ab5a80
--- /dev/null
+++ b/docs_src/header_params/tutorial002_py310.py
@@ -0,0 +1,10 @@
+from fastapi import FastAPI, Header
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(
+ strange_header: str | None = Header(None, convert_underscores=False)
+):
+ return {"strange_header": strange_header}
diff --git a/docs_src/header_params/tutorial003_py310.py b/docs_src/header_params/tutorial003_py310.py
new file mode 100644
index 00000000..2dac2c13
--- /dev/null
+++ b/docs_src/header_params/tutorial003_py310.py
@@ -0,0 +1,8 @@
+from fastapi import FastAPI, Header
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(x_token: list[str] | None = Header(None)):
+ return {"X-Token values": x_token}
diff --git a/docs_src/header_params/tutorial003_py39.py b/docs_src/header_params/tutorial003_py39.py
new file mode 100644
index 00000000..35976652
--- /dev/null
+++ b/docs_src/header_params/tutorial003_py39.py
@@ -0,0 +1,10 @@
+from typing import Optional
+
+from fastapi import FastAPI, Header
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(x_token: Optional[list[str]] = Header(None)):
+ return {"X-Token values": x_token}
diff --git a/docs_src/metadata/tutorial001.py b/docs_src/metadata/tutorial001.py
index 16bab8a0..3fba9e7d 100644
--- a/docs_src/metadata/tutorial001.py
+++ b/docs_src/metadata/tutorial001.py
@@ -1,12 +1,37 @@
from fastapi import FastAPI
+description = """
+ChimichangApp API helps you do awesome stuff. 🚀
+
+## Items
+
+You can **read items**.
+
+## Users
+
+You will be able to:
+
+* **Create users** (_not implemented_).
+* **Read users** (_not implemented_).
+"""
+
app = FastAPI(
- title="My Super Project",
- description="This is a very fancy project, with auto docs for the API and everything",
- version="2.5.0",
+ title="ChimichangApp",
+ description=description,
+ version="0.0.1",
+ terms_of_service="http://example.com/terms/",
+ contact={
+ "name": "Deadpoolio the Amazing",
+ "url": "http://x-force.example.com/contact/",
+ "email": "dp@x-force.example.com",
+ },
+ license_info={
+ "name": "Apache 2.0",
+ "url": "https://www.apache.org/licenses/LICENSE-2.0.html",
+ },
)
@app.get("/items/")
async def read_items():
- return [{"name": "Foo"}]
+ return [{"name": "Katana"}]
diff --git a/docs_src/path_operation_advanced_configuration/tutorial005.py b/docs_src/path_operation_advanced_configuration/tutorial005.py
new file mode 100644
index 00000000..5837ad83
--- /dev/null
+++ b/docs_src/path_operation_advanced_configuration/tutorial005.py
@@ -0,0 +1,8 @@
+from fastapi import FastAPI
+
+app = FastAPI()
+
+
+@app.get("/items/", openapi_extra={"x-aperture-labs-portal": "blue"})
+async def read_items():
+ return [{"item_id": "portal-gun"}]
diff --git a/docs_src/path_operation_advanced_configuration/tutorial006.py b/docs_src/path_operation_advanced_configuration/tutorial006.py
new file mode 100644
index 00000000..403c3ee3
--- /dev/null
+++ b/docs_src/path_operation_advanced_configuration/tutorial006.py
@@ -0,0 +1,41 @@
+from fastapi import FastAPI, Request
+
+app = FastAPI()
+
+
+def magic_data_reader(raw_body: bytes):
+ return {
+ "size": len(raw_body),
+ "content": {
+ "name": "Maaaagic",
+ "price": 42,
+ "description": "Just kiddin', no magic here. ✨",
+ },
+ }
+
+
+@app.post(
+ "/items/",
+ openapi_extra={
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "required": ["name", "price"],
+ "type": "object",
+ "properties": {
+ "name": {"type": "string"},
+ "price": {"type": "number"},
+ "description": {"type": "string"},
+ },
+ }
+ }
+ },
+ "required": True,
+ },
+ },
+)
+async def create_item(request: Request):
+ raw_body = await request.body()
+ data = magic_data_reader(raw_body)
+ return data
diff --git a/docs_src/path_operation_advanced_configuration/tutorial007.py b/docs_src/path_operation_advanced_configuration/tutorial007.py
new file mode 100644
index 00000000..d51752bb
--- /dev/null
+++ b/docs_src/path_operation_advanced_configuration/tutorial007.py
@@ -0,0 +1,34 @@
+from typing import List
+
+import yaml
+from fastapi import FastAPI, HTTPException, Request
+from pydantic import BaseModel, ValidationError
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ tags: List[str]
+
+
+@app.post(
+ "/items/",
+ openapi_extra={
+ "requestBody": {
+ "content": {"application/x-yaml": {"schema": Item.schema()}},
+ "required": True,
+ },
+ },
+)
+async def create_item(request: Request):
+ raw_body = await request.body()
+ try:
+ data = yaml.safe_load(raw_body)
+ except yaml.YAMLError:
+ raise HTTPException(status_code=422, detail="Invalid YAML")
+ try:
+ item = Item.parse_obj(data)
+ except ValidationError as e:
+ raise HTTPException(status_code=422, detail=e.errors())
+ return item
diff --git a/docs_src/path_operation_configuration/tutorial001.py b/docs_src/path_operation_configuration/tutorial001.py
index 66ea442e..1316d923 100644
--- a/docs_src/path_operation_configuration/tutorial001.py
+++ b/docs_src/path_operation_configuration/tutorial001.py
@@ -11,7 +11,7 @@ class Item(BaseModel):
description: Optional[str] = None
price: float
tax: Optional[float] = None
- tags: Set[str] = []
+ tags: Set[str] = set()
@app.post("/items/", response_model=Item, status_code=status.HTTP_201_CREATED)
diff --git a/docs_src/path_operation_configuration/tutorial001_py310.py b/docs_src/path_operation_configuration/tutorial001_py310.py
new file mode 100644
index 00000000..da078fdf
--- /dev/null
+++ b/docs_src/path_operation_configuration/tutorial001_py310.py
@@ -0,0 +1,17 @@
+from fastapi import FastAPI, status
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+ tags: set[str] = set()
+
+
+@app.post("/items/", response_model=Item, status_code=status.HTTP_201_CREATED)
+async def create_item(item: Item):
+ return item
diff --git a/docs_src/path_operation_configuration/tutorial001_py39.py b/docs_src/path_operation_configuration/tutorial001_py39.py
new file mode 100644
index 00000000..5c04d8ba
--- /dev/null
+++ b/docs_src/path_operation_configuration/tutorial001_py39.py
@@ -0,0 +1,19 @@
+from typing import Optional
+
+from fastapi import FastAPI, status
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Optional[str] = None
+ price: float
+ tax: Optional[float] = None
+ tags: set[str] = set()
+
+
+@app.post("/items/", response_model=Item, status_code=status.HTTP_201_CREATED)
+async def create_item(item: Item):
+ return item
diff --git a/docs_src/path_operation_configuration/tutorial002.py b/docs_src/path_operation_configuration/tutorial002.py
index 01df041b..2df537d8 100644
--- a/docs_src/path_operation_configuration/tutorial002.py
+++ b/docs_src/path_operation_configuration/tutorial002.py
@@ -11,7 +11,7 @@ class Item(BaseModel):
description: Optional[str] = None
price: float
tax: Optional[float] = None
- tags: Set[str] = []
+ tags: Set[str] = set()
@app.post("/items/", response_model=Item, tags=["items"])
diff --git a/docs_src/path_operation_configuration/tutorial002_py310.py b/docs_src/path_operation_configuration/tutorial002_py310.py
new file mode 100644
index 00000000..9a8af543
--- /dev/null
+++ b/docs_src/path_operation_configuration/tutorial002_py310.py
@@ -0,0 +1,27 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+ tags: set[str] = set()
+
+
+@app.post("/items/", response_model=Item, tags=["items"])
+async def create_item(item: Item):
+ return item
+
+
+@app.get("/items/", tags=["items"])
+async def read_items():
+ return [{"name": "Foo", "price": 42}]
+
+
+@app.get("/users/", tags=["users"])
+async def read_users():
+ return [{"username": "johndoe"}]
diff --git a/docs_src/path_operation_configuration/tutorial002_py39.py b/docs_src/path_operation_configuration/tutorial002_py39.py
new file mode 100644
index 00000000..766d9fb0
--- /dev/null
+++ b/docs_src/path_operation_configuration/tutorial002_py39.py
@@ -0,0 +1,29 @@
+from typing import Optional
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Optional[str] = None
+ price: float
+ tax: Optional[float] = None
+ tags: set[str] = set()
+
+
+@app.post("/items/", response_model=Item, tags=["items"])
+async def create_item(item: Item):
+ return item
+
+
+@app.get("/items/", tags=["items"])
+async def read_items():
+ return [{"name": "Foo", "price": 42}]
+
+
+@app.get("/users/", tags=["users"])
+async def read_users():
+ return [{"username": "johndoe"}]
diff --git a/docs_src/path_operation_configuration/tutorial002b.py b/docs_src/path_operation_configuration/tutorial002b.py
new file mode 100644
index 00000000..d53b4d81
--- /dev/null
+++ b/docs_src/path_operation_configuration/tutorial002b.py
@@ -0,0 +1,20 @@
+from enum import Enum
+
+from fastapi import FastAPI
+
+app = FastAPI()
+
+
+class Tags(Enum):
+ items = "items"
+ users = "users"
+
+
+@app.get("/items/", tags=[Tags.items])
+async def get_items():
+ return ["Portal gun", "Plumbus"]
+
+
+@app.get("/users/", tags=[Tags.users])
+async def read_users():
+ return ["Rick", "Morty"]
diff --git a/docs_src/path_operation_configuration/tutorial003.py b/docs_src/path_operation_configuration/tutorial003.py
index 29bcb4a2..269a1a25 100644
--- a/docs_src/path_operation_configuration/tutorial003.py
+++ b/docs_src/path_operation_configuration/tutorial003.py
@@ -11,7 +11,7 @@ class Item(BaseModel):
description: Optional[str] = None
price: float
tax: Optional[float] = None
- tags: Set[str] = []
+ tags: Set[str] = set()
@app.post(
diff --git a/docs_src/path_operation_configuration/tutorial003_py310.py b/docs_src/path_operation_configuration/tutorial003_py310.py
new file mode 100644
index 00000000..3d94afe2
--- /dev/null
+++ b/docs_src/path_operation_configuration/tutorial003_py310.py
@@ -0,0 +1,22 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+ tags: set[str] = set()
+
+
+@app.post(
+ "/items/",
+ response_model=Item,
+ summary="Create an item",
+ description="Create an item with all the information, name, description, price, tax and a set of unique tags",
+)
+async def create_item(item: Item):
+ return item
diff --git a/docs_src/path_operation_configuration/tutorial003_py39.py b/docs_src/path_operation_configuration/tutorial003_py39.py
new file mode 100644
index 00000000..446198b5
--- /dev/null
+++ b/docs_src/path_operation_configuration/tutorial003_py39.py
@@ -0,0 +1,24 @@
+from typing import Optional
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Optional[str] = None
+ price: float
+ tax: Optional[float] = None
+ tags: set[str] = set()
+
+
+@app.post(
+ "/items/",
+ response_model=Item,
+ summary="Create an item",
+ description="Create an item with all the information, name, description, price, tax and a set of unique tags",
+)
+async def create_item(item: Item):
+ return item
diff --git a/docs_src/path_operation_configuration/tutorial004.py b/docs_src/path_operation_configuration/tutorial004.py
index ac95cc4b..de83be83 100644
--- a/docs_src/path_operation_configuration/tutorial004.py
+++ b/docs_src/path_operation_configuration/tutorial004.py
@@ -11,7 +11,7 @@ class Item(BaseModel):
description: Optional[str] = None
price: float
tax: Optional[float] = None
- tags: Set[str] = []
+ tags: Set[str] = set()
@app.post("/items/", response_model=Item, summary="Create an item")
diff --git a/docs_src/path_operation_configuration/tutorial004_py310.py b/docs_src/path_operation_configuration/tutorial004_py310.py
new file mode 100644
index 00000000..4cb8bdd4
--- /dev/null
+++ b/docs_src/path_operation_configuration/tutorial004_py310.py
@@ -0,0 +1,26 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+ tags: set[str] = set()
+
+
+@app.post("/items/", response_model=Item, summary="Create an item")
+async def create_item(item: Item):
+ """
+ Create an item with all the information:
+
+ - **name**: each item must have a name
+ - **description**: a long description
+ - **price**: required
+ - **tax**: if the item doesn't have tax, you can omit this
+ - **tags**: a set of unique tag strings for this item
+ """
+ return item
diff --git a/docs_src/path_operation_configuration/tutorial004_py39.py b/docs_src/path_operation_configuration/tutorial004_py39.py
new file mode 100644
index 00000000..bf6005b9
--- /dev/null
+++ b/docs_src/path_operation_configuration/tutorial004_py39.py
@@ -0,0 +1,28 @@
+from typing import Optional
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Optional[str] = None
+ price: float
+ tax: Optional[float] = None
+ tags: set[str] = set()
+
+
+@app.post("/items/", response_model=Item, summary="Create an item")
+async def create_item(item: Item):
+ """
+ Create an item with all the information:
+
+ - **name**: each item must have a name
+ - **description**: a long description
+ - **price**: required
+ - **tax**: if the item doesn't have tax, you can omit this
+ - **tags**: a set of unique tag strings for this item
+ """
+ return item
diff --git a/docs_src/path_operation_configuration/tutorial005.py b/docs_src/path_operation_configuration/tutorial005.py
index c1b80988..0f62c381 100644
--- a/docs_src/path_operation_configuration/tutorial005.py
+++ b/docs_src/path_operation_configuration/tutorial005.py
@@ -11,7 +11,7 @@ class Item(BaseModel):
description: Optional[str] = None
price: float
tax: Optional[float] = None
- tags: Set[str] = []
+ tags: Set[str] = set()
@app.post(
diff --git a/docs_src/path_operation_configuration/tutorial005_py310.py b/docs_src/path_operation_configuration/tutorial005_py310.py
new file mode 100644
index 00000000..b176631d
--- /dev/null
+++ b/docs_src/path_operation_configuration/tutorial005_py310.py
@@ -0,0 +1,31 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+ tags: set[str] = set()
+
+
+@app.post(
+ "/items/",
+ response_model=Item,
+ summary="Create an item",
+ response_description="The created item",
+)
+async def create_item(item: Item):
+ """
+ Create an item with all the information:
+
+ - **name**: each item must have a name
+ - **description**: a long description
+ - **price**: required
+ - **tax**: if the item doesn't have tax, you can omit this
+ - **tags**: a set of unique tag strings for this item
+ """
+ return item
diff --git a/docs_src/path_operation_configuration/tutorial005_py39.py b/docs_src/path_operation_configuration/tutorial005_py39.py
new file mode 100644
index 00000000..5ef32040
--- /dev/null
+++ b/docs_src/path_operation_configuration/tutorial005_py39.py
@@ -0,0 +1,33 @@
+from typing import Optional
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Optional[str] = None
+ price: float
+ tax: Optional[float] = None
+ tags: set[str] = set()
+
+
+@app.post(
+ "/items/",
+ response_model=Item,
+ summary="Create an item",
+ response_description="The created item",
+)
+async def create_item(item: Item):
+ """
+ Create an item with all the information:
+
+ - **name**: each item must have a name
+ - **description**: a long description
+ - **price**: required
+ - **tax**: if the item doesn't have tax, you can omit this
+ - **tags**: a set of unique tag strings for this item
+ """
+ return item
diff --git a/docs_src/path_params_numeric_validations/tutorial001_py310.py b/docs_src/path_params_numeric_validations/tutorial001_py310.py
new file mode 100644
index 00000000..b940a094
--- /dev/null
+++ b/docs_src/path_params_numeric_validations/tutorial001_py310.py
@@ -0,0 +1,14 @@
+from fastapi import FastAPI, Path, Query
+
+app = FastAPI()
+
+
+@app.get("/items/{item_id}")
+async def read_items(
+ item_id: int = Path(..., title="The ID of the item to get"),
+ q: str | None = Query(None, alias="item-query"),
+):
+ results = {"item_id": item_id}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/python_types/tutorial006_py39.py b/docs_src/python_types/tutorial006_py39.py
new file mode 100644
index 00000000..486b67ca
--- /dev/null
+++ b/docs_src/python_types/tutorial006_py39.py
@@ -0,0 +1,3 @@
+def process_items(items: list[str]):
+ for item in items:
+ print(item)
diff --git a/docs_src/python_types/tutorial007_py39.py b/docs_src/python_types/tutorial007_py39.py
new file mode 100644
index 00000000..ea96c796
--- /dev/null
+++ b/docs_src/python_types/tutorial007_py39.py
@@ -0,0 +1,2 @@
+def process_items(items_t: tuple[int, int, str], items_s: set[bytes]):
+ return items_t, items_s
diff --git a/docs_src/python_types/tutorial008_py39.py b/docs_src/python_types/tutorial008_py39.py
new file mode 100644
index 00000000..a393385b
--- /dev/null
+++ b/docs_src/python_types/tutorial008_py39.py
@@ -0,0 +1,4 @@
+def process_items(prices: dict[str, float]):
+ for item_name, item_price in prices.items():
+ print(item_name)
+ print(item_price)
diff --git a/docs_src/python_types/tutorial008b.py b/docs_src/python_types/tutorial008b.py
new file mode 100644
index 00000000..e52539ea
--- /dev/null
+++ b/docs_src/python_types/tutorial008b.py
@@ -0,0 +1,5 @@
+from typing import Union
+
+
+def process_item(item: Union[int, str]):
+ print(item)
diff --git a/docs_src/python_types/tutorial008b_py310.py b/docs_src/python_types/tutorial008b_py310.py
new file mode 100644
index 00000000..6bb43784
--- /dev/null
+++ b/docs_src/python_types/tutorial008b_py310.py
@@ -0,0 +1,2 @@
+def process_item(item: int | str):
+ print(item)
diff --git a/docs_src/python_types/tutorial009_py310.py b/docs_src/python_types/tutorial009_py310.py
new file mode 100644
index 00000000..8464c6ff
--- /dev/null
+++ b/docs_src/python_types/tutorial009_py310.py
@@ -0,0 +1,5 @@
+def say_hi(name: str | None = None):
+ if name is not None:
+ print(f"Hey {name}!")
+ else:
+ print("Hello World")
diff --git a/docs_src/python_types/tutorial009b.py b/docs_src/python_types/tutorial009b.py
new file mode 100644
index 00000000..9f1a05bc
--- /dev/null
+++ b/docs_src/python_types/tutorial009b.py
@@ -0,0 +1,8 @@
+from typing import Union
+
+
+def say_hi(name: Union[str, None] = None):
+ if name is not None:
+ print(f"Hey {name}!")
+ else:
+ print("Hello World")
diff --git a/docs_src/python_types/tutorial011_py310.py b/docs_src/python_types/tutorial011_py310.py
new file mode 100644
index 00000000..7f173880
--- /dev/null
+++ b/docs_src/python_types/tutorial011_py310.py
@@ -0,0 +1,22 @@
+from datetime import datetime
+
+from pydantic import BaseModel
+
+
+class User(BaseModel):
+ id: int
+ name = "John Doe"
+ signup_ts: datetime | None = None
+ friends: list[int] = []
+
+
+external_data = {
+ "id": "123",
+ "signup_ts": "2017-06-01 12:22",
+ "friends": [1, "2", b"3"],
+}
+user = User(**external_data)
+print(user)
+# > User id=123 name='John Doe' signup_ts=datetime.datetime(2017, 6, 1, 12, 22) friends=[1, 2, 3]
+print(user.id)
+# > 123
diff --git a/docs_src/python_types/tutorial011_py39.py b/docs_src/python_types/tutorial011_py39.py
new file mode 100644
index 00000000..af79e2df
--- /dev/null
+++ b/docs_src/python_types/tutorial011_py39.py
@@ -0,0 +1,23 @@
+from datetime import datetime
+from typing import Optional
+
+from pydantic import BaseModel
+
+
+class User(BaseModel):
+ id: int
+ name = "John Doe"
+ signup_ts: Optional[datetime] = None
+ friends: list[int] = []
+
+
+external_data = {
+ "id": "123",
+ "signup_ts": "2017-06-01 12:22",
+ "friends": [1, "2", b"3"],
+}
+user = User(**external_data)
+print(user)
+# > User id=123 name='John Doe' signup_ts=datetime.datetime(2017, 6, 1, 12, 22) friends=[1, 2, 3]
+print(user.id)
+# > 123
diff --git a/docs_src/query_params/tutorial002_py310.py b/docs_src/query_params/tutorial002_py310.py
new file mode 100644
index 00000000..bedb58ce
--- /dev/null
+++ b/docs_src/query_params/tutorial002_py310.py
@@ -0,0 +1,10 @@
+from fastapi import FastAPI
+
+app = FastAPI()
+
+
+@app.get("/items/{item_id}")
+async def read_item(item_id: str, q: str | None = None):
+ if q:
+ return {"item_id": item_id, "q": q}
+ return {"item_id": item_id}
diff --git a/docs_src/query_params/tutorial003_py310.py b/docs_src/query_params/tutorial003_py310.py
new file mode 100644
index 00000000..1eec66d9
--- /dev/null
+++ b/docs_src/query_params/tutorial003_py310.py
@@ -0,0 +1,15 @@
+from fastapi import FastAPI
+
+app = FastAPI()
+
+
+@app.get("/items/{item_id}")
+async def read_item(item_id: str, q: str | None = None, short: bool = False):
+ item = {"item_id": item_id}
+ if q:
+ item.update({"q": q})
+ if not short:
+ item.update(
+ {"description": "This is an amazing item that has a long description"}
+ )
+ return item
diff --git a/docs_src/query_params/tutorial004_py310.py b/docs_src/query_params/tutorial004_py310.py
new file mode 100644
index 00000000..8ec4338d
--- /dev/null
+++ b/docs_src/query_params/tutorial004_py310.py
@@ -0,0 +1,17 @@
+from fastapi import FastAPI
+
+app = FastAPI()
+
+
+@app.get("/users/{user_id}/items/{item_id}")
+async def read_user_item(
+ user_id: int, item_id: str, q: str | None = None, short: bool = False
+):
+ item = {"item_id": item_id, "owner_id": user_id}
+ if q:
+ item.update({"q": q})
+ if not short:
+ item.update(
+ {"description": "This is an amazing item that has a long description"}
+ )
+ return item
diff --git a/docs_src/query_params/tutorial006_py310.py b/docs_src/query_params/tutorial006_py310.py
new file mode 100644
index 00000000..be2a44c3
--- /dev/null
+++ b/docs_src/query_params/tutorial006_py310.py
@@ -0,0 +1,11 @@
+from fastapi import FastAPI
+
+app = FastAPI()
+
+
+@app.get("/items/{item_id}")
+async def read_user_item(
+ item_id: str, needy: str, skip: int = 0, limit: int | None = None
+):
+ item = {"item_id": item_id, "needy": needy, "skip": skip, "limit": limit}
+ return item
diff --git a/docs_src/query_params/tutorial006b.py b/docs_src/query_params/tutorial006b.py
new file mode 100644
index 00000000..f0dbfe08
--- /dev/null
+++ b/docs_src/query_params/tutorial006b.py
@@ -0,0 +1,13 @@
+from typing import Union
+
+from fastapi import FastAPI
+
+app = FastAPI()
+
+
+@app.get("/items/{item_id}")
+async def read_user_item(
+ item_id: str, needy: str, skip: int = 0, limit: Union[int, None] = None
+):
+ item = {"item_id": item_id, "needy": needy, "skip": skip, "limit": limit}
+ return item
diff --git a/docs_src/query_params_str_validations/tutorial001_py310.py b/docs_src/query_params_str_validations/tutorial001_py310.py
new file mode 100644
index 00000000..eeefddfa
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial001_py310.py
@@ -0,0 +1,11 @@
+from fastapi import FastAPI
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: str | None = None):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial002_py310.py b/docs_src/query_params_str_validations/tutorial002_py310.py
new file mode 100644
index 00000000..fa3139d5
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial002_py310.py
@@ -0,0 +1,11 @@
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: str | None = Query(None, max_length=50)):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial003_py310.py b/docs_src/query_params_str_validations/tutorial003_py310.py
new file mode 100644
index 00000000..335858a4
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial003_py310.py
@@ -0,0 +1,11 @@
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: str | None = Query(None, min_length=3, max_length=50)):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial004_py310.py b/docs_src/query_params_str_validations/tutorial004_py310.py
new file mode 100644
index 00000000..518b779f
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial004_py310.py
@@ -0,0 +1,13 @@
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(
+ q: str | None = Query(None, min_length=3, max_length=50, regex="^fixedquery$")
+):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial007_py310.py b/docs_src/query_params_str_validations/tutorial007_py310.py
new file mode 100644
index 00000000..14ef4cb6
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial007_py310.py
@@ -0,0 +1,11 @@
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: str | None = Query(None, title="Query string", min_length=3)):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial008_py310.py b/docs_src/query_params_str_validations/tutorial008_py310.py
new file mode 100644
index 00000000..06bb0244
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial008_py310.py
@@ -0,0 +1,19 @@
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(
+ q: str
+ | None = Query(
+ None,
+ title="Query string",
+ description="Query string for the items to search in the database that have a good match",
+ min_length=3,
+ )
+):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial009_py310.py b/docs_src/query_params_str_validations/tutorial009_py310.py
new file mode 100644
index 00000000..e84c116f
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial009_py310.py
@@ -0,0 +1,11 @@
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: str | None = Query(None, alias="item-query")):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial010_py310.py b/docs_src/query_params_str_validations/tutorial010_py310.py
new file mode 100644
index 00000000..c3580085
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial010_py310.py
@@ -0,0 +1,23 @@
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(
+ q: str
+ | None = Query(
+ None,
+ alias="item-query",
+ title="Query string",
+ description="Query string for the items to search in the database that have a good match",
+ min_length=3,
+ max_length=50,
+ regex="^fixedquery$",
+ deprecated=True,
+ )
+):
+ results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
+ if q:
+ results.update({"q": q})
+ return results
diff --git a/docs_src/query_params_str_validations/tutorial011_py310.py b/docs_src/query_params_str_validations/tutorial011_py310.py
new file mode 100644
index 00000000..c3d992e6
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial011_py310.py
@@ -0,0 +1,9 @@
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: list[str] | None = Query(None)):
+ query_items = {"q": q}
+ return query_items
diff --git a/docs_src/query_params_str_validations/tutorial011_py39.py b/docs_src/query_params_str_validations/tutorial011_py39.py
new file mode 100644
index 00000000..38ba764d
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial011_py39.py
@@ -0,0 +1,11 @@
+from typing import Optional
+
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: Optional[list[str]] = Query(None)):
+ query_items = {"q": q}
+ return query_items
diff --git a/docs_src/query_params_str_validations/tutorial012_py39.py b/docs_src/query_params_str_validations/tutorial012_py39.py
new file mode 100644
index 00000000..1900133d
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial012_py39.py
@@ -0,0 +1,9 @@
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(q: list[str] = Query(["foo", "bar"])):
+ query_items = {"q": q}
+ return query_items
diff --git a/docs_src/query_params_str_validations/tutorial014.py b/docs_src/query_params_str_validations/tutorial014.py
new file mode 100644
index 00000000..fb50bc27
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial014.py
@@ -0,0 +1,15 @@
+from typing import Optional
+
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(
+ hidden_query: Optional[str] = Query(None, include_in_schema=False)
+):
+ if hidden_query:
+ return {"hidden_query": hidden_query}
+ else:
+ return {"hidden_query": "Not found"}
diff --git a/docs_src/query_params_str_validations/tutorial014_py310.py b/docs_src/query_params_str_validations/tutorial014_py310.py
new file mode 100644
index 00000000..7ae39c7f
--- /dev/null
+++ b/docs_src/query_params_str_validations/tutorial014_py310.py
@@ -0,0 +1,11 @@
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(hidden_query: str | None = Query(None, include_in_schema=False)):
+ if hidden_query:
+ return {"hidden_query": hidden_query}
+ else:
+ return {"hidden_query": "Not found"}
diff --git a/docs_src/request_files/tutorial001.py b/docs_src/request_files/tutorial001.py
index fffb56af..0fb1dd57 100644
--- a/docs_src/request_files/tutorial001.py
+++ b/docs_src/request_files/tutorial001.py
@@ -9,5 +9,5 @@ async def create_file(file: bytes = File(...)):
@app.post("/uploadfile/")
-async def create_upload_file(file: UploadFile = File(...)):
+async def create_upload_file(file: UploadFile):
return {"filename": file.filename}
diff --git a/docs_src/request_files/tutorial001_02.py b/docs_src/request_files/tutorial001_02.py
new file mode 100644
index 00000000..26a4c9cb
--- /dev/null
+++ b/docs_src/request_files/tutorial001_02.py
@@ -0,0 +1,21 @@
+from typing import Optional
+
+from fastapi import FastAPI, File, UploadFile
+
+app = FastAPI()
+
+
+@app.post("/files/")
+async def create_file(file: Optional[bytes] = File(None)):
+ if not file:
+ return {"message": "No file sent"}
+ else:
+ return {"file_size": len(file)}
+
+
+@app.post("/uploadfile/")
+async def create_upload_file(file: Optional[UploadFile] = None):
+ if not file:
+ return {"message": "No upload file sent"}
+ else:
+ return {"filename": file.filename}
diff --git a/docs_src/request_files/tutorial001_02_py310.py b/docs_src/request_files/tutorial001_02_py310.py
new file mode 100644
index 00000000..0e576251
--- /dev/null
+++ b/docs_src/request_files/tutorial001_02_py310.py
@@ -0,0 +1,19 @@
+from fastapi import FastAPI, File, UploadFile
+
+app = FastAPI()
+
+
+@app.post("/files/")
+async def create_file(file: bytes | None = File(None)):
+ if not file:
+ return {"message": "No file sent"}
+ else:
+ return {"file_size": len(file)}
+
+
+@app.post("/uploadfile/")
+async def create_upload_file(file: UploadFile | None = None):
+ if not file:
+ return {"message": "No upload file sent"}
+ else:
+ return {"filename": file.filename}
diff --git a/docs_src/request_files/tutorial001_03.py b/docs_src/request_files/tutorial001_03.py
new file mode 100644
index 00000000..abcac9e4
--- /dev/null
+++ b/docs_src/request_files/tutorial001_03.py
@@ -0,0 +1,15 @@
+from fastapi import FastAPI, File, UploadFile
+
+app = FastAPI()
+
+
+@app.post("/files/")
+async def create_file(file: bytes = File(..., description="A file read as bytes")):
+ return {"file_size": len(file)}
+
+
+@app.post("/uploadfile/")
+async def create_upload_file(
+ file: UploadFile = File(..., description="A file read as UploadFile")
+):
+ return {"filename": file.filename}
diff --git a/docs_src/request_files/tutorial002.py b/docs_src/request_files/tutorial002.py
index 6fdf16a7..94abb7c6 100644
--- a/docs_src/request_files/tutorial002.py
+++ b/docs_src/request_files/tutorial002.py
@@ -12,7 +12,7 @@ async def create_files(files: List[bytes] = File(...)):
@app.post("/uploadfiles/")
-async def create_upload_files(files: List[UploadFile] = File(...)):
+async def create_upload_files(files: List[UploadFile]):
return {"filenames": [file.filename for file in files]}
diff --git a/docs_src/request_files/tutorial002_py39.py b/docs_src/request_files/tutorial002_py39.py
new file mode 100644
index 00000000..2779618b
--- /dev/null
+++ b/docs_src/request_files/tutorial002_py39.py
@@ -0,0 +1,31 @@
+from fastapi import FastAPI, File, UploadFile
+from fastapi.responses import HTMLResponse
+
+app = FastAPI()
+
+
+@app.post("/files/")
+async def create_files(files: list[bytes] = File(...)):
+ return {"file_sizes": [len(file) for file in files]}
+
+
+@app.post("/uploadfiles/")
+async def create_upload_files(files: list[UploadFile]):
+ return {"filenames": [file.filename for file in files]}
+
+
+@app.get("/")
+async def main():
+ content = """
+
+
+
+
+ """
+ return HTMLResponse(content=content)
diff --git a/docs_src/request_files/tutorial003.py b/docs_src/request_files/tutorial003.py
new file mode 100644
index 00000000..4a91b7a8
--- /dev/null
+++ b/docs_src/request_files/tutorial003.py
@@ -0,0 +1,37 @@
+from typing import List
+
+from fastapi import FastAPI, File, UploadFile
+from fastapi.responses import HTMLResponse
+
+app = FastAPI()
+
+
+@app.post("/files/")
+async def create_files(
+ files: List[bytes] = File(..., description="Multiple files as bytes")
+):
+ return {"file_sizes": [len(file) for file in files]}
+
+
+@app.post("/uploadfiles/")
+async def create_upload_files(
+ files: List[UploadFile] = File(..., description="Multiple files as UploadFile")
+):
+ return {"filenames": [file.filename for file in files]}
+
+
+@app.get("/")
+async def main():
+ content = """
+
+
+
+
+ """
+ return HTMLResponse(content=content)
diff --git a/docs_src/request_files/tutorial003_py39.py b/docs_src/request_files/tutorial003_py39.py
new file mode 100644
index 00000000..d853f48d
--- /dev/null
+++ b/docs_src/request_files/tutorial003_py39.py
@@ -0,0 +1,35 @@
+from fastapi import FastAPI, File, UploadFile
+from fastapi.responses import HTMLResponse
+
+app = FastAPI()
+
+
+@app.post("/files/")
+async def create_files(
+ files: list[bytes] = File(..., description="Multiple files as bytes")
+):
+ return {"file_sizes": [len(file) for file in files]}
+
+
+@app.post("/uploadfiles/")
+async def create_upload_files(
+ files: list[UploadFile] = File(..., description="Multiple files as UploadFile")
+):
+ return {"filenames": [file.filename for file in files]}
+
+
+@app.get("/")
+async def main():
+ content = """
+
+
+
+
+ """
+ return HTMLResponse(content=content)
diff --git a/docs_src/response_model/tutorial001_py310.py b/docs_src/response_model/tutorial001_py310.py
new file mode 100644
index 00000000..59efecde
--- /dev/null
+++ b/docs_src/response_model/tutorial001_py310.py
@@ -0,0 +1,17 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+ tags: list[str] = []
+
+
+@app.post("/items/", response_model=Item)
+async def create_item(item: Item):
+ return item
diff --git a/docs_src/response_model/tutorial001_py39.py b/docs_src/response_model/tutorial001_py39.py
new file mode 100644
index 00000000..37b86686
--- /dev/null
+++ b/docs_src/response_model/tutorial001_py39.py
@@ -0,0 +1,19 @@
+from typing import Optional
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Optional[str] = None
+ price: float
+ tax: Optional[float] = None
+ tags: list[str] = []
+
+
+@app.post("/items/", response_model=Item)
+async def create_item(item: Item):
+ return item
diff --git a/docs_src/response_model/tutorial002_py310.py b/docs_src/response_model/tutorial002_py310.py
new file mode 100644
index 00000000..29ab9c9d
--- /dev/null
+++ b/docs_src/response_model/tutorial002_py310.py
@@ -0,0 +1,17 @@
+from fastapi import FastAPI
+from pydantic import BaseModel, EmailStr
+
+app = FastAPI()
+
+
+class UserIn(BaseModel):
+ username: str
+ password: str
+ email: EmailStr
+ full_name: str | None = None
+
+
+# Don't do this in production!
+@app.post("/user/", response_model=UserIn)
+async def create_user(user: UserIn):
+ return user
diff --git a/docs_src/response_model/tutorial003_py310.py b/docs_src/response_model/tutorial003_py310.py
new file mode 100644
index 00000000..fc9693e3
--- /dev/null
+++ b/docs_src/response_model/tutorial003_py310.py
@@ -0,0 +1,22 @@
+from fastapi import FastAPI
+from pydantic import BaseModel, EmailStr
+
+app = FastAPI()
+
+
+class UserIn(BaseModel):
+ username: str
+ password: str
+ email: EmailStr
+ full_name: str | None = None
+
+
+class UserOut(BaseModel):
+ username: str
+ email: EmailStr
+ full_name: str | None = None
+
+
+@app.post("/user/", response_model=UserOut)
+async def create_user(user: UserIn):
+ return user
diff --git a/docs_src/response_model/tutorial004_py310.py b/docs_src/response_model/tutorial004_py310.py
new file mode 100644
index 00000000..a3e21b43
--- /dev/null
+++ b/docs_src/response_model/tutorial004_py310.py
@@ -0,0 +1,24 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float = 10.5
+ tags: list[str] = []
+
+
+items = {
+ "foo": {"name": "Foo", "price": 50.2},
+ "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
+ "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
+}
+
+
+@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
+async def read_item(item_id: str):
+ return items[item_id]
diff --git a/docs_src/response_model/tutorial004_py39.py b/docs_src/response_model/tutorial004_py39.py
new file mode 100644
index 00000000..07ccbbf4
--- /dev/null
+++ b/docs_src/response_model/tutorial004_py39.py
@@ -0,0 +1,26 @@
+from typing import Optional
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: Optional[str] = None
+ price: float
+ tax: float = 10.5
+ tags: list[str] = []
+
+
+items = {
+ "foo": {"name": "Foo", "price": 50.2},
+ "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
+ "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
+}
+
+
+@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
+async def read_item(item_id: str):
+ return items[item_id]
diff --git a/docs_src/response_model/tutorial005_py310.py b/docs_src/response_model/tutorial005_py310.py
new file mode 100644
index 00000000..acb3035b
--- /dev/null
+++ b/docs_src/response_model/tutorial005_py310.py
@@ -0,0 +1,37 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float = 10.5
+
+
+items = {
+ "foo": {"name": "Foo", "price": 50.2},
+ "bar": {"name": "Bar", "description": "The Bar fighters", "price": 62, "tax": 20.2},
+ "baz": {
+ "name": "Baz",
+ "description": "There goes my baz",
+ "price": 50.2,
+ "tax": 10.5,
+ },
+}
+
+
+@app.get(
+ "/items/{item_id}/name",
+ response_model=Item,
+ response_model_include={"name", "description"},
+)
+async def read_item_name(item_id: str):
+ return items[item_id]
+
+
+@app.get("/items/{item_id}/public", response_model=Item, response_model_exclude={"tax"})
+async def read_item_public_data(item_id: str):
+ return items[item_id]
diff --git a/docs_src/response_model/tutorial006_py310.py b/docs_src/response_model/tutorial006_py310.py
new file mode 100644
index 00000000..9ccec324
--- /dev/null
+++ b/docs_src/response_model/tutorial006_py310.py
@@ -0,0 +1,37 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float = 10.5
+
+
+items = {
+ "foo": {"name": "Foo", "price": 50.2},
+ "bar": {"name": "Bar", "description": "The Bar fighters", "price": 62, "tax": 20.2},
+ "baz": {
+ "name": "Baz",
+ "description": "There goes my baz",
+ "price": 50.2,
+ "tax": 10.5,
+ },
+}
+
+
+@app.get(
+ "/items/{item_id}/name",
+ response_model=Item,
+ response_model_include=["name", "description"],
+)
+async def read_item_name(item_id: str):
+ return items[item_id]
+
+
+@app.get("/items/{item_id}/public", response_model=Item, response_model_exclude=["tax"])
+async def read_item_public_data(item_id: str):
+ return items[item_id]
diff --git a/docs_src/schema_extra_example/tutorial001_py310.py b/docs_src/schema_extra_example/tutorial001_py310.py
new file mode 100644
index 00000000..77ceedd6
--- /dev/null
+++ b/docs_src/schema_extra_example/tutorial001_py310.py
@@ -0,0 +1,27 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+
+ class Config:
+ schema_extra = {
+ "example": {
+ "name": "Foo",
+ "description": "A very nice Item",
+ "price": 35.4,
+ "tax": 3.2,
+ }
+ }
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Item):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/schema_extra_example/tutorial002_py310.py b/docs_src/schema_extra_example/tutorial002_py310.py
new file mode 100644
index 00000000..4f8f8304
--- /dev/null
+++ b/docs_src/schema_extra_example/tutorial002_py310.py
@@ -0,0 +1,17 @@
+from fastapi import FastAPI
+from pydantic import BaseModel, Field
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str = Field(..., example="Foo")
+ description: str | None = Field(None, example="A very nice Item")
+ price: float = Field(..., example=35.4)
+ tax: float | None = Field(None, example=3.2)
+
+
+@app.put("/items/{item_id}")
+async def update_item(item_id: int, item: Item):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/schema_extra_example/tutorial003_py310.py b/docs_src/schema_extra_example/tutorial003_py310.py
new file mode 100644
index 00000000..cf4c99dc
--- /dev/null
+++ b/docs_src/schema_extra_example/tutorial003_py310.py
@@ -0,0 +1,28 @@
+from fastapi import Body, FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(
+ item_id: int,
+ item: Item = Body(
+ ...,
+ example={
+ "name": "Foo",
+ "description": "A very nice Item",
+ "price": 35.4,
+ "tax": 3.2,
+ },
+ ),
+):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/schema_extra_example/tutorial004_py310.py b/docs_src/schema_extra_example/tutorial004_py310.py
new file mode 100644
index 00000000..6f29c1a5
--- /dev/null
+++ b/docs_src/schema_extra_example/tutorial004_py310.py
@@ -0,0 +1,50 @@
+from fastapi import Body, FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str | None = None
+ price: float
+ tax: float | None = None
+
+
+@app.put("/items/{item_id}")
+async def update_item(
+ *,
+ item_id: int,
+ item: Item = Body(
+ ...,
+ 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",
+ },
+ },
+ },
+ ),
+):
+ results = {"item_id": item_id, "item": item}
+ return results
diff --git a/docs_src/security/tutorial002_py310.py b/docs_src/security/tutorial002_py310.py
new file mode 100644
index 00000000..3c2018f5
--- /dev/null
+++ b/docs_src/security/tutorial002_py310.py
@@ -0,0 +1,30 @@
+from fastapi import Depends, FastAPI
+from fastapi.security import OAuth2PasswordBearer
+from pydantic import BaseModel
+
+app = FastAPI()
+
+oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
+
+
+class User(BaseModel):
+ username: str
+ email: str | None = None
+ full_name: str | None = None
+ disabled: bool | None = None
+
+
+def fake_decode_token(token):
+ return User(
+ username=token + "fakedecoded", email="john@example.com", full_name="John Doe"
+ )
+
+
+async def get_current_user(token: str = Depends(oauth2_scheme)):
+ user = fake_decode_token(token)
+ return user
+
+
+@app.get("/users/me")
+async def read_users_me(current_user: User = Depends(get_current_user)):
+ return current_user
diff --git a/docs_src/security/tutorial003_py310.py b/docs_src/security/tutorial003_py310.py
new file mode 100644
index 00000000..af935e99
--- /dev/null
+++ b/docs_src/security/tutorial003_py310.py
@@ -0,0 +1,88 @@
+from fastapi import Depends, FastAPI, HTTPException, status
+from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
+from pydantic import BaseModel
+
+fake_users_db = {
+ "johndoe": {
+ "username": "johndoe",
+ "full_name": "John Doe",
+ "email": "johndoe@example.com",
+ "hashed_password": "fakehashedsecret",
+ "disabled": False,
+ },
+ "alice": {
+ "username": "alice",
+ "full_name": "Alice Wonderson",
+ "email": "alice@example.com",
+ "hashed_password": "fakehashedsecret2",
+ "disabled": True,
+ },
+}
+
+app = FastAPI()
+
+
+def fake_hash_password(password: str):
+ return "fakehashed" + password
+
+
+oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
+
+
+class User(BaseModel):
+ username: str
+ email: str | None = None
+ full_name: str | None = None
+ disabled: bool | None = None
+
+
+class UserInDB(User):
+ hashed_password: str
+
+
+def get_user(db, username: str):
+ if username in db:
+ user_dict = db[username]
+ return UserInDB(**user_dict)
+
+
+def fake_decode_token(token):
+ # This doesn't provide any security at all
+ # Check the next version
+ user = get_user(fake_users_db, token)
+ return user
+
+
+async def get_current_user(token: str = Depends(oauth2_scheme)):
+ user = fake_decode_token(token)
+ if not user:
+ raise HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Invalid authentication credentials",
+ headers={"WWW-Authenticate": "Bearer"},
+ )
+ return user
+
+
+async def get_current_active_user(current_user: User = Depends(get_current_user)):
+ if current_user.disabled:
+ raise HTTPException(status_code=400, detail="Inactive user")
+ return current_user
+
+
+@app.post("/token")
+async def login(form_data: OAuth2PasswordRequestForm = Depends()):
+ user_dict = fake_users_db.get(form_data.username)
+ if not user_dict:
+ raise HTTPException(status_code=400, detail="Incorrect username or password")
+ user = UserInDB(**user_dict)
+ hashed_password = fake_hash_password(form_data.password)
+ if not hashed_password == user.hashed_password:
+ raise HTTPException(status_code=400, detail="Incorrect username or password")
+
+ return {"access_token": user.username, "token_type": "bearer"}
+
+
+@app.get("/users/me")
+async def read_users_me(current_user: User = Depends(get_current_active_user)):
+ return current_user
diff --git a/docs_src/security/tutorial004_py310.py b/docs_src/security/tutorial004_py310.py
new file mode 100644
index 00000000..797d56d0
--- /dev/null
+++ b/docs_src/security/tutorial004_py310.py
@@ -0,0 +1,137 @@
+from datetime import datetime, timedelta
+
+from fastapi import Depends, FastAPI, HTTPException, status
+from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
+from jose import JWTError, jwt
+from passlib.context import CryptContext
+from pydantic import BaseModel
+
+# to get a string like this run:
+# openssl rand -hex 32
+SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
+ALGORITHM = "HS256"
+ACCESS_TOKEN_EXPIRE_MINUTES = 30
+
+
+fake_users_db = {
+ "johndoe": {
+ "username": "johndoe",
+ "full_name": "John Doe",
+ "email": "johndoe@example.com",
+ "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
+ "disabled": False,
+ }
+}
+
+
+class Token(BaseModel):
+ access_token: str
+ token_type: str
+
+
+class TokenData(BaseModel):
+ username: str | None = None
+
+
+class User(BaseModel):
+ username: str
+ email: str | None = None
+ full_name: str | None = None
+ disabled: bool | None = None
+
+
+class UserInDB(User):
+ hashed_password: str
+
+
+pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
+
+oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
+
+app = FastAPI()
+
+
+def verify_password(plain_password, hashed_password):
+ return pwd_context.verify(plain_password, hashed_password)
+
+
+def get_password_hash(password):
+ return pwd_context.hash(password)
+
+
+def get_user(db, username: str):
+ if username in db:
+ user_dict = db[username]
+ return UserInDB(**user_dict)
+
+
+def authenticate_user(fake_db, username: str, password: str):
+ user = get_user(fake_db, username)
+ if not user:
+ return False
+ if not verify_password(password, user.hashed_password):
+ return False
+ return user
+
+
+def create_access_token(data: dict, expires_delta: timedelta | None = None):
+ to_encode = data.copy()
+ if expires_delta:
+ expire = datetime.utcnow() + expires_delta
+ else:
+ expire = datetime.utcnow() + timedelta(minutes=15)
+ to_encode.update({"exp": expire})
+ encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
+ return encoded_jwt
+
+
+async def get_current_user(token: str = Depends(oauth2_scheme)):
+ credentials_exception = HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Could not validate credentials",
+ headers={"WWW-Authenticate": "Bearer"},
+ )
+ try:
+ payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
+ username: str = payload.get("sub")
+ if username is None:
+ raise credentials_exception
+ token_data = TokenData(username=username)
+ except JWTError:
+ raise credentials_exception
+ user = get_user(fake_users_db, username=token_data.username)
+ if user is None:
+ raise credentials_exception
+ return user
+
+
+async def get_current_active_user(current_user: User = Depends(get_current_user)):
+ if current_user.disabled:
+ raise HTTPException(status_code=400, detail="Inactive user")
+ return current_user
+
+
+@app.post("/token", response_model=Token)
+async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
+ user = authenticate_user(fake_users_db, form_data.username, form_data.password)
+ if not user:
+ raise HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Incorrect username or password",
+ headers={"WWW-Authenticate": "Bearer"},
+ )
+ access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
+ access_token = create_access_token(
+ data={"sub": user.username}, expires_delta=access_token_expires
+ )
+ return {"access_token": access_token, "token_type": "bearer"}
+
+
+@app.get("/users/me/", response_model=User)
+async def read_users_me(current_user: User = Depends(get_current_active_user)):
+ return current_user
+
+
+@app.get("/users/me/items/")
+async def read_own_items(current_user: User = Depends(get_current_active_user)):
+ return [{"item_id": "Foo", "owner": current_user.username}]
diff --git a/docs_src/security/tutorial005_py310.py b/docs_src/security/tutorial005_py310.py
new file mode 100644
index 00000000..c6a095d2
--- /dev/null
+++ b/docs_src/security/tutorial005_py310.py
@@ -0,0 +1,172 @@
+from datetime import datetime, timedelta
+
+from fastapi import Depends, FastAPI, HTTPException, Security, status
+from fastapi.security import (
+ OAuth2PasswordBearer,
+ OAuth2PasswordRequestForm,
+ SecurityScopes,
+)
+from jose import JWTError, jwt
+from passlib.context import CryptContext
+from pydantic import BaseModel, ValidationError
+
+# to get a string like this run:
+# openssl rand -hex 32
+SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
+ALGORITHM = "HS256"
+ACCESS_TOKEN_EXPIRE_MINUTES = 30
+
+
+fake_users_db = {
+ "johndoe": {
+ "username": "johndoe",
+ "full_name": "John Doe",
+ "email": "johndoe@example.com",
+ "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
+ "disabled": False,
+ },
+ "alice": {
+ "username": "alice",
+ "full_name": "Alice Chains",
+ "email": "alicechains@example.com",
+ "hashed_password": "$2b$12$gSvqqUPvlXP2tfVFaWK1Be7DlH.PKZbv5H8KnzzVgXXbVxpva.pFm",
+ "disabled": True,
+ },
+}
+
+
+class Token(BaseModel):
+ access_token: str
+ token_type: str
+
+
+class TokenData(BaseModel):
+ username: str | None = None
+ scopes: list[str] = []
+
+
+class User(BaseModel):
+ username: str
+ email: str | None = None
+ full_name: str | None = None
+ disabled: bool | None = None
+
+
+class UserInDB(User):
+ hashed_password: str
+
+
+pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
+
+oauth2_scheme = OAuth2PasswordBearer(
+ tokenUrl="token",
+ scopes={"me": "Read information about the current user.", "items": "Read items."},
+)
+
+app = FastAPI()
+
+
+def verify_password(plain_password, hashed_password):
+ return pwd_context.verify(plain_password, hashed_password)
+
+
+def get_password_hash(password):
+ return pwd_context.hash(password)
+
+
+def get_user(db, username: str):
+ if username in db:
+ user_dict = db[username]
+ return UserInDB(**user_dict)
+
+
+def authenticate_user(fake_db, username: str, password: str):
+ user = get_user(fake_db, username)
+ if not user:
+ return False
+ if not verify_password(password, user.hashed_password):
+ return False
+ return user
+
+
+def create_access_token(data: dict, expires_delta: timedelta | None = None):
+ to_encode = data.copy()
+ if expires_delta:
+ expire = datetime.utcnow() + expires_delta
+ else:
+ expire = datetime.utcnow() + timedelta(minutes=15)
+ to_encode.update({"exp": expire})
+ encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
+ return encoded_jwt
+
+
+async def get_current_user(
+ security_scopes: SecurityScopes, token: str = Depends(oauth2_scheme)
+):
+ if security_scopes.scopes:
+ authenticate_value = f'Bearer scope="{security_scopes.scope_str}"'
+ else:
+ authenticate_value = f"Bearer"
+ credentials_exception = HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Could not validate credentials",
+ headers={"WWW-Authenticate": authenticate_value},
+ )
+ try:
+ payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
+ username: str = payload.get("sub")
+ if username is None:
+ raise credentials_exception
+ token_scopes = payload.get("scopes", [])
+ token_data = TokenData(scopes=token_scopes, username=username)
+ except (JWTError, ValidationError):
+ raise credentials_exception
+ user = get_user(fake_users_db, username=token_data.username)
+ if user is None:
+ raise credentials_exception
+ for scope in security_scopes.scopes:
+ if scope not in token_data.scopes:
+ raise HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Not enough permissions",
+ headers={"WWW-Authenticate": authenticate_value},
+ )
+ return user
+
+
+async def get_current_active_user(
+ current_user: User = Security(get_current_user, scopes=["me"])
+):
+ if current_user.disabled:
+ raise HTTPException(status_code=400, detail="Inactive user")
+ return current_user
+
+
+@app.post("/token", response_model=Token)
+async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
+ user = authenticate_user(fake_users_db, form_data.username, form_data.password)
+ if not user:
+ raise HTTPException(status_code=400, detail="Incorrect username or password")
+ access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
+ access_token = create_access_token(
+ data={"sub": user.username, "scopes": form_data.scopes},
+ expires_delta=access_token_expires,
+ )
+ return {"access_token": access_token, "token_type": "bearer"}
+
+
+@app.get("/users/me/", response_model=User)
+async def read_users_me(current_user: User = Depends(get_current_active_user)):
+ return current_user
+
+
+@app.get("/users/me/items/")
+async def read_own_items(
+ current_user: User = Security(get_current_active_user, scopes=["items"])
+):
+ return [{"item_id": "Foo", "owner": current_user.username}]
+
+
+@app.get("/status/")
+async def read_system_status(current_user: User = Depends(get_current_user)):
+ return {"status": "ok"}
diff --git a/docs_src/security/tutorial005_py39.py b/docs_src/security/tutorial005_py39.py
new file mode 100644
index 00000000..d45c08ce
--- /dev/null
+++ b/docs_src/security/tutorial005_py39.py
@@ -0,0 +1,173 @@
+from datetime import datetime, timedelta
+from typing import Optional
+
+from fastapi import Depends, FastAPI, HTTPException, Security, status
+from fastapi.security import (
+ OAuth2PasswordBearer,
+ OAuth2PasswordRequestForm,
+ SecurityScopes,
+)
+from jose import JWTError, jwt
+from passlib.context import CryptContext
+from pydantic import BaseModel, ValidationError
+
+# to get a string like this run:
+# openssl rand -hex 32
+SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
+ALGORITHM = "HS256"
+ACCESS_TOKEN_EXPIRE_MINUTES = 30
+
+
+fake_users_db = {
+ "johndoe": {
+ "username": "johndoe",
+ "full_name": "John Doe",
+ "email": "johndoe@example.com",
+ "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
+ "disabled": False,
+ },
+ "alice": {
+ "username": "alice",
+ "full_name": "Alice Chains",
+ "email": "alicechains@example.com",
+ "hashed_password": "$2b$12$gSvqqUPvlXP2tfVFaWK1Be7DlH.PKZbv5H8KnzzVgXXbVxpva.pFm",
+ "disabled": True,
+ },
+}
+
+
+class Token(BaseModel):
+ access_token: str
+ token_type: str
+
+
+class TokenData(BaseModel):
+ username: Optional[str] = None
+ scopes: list[str] = []
+
+
+class User(BaseModel):
+ username: str
+ email: Optional[str] = None
+ full_name: Optional[str] = None
+ disabled: Optional[bool] = None
+
+
+class UserInDB(User):
+ hashed_password: str
+
+
+pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
+
+oauth2_scheme = OAuth2PasswordBearer(
+ tokenUrl="token",
+ scopes={"me": "Read information about the current user.", "items": "Read items."},
+)
+
+app = FastAPI()
+
+
+def verify_password(plain_password, hashed_password):
+ return pwd_context.verify(plain_password, hashed_password)
+
+
+def get_password_hash(password):
+ return pwd_context.hash(password)
+
+
+def get_user(db, username: str):
+ if username in db:
+ user_dict = db[username]
+ return UserInDB(**user_dict)
+
+
+def authenticate_user(fake_db, username: str, password: str):
+ user = get_user(fake_db, username)
+ if not user:
+ return False
+ if not verify_password(password, user.hashed_password):
+ return False
+ return user
+
+
+def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
+ to_encode = data.copy()
+ if expires_delta:
+ expire = datetime.utcnow() + expires_delta
+ else:
+ expire = datetime.utcnow() + timedelta(minutes=15)
+ to_encode.update({"exp": expire})
+ encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
+ return encoded_jwt
+
+
+async def get_current_user(
+ security_scopes: SecurityScopes, token: str = Depends(oauth2_scheme)
+):
+ if security_scopes.scopes:
+ authenticate_value = f'Bearer scope="{security_scopes.scope_str}"'
+ else:
+ authenticate_value = f"Bearer"
+ credentials_exception = HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Could not validate credentials",
+ headers={"WWW-Authenticate": authenticate_value},
+ )
+ try:
+ payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
+ username: str = payload.get("sub")
+ if username is None:
+ raise credentials_exception
+ token_scopes = payload.get("scopes", [])
+ token_data = TokenData(scopes=token_scopes, username=username)
+ except (JWTError, ValidationError):
+ raise credentials_exception
+ user = get_user(fake_users_db, username=token_data.username)
+ if user is None:
+ raise credentials_exception
+ for scope in security_scopes.scopes:
+ if scope not in token_data.scopes:
+ raise HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Not enough permissions",
+ headers={"WWW-Authenticate": authenticate_value},
+ )
+ return user
+
+
+async def get_current_active_user(
+ current_user: User = Security(get_current_user, scopes=["me"])
+):
+ if current_user.disabled:
+ raise HTTPException(status_code=400, detail="Inactive user")
+ return current_user
+
+
+@app.post("/token", response_model=Token)
+async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
+ user = authenticate_user(fake_users_db, form_data.username, form_data.password)
+ if not user:
+ raise HTTPException(status_code=400, detail="Incorrect username or password")
+ access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
+ access_token = create_access_token(
+ data={"sub": user.username, "scopes": form_data.scopes},
+ expires_delta=access_token_expires,
+ )
+ return {"access_token": access_token, "token_type": "bearer"}
+
+
+@app.get("/users/me/", response_model=User)
+async def read_users_me(current_user: User = Depends(get_current_active_user)):
+ return current_user
+
+
+@app.get("/users/me/items/")
+async def read_own_items(
+ current_user: User = Security(get_current_active_user, scopes=["items"])
+):
+ return [{"item_id": "Foo", "owner": current_user.username}]
+
+
+@app.get("/status/")
+async def read_system_status(current_user: User = Depends(get_current_user)):
+ return {"status": "ok"}
diff --git a/docs_src/settings/app01/main.py b/docs_src/settings/app01/main.py
index 53503797..4a3a86ce 100644
--- a/docs_src/settings/app01/main.py
+++ b/docs_src/settings/app01/main.py
@@ -1,6 +1,6 @@
from fastapi import FastAPI
-from . import config
+from .config import settings
app = FastAPI()
@@ -8,7 +8,7 @@ app = FastAPI()
@app.get("/info")
async def info():
return {
- "app_name": config.settings.app_name,
- "admin_email": config.settings.admin_email,
- "items_per_user": config.settings.items_per_user,
+ "app_name": settings.app_name,
+ "admin_email": settings.admin_email,
+ "items_per_user": settings.items_per_user,
}
diff --git a/docs_src/settings/app02/main.py b/docs_src/settings/app02/main.py
index 69bc8c6e..163aa261 100644
--- a/docs_src/settings/app02/main.py
+++ b/docs_src/settings/app02/main.py
@@ -2,18 +2,18 @@ from functools import lru_cache
from fastapi import Depends, FastAPI
-from . import config
+from .config import Settings
app = FastAPI()
@lru_cache()
def get_settings():
- return config.Settings()
+ return Settings()
@app.get("/info")
-async def info(settings: config.Settings = Depends(get_settings)):
+async def info(settings: Settings = Depends(get_settings)):
return {
"app_name": settings.app_name,
"admin_email": settings.admin_email,
diff --git a/docs_src/settings/app02/test_main.py b/docs_src/settings/app02/test_main.py
index 44d9f837..7a04d7e8 100644
--- a/docs_src/settings/app02/test_main.py
+++ b/docs_src/settings/app02/test_main.py
@@ -1,19 +1,19 @@
from fastapi.testclient import TestClient
-from . import config, main
+from .config import Settings
+from .main import app, get_settings
-client = TestClient(main.app)
+client = TestClient(app)
def get_settings_override():
- return config.Settings(admin_email="testing_admin@example.com")
+ return Settings(admin_email="testing_admin@example.com")
-main.app.dependency_overrides[main.get_settings] = get_settings_override
+app.dependency_overrides[get_settings] = get_settings_override
def test_app():
-
response = client.get("/info")
data = response.json()
assert data == {
diff --git a/docs_src/sql_databases/sql_app_py310/__init__.py b/docs_src/sql_databases/sql_app_py310/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/docs_src/sql_databases/sql_app_py310/alt_main.py b/docs_src/sql_databases/sql_app_py310/alt_main.py
new file mode 100644
index 00000000..5de88ec3
--- /dev/null
+++ b/docs_src/sql_databases/sql_app_py310/alt_main.py
@@ -0,0 +1,60 @@
+from fastapi import Depends, FastAPI, HTTPException, Request, Response
+from sqlalchemy.orm import Session
+
+from . import crud, models, schemas
+from .database import SessionLocal, engine
+
+models.Base.metadata.create_all(bind=engine)
+
+app = FastAPI()
+
+
+@app.middleware("http")
+async def db_session_middleware(request: Request, call_next):
+ response = Response("Internal server error", status_code=500)
+ try:
+ request.state.db = SessionLocal()
+ response = await call_next(request)
+ finally:
+ request.state.db.close()
+ return response
+
+
+# Dependency
+def get_db(request: Request):
+ return request.state.db
+
+
+@app.post("/users/", response_model=schemas.User)
+def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
+ db_user = crud.get_user_by_email(db, email=user.email)
+ if db_user:
+ raise HTTPException(status_code=400, detail="Email already registered")
+ return crud.create_user(db=db, user=user)
+
+
+@app.get("/users/", response_model=list[schemas.User])
+def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
+ users = crud.get_users(db, skip=skip, limit=limit)
+ return users
+
+
+@app.get("/users/{user_id}", response_model=schemas.User)
+def read_user(user_id: int, db: Session = Depends(get_db)):
+ db_user = crud.get_user(db, user_id=user_id)
+ if db_user is None:
+ raise HTTPException(status_code=404, detail="User not found")
+ return db_user
+
+
+@app.post("/users/{user_id}/items/", response_model=schemas.Item)
+def create_item_for_user(
+ user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db)
+):
+ return crud.create_user_item(db=db, item=item, user_id=user_id)
+
+
+@app.get("/items/", response_model=list[schemas.Item])
+def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
+ items = crud.get_items(db, skip=skip, limit=limit)
+ return items
diff --git a/docs_src/sql_databases/sql_app_py310/crud.py b/docs_src/sql_databases/sql_app_py310/crud.py
new file mode 100644
index 00000000..679acdb5
--- /dev/null
+++ b/docs_src/sql_databases/sql_app_py310/crud.py
@@ -0,0 +1,36 @@
+from sqlalchemy.orm import Session
+
+from . import models, schemas
+
+
+def get_user(db: Session, user_id: int):
+ return db.query(models.User).filter(models.User.id == user_id).first()
+
+
+def get_user_by_email(db: Session, email: str):
+ return db.query(models.User).filter(models.User.email == email).first()
+
+
+def get_users(db: Session, skip: int = 0, limit: int = 100):
+ return db.query(models.User).offset(skip).limit(limit).all()
+
+
+def create_user(db: Session, user: schemas.UserCreate):
+ fake_hashed_password = user.password + "notreallyhashed"
+ db_user = models.User(email=user.email, hashed_password=fake_hashed_password)
+ db.add(db_user)
+ db.commit()
+ db.refresh(db_user)
+ return db_user
+
+
+def get_items(db: Session, skip: int = 0, limit: int = 100):
+ return db.query(models.Item).offset(skip).limit(limit).all()
+
+
+def create_user_item(db: Session, item: schemas.ItemCreate, user_id: int):
+ db_item = models.Item(**item.dict(), owner_id=user_id)
+ db.add(db_item)
+ db.commit()
+ db.refresh(db_item)
+ return db_item
diff --git a/docs_src/sql_databases/sql_app_py310/database.py b/docs_src/sql_databases/sql_app_py310/database.py
new file mode 100644
index 00000000..45a8b9f6
--- /dev/null
+++ b/docs_src/sql_databases/sql_app_py310/database.py
@@ -0,0 +1,13 @@
+from sqlalchemy import create_engine
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import sessionmaker
+
+SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db"
+# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db"
+
+engine = create_engine(
+ SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
+)
+SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
+
+Base = declarative_base()
diff --git a/docs_src/sql_databases/sql_app_py310/main.py b/docs_src/sql_databases/sql_app_py310/main.py
new file mode 100644
index 00000000..a9856d0b
--- /dev/null
+++ b/docs_src/sql_databases/sql_app_py310/main.py
@@ -0,0 +1,53 @@
+from fastapi import Depends, FastAPI, HTTPException
+from sqlalchemy.orm import Session
+
+from . import crud, models, schemas
+from .database import SessionLocal, engine
+
+models.Base.metadata.create_all(bind=engine)
+
+app = FastAPI()
+
+
+# Dependency
+def get_db():
+ db = SessionLocal()
+ try:
+ yield db
+ finally:
+ db.close()
+
+
+@app.post("/users/", response_model=schemas.User)
+def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
+ db_user = crud.get_user_by_email(db, email=user.email)
+ if db_user:
+ raise HTTPException(status_code=400, detail="Email already registered")
+ return crud.create_user(db=db, user=user)
+
+
+@app.get("/users/", response_model=list[schemas.User])
+def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
+ users = crud.get_users(db, skip=skip, limit=limit)
+ return users
+
+
+@app.get("/users/{user_id}", response_model=schemas.User)
+def read_user(user_id: int, db: Session = Depends(get_db)):
+ db_user = crud.get_user(db, user_id=user_id)
+ if db_user is None:
+ raise HTTPException(status_code=404, detail="User not found")
+ return db_user
+
+
+@app.post("/users/{user_id}/items/", response_model=schemas.Item)
+def create_item_for_user(
+ user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db)
+):
+ return crud.create_user_item(db=db, item=item, user_id=user_id)
+
+
+@app.get("/items/", response_model=list[schemas.Item])
+def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
+ items = crud.get_items(db, skip=skip, limit=limit)
+ return items
diff --git a/docs_src/sql_databases/sql_app_py310/models.py b/docs_src/sql_databases/sql_app_py310/models.py
new file mode 100644
index 00000000..62d8ab4a
--- /dev/null
+++ b/docs_src/sql_databases/sql_app_py310/models.py
@@ -0,0 +1,26 @@
+from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
+from sqlalchemy.orm import relationship
+
+from .database import Base
+
+
+class User(Base):
+ __tablename__ = "users"
+
+ id = Column(Integer, primary_key=True, index=True)
+ email = Column(String, unique=True, index=True)
+ hashed_password = Column(String)
+ is_active = Column(Boolean, default=True)
+
+ items = relationship("Item", back_populates="owner")
+
+
+class Item(Base):
+ __tablename__ = "items"
+
+ id = Column(Integer, primary_key=True, index=True)
+ title = Column(String, index=True)
+ description = Column(String, index=True)
+ owner_id = Column(Integer, ForeignKey("users.id"))
+
+ owner = relationship("User", back_populates="items")
diff --git a/docs_src/sql_databases/sql_app_py310/schemas.py b/docs_src/sql_databases/sql_app_py310/schemas.py
new file mode 100644
index 00000000..aea2e3f1
--- /dev/null
+++ b/docs_src/sql_databases/sql_app_py310/schemas.py
@@ -0,0 +1,35 @@
+from pydantic import BaseModel
+
+
+class ItemBase(BaseModel):
+ title: str
+ description: str | None = None
+
+
+class ItemCreate(ItemBase):
+ pass
+
+
+class Item(ItemBase):
+ id: int
+ owner_id: int
+
+ class Config:
+ orm_mode = True
+
+
+class UserBase(BaseModel):
+ email: str
+
+
+class UserCreate(UserBase):
+ password: str
+
+
+class User(UserBase):
+ id: int
+ is_active: bool
+ items: list[Item] = []
+
+ class Config:
+ orm_mode = True
diff --git a/docs_src/sql_databases/sql_app_py310/tests/__init__.py b/docs_src/sql_databases/sql_app_py310/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/docs_src/sql_databases/sql_app_py310/tests/test_sql_app.py b/docs_src/sql_databases/sql_app_py310/tests/test_sql_app.py
new file mode 100644
index 00000000..c60c3356
--- /dev/null
+++ b/docs_src/sql_databases/sql_app_py310/tests/test_sql_app.py
@@ -0,0 +1,47 @@
+from fastapi.testclient import TestClient
+from sqlalchemy import create_engine
+from sqlalchemy.orm import sessionmaker
+
+from ..database import Base
+from ..main import app, get_db
+
+SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
+
+engine = create_engine(
+ SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
+)
+TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
+
+
+Base.metadata.create_all(bind=engine)
+
+
+def override_get_db():
+ try:
+ db = TestingSessionLocal()
+ yield db
+ finally:
+ db.close()
+
+
+app.dependency_overrides[get_db] = override_get_db
+
+client = TestClient(app)
+
+
+def test_create_user():
+ response = client.post(
+ "/users/",
+ json={"email": "deadpool@example.com", "password": "chimichangas4life"},
+ )
+ assert response.status_code == 200, response.text
+ data = response.json()
+ assert data["email"] == "deadpool@example.com"
+ assert "id" in data
+ user_id = data["id"]
+
+ response = client.get(f"/users/{user_id}")
+ assert response.status_code == 200, response.text
+ data = response.json()
+ assert data["email"] == "deadpool@example.com"
+ assert data["id"] == user_id
diff --git a/docs_src/sql_databases/sql_app_py39/__init__.py b/docs_src/sql_databases/sql_app_py39/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/docs_src/sql_databases/sql_app_py39/alt_main.py b/docs_src/sql_databases/sql_app_py39/alt_main.py
new file mode 100644
index 00000000..5de88ec3
--- /dev/null
+++ b/docs_src/sql_databases/sql_app_py39/alt_main.py
@@ -0,0 +1,60 @@
+from fastapi import Depends, FastAPI, HTTPException, Request, Response
+from sqlalchemy.orm import Session
+
+from . import crud, models, schemas
+from .database import SessionLocal, engine
+
+models.Base.metadata.create_all(bind=engine)
+
+app = FastAPI()
+
+
+@app.middleware("http")
+async def db_session_middleware(request: Request, call_next):
+ response = Response("Internal server error", status_code=500)
+ try:
+ request.state.db = SessionLocal()
+ response = await call_next(request)
+ finally:
+ request.state.db.close()
+ return response
+
+
+# Dependency
+def get_db(request: Request):
+ return request.state.db
+
+
+@app.post("/users/", response_model=schemas.User)
+def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
+ db_user = crud.get_user_by_email(db, email=user.email)
+ if db_user:
+ raise HTTPException(status_code=400, detail="Email already registered")
+ return crud.create_user(db=db, user=user)
+
+
+@app.get("/users/", response_model=list[schemas.User])
+def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
+ users = crud.get_users(db, skip=skip, limit=limit)
+ return users
+
+
+@app.get("/users/{user_id}", response_model=schemas.User)
+def read_user(user_id: int, db: Session = Depends(get_db)):
+ db_user = crud.get_user(db, user_id=user_id)
+ if db_user is None:
+ raise HTTPException(status_code=404, detail="User not found")
+ return db_user
+
+
+@app.post("/users/{user_id}/items/", response_model=schemas.Item)
+def create_item_for_user(
+ user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db)
+):
+ return crud.create_user_item(db=db, item=item, user_id=user_id)
+
+
+@app.get("/items/", response_model=list[schemas.Item])
+def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
+ items = crud.get_items(db, skip=skip, limit=limit)
+ return items
diff --git a/docs_src/sql_databases/sql_app_py39/crud.py b/docs_src/sql_databases/sql_app_py39/crud.py
new file mode 100644
index 00000000..679acdb5
--- /dev/null
+++ b/docs_src/sql_databases/sql_app_py39/crud.py
@@ -0,0 +1,36 @@
+from sqlalchemy.orm import Session
+
+from . import models, schemas
+
+
+def get_user(db: Session, user_id: int):
+ return db.query(models.User).filter(models.User.id == user_id).first()
+
+
+def get_user_by_email(db: Session, email: str):
+ return db.query(models.User).filter(models.User.email == email).first()
+
+
+def get_users(db: Session, skip: int = 0, limit: int = 100):
+ return db.query(models.User).offset(skip).limit(limit).all()
+
+
+def create_user(db: Session, user: schemas.UserCreate):
+ fake_hashed_password = user.password + "notreallyhashed"
+ db_user = models.User(email=user.email, hashed_password=fake_hashed_password)
+ db.add(db_user)
+ db.commit()
+ db.refresh(db_user)
+ return db_user
+
+
+def get_items(db: Session, skip: int = 0, limit: int = 100):
+ return db.query(models.Item).offset(skip).limit(limit).all()
+
+
+def create_user_item(db: Session, item: schemas.ItemCreate, user_id: int):
+ db_item = models.Item(**item.dict(), owner_id=user_id)
+ db.add(db_item)
+ db.commit()
+ db.refresh(db_item)
+ return db_item
diff --git a/docs_src/sql_databases/sql_app_py39/database.py b/docs_src/sql_databases/sql_app_py39/database.py
new file mode 100644
index 00000000..45a8b9f6
--- /dev/null
+++ b/docs_src/sql_databases/sql_app_py39/database.py
@@ -0,0 +1,13 @@
+from sqlalchemy import create_engine
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import sessionmaker
+
+SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db"
+# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db"
+
+engine = create_engine(
+ SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
+)
+SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
+
+Base = declarative_base()
diff --git a/docs_src/sql_databases/sql_app_py39/main.py b/docs_src/sql_databases/sql_app_py39/main.py
new file mode 100644
index 00000000..a9856d0b
--- /dev/null
+++ b/docs_src/sql_databases/sql_app_py39/main.py
@@ -0,0 +1,53 @@
+from fastapi import Depends, FastAPI, HTTPException
+from sqlalchemy.orm import Session
+
+from . import crud, models, schemas
+from .database import SessionLocal, engine
+
+models.Base.metadata.create_all(bind=engine)
+
+app = FastAPI()
+
+
+# Dependency
+def get_db():
+ db = SessionLocal()
+ try:
+ yield db
+ finally:
+ db.close()
+
+
+@app.post("/users/", response_model=schemas.User)
+def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
+ db_user = crud.get_user_by_email(db, email=user.email)
+ if db_user:
+ raise HTTPException(status_code=400, detail="Email already registered")
+ return crud.create_user(db=db, user=user)
+
+
+@app.get("/users/", response_model=list[schemas.User])
+def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
+ users = crud.get_users(db, skip=skip, limit=limit)
+ return users
+
+
+@app.get("/users/{user_id}", response_model=schemas.User)
+def read_user(user_id: int, db: Session = Depends(get_db)):
+ db_user = crud.get_user(db, user_id=user_id)
+ if db_user is None:
+ raise HTTPException(status_code=404, detail="User not found")
+ return db_user
+
+
+@app.post("/users/{user_id}/items/", response_model=schemas.Item)
+def create_item_for_user(
+ user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db)
+):
+ return crud.create_user_item(db=db, item=item, user_id=user_id)
+
+
+@app.get("/items/", response_model=list[schemas.Item])
+def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
+ items = crud.get_items(db, skip=skip, limit=limit)
+ return items
diff --git a/docs_src/sql_databases/sql_app_py39/models.py b/docs_src/sql_databases/sql_app_py39/models.py
new file mode 100644
index 00000000..62d8ab4a
--- /dev/null
+++ b/docs_src/sql_databases/sql_app_py39/models.py
@@ -0,0 +1,26 @@
+from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
+from sqlalchemy.orm import relationship
+
+from .database import Base
+
+
+class User(Base):
+ __tablename__ = "users"
+
+ id = Column(Integer, primary_key=True, index=True)
+ email = Column(String, unique=True, index=True)
+ hashed_password = Column(String)
+ is_active = Column(Boolean, default=True)
+
+ items = relationship("Item", back_populates="owner")
+
+
+class Item(Base):
+ __tablename__ = "items"
+
+ id = Column(Integer, primary_key=True, index=True)
+ title = Column(String, index=True)
+ description = Column(String, index=True)
+ owner_id = Column(Integer, ForeignKey("users.id"))
+
+ owner = relationship("User", back_populates="items")
diff --git a/docs_src/sql_databases/sql_app_py39/schemas.py b/docs_src/sql_databases/sql_app_py39/schemas.py
new file mode 100644
index 00000000..a19f1cdf
--- /dev/null
+++ b/docs_src/sql_databases/sql_app_py39/schemas.py
@@ -0,0 +1,37 @@
+from typing import Optional
+
+from pydantic import BaseModel
+
+
+class ItemBase(BaseModel):
+ title: str
+ description: Optional[str] = None
+
+
+class ItemCreate(ItemBase):
+ pass
+
+
+class Item(ItemBase):
+ id: int
+ owner_id: int
+
+ class Config:
+ orm_mode = True
+
+
+class UserBase(BaseModel):
+ email: str
+
+
+class UserCreate(UserBase):
+ password: str
+
+
+class User(UserBase):
+ id: int
+ is_active: bool
+ items: list[Item] = []
+
+ class Config:
+ orm_mode = True
diff --git a/docs_src/sql_databases/sql_app_py39/tests/__init__.py b/docs_src/sql_databases/sql_app_py39/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/docs_src/sql_databases/sql_app_py39/tests/test_sql_app.py b/docs_src/sql_databases/sql_app_py39/tests/test_sql_app.py
new file mode 100644
index 00000000..c60c3356
--- /dev/null
+++ b/docs_src/sql_databases/sql_app_py39/tests/test_sql_app.py
@@ -0,0 +1,47 @@
+from fastapi.testclient import TestClient
+from sqlalchemy import create_engine
+from sqlalchemy.orm import sessionmaker
+
+from ..database import Base
+from ..main import app, get_db
+
+SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
+
+engine = create_engine(
+ SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
+)
+TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
+
+
+Base.metadata.create_all(bind=engine)
+
+
+def override_get_db():
+ try:
+ db = TestingSessionLocal()
+ yield db
+ finally:
+ db.close()
+
+
+app.dependency_overrides[get_db] = override_get_db
+
+client = TestClient(app)
+
+
+def test_create_user():
+ response = client.post(
+ "/users/",
+ json={"email": "deadpool@example.com", "password": "chimichangas4life"},
+ )
+ assert response.status_code == 200, response.text
+ data = response.json()
+ assert data["email"] == "deadpool@example.com"
+ assert "id" in data
+ user_id = data["id"]
+
+ response = client.get(f"/users/{user_id}")
+ assert response.status_code == 200, response.text
+ data = response.json()
+ assert data["email"] == "deadpool@example.com"
+ assert data["id"] == user_id
diff --git a/fastapi/__init__.py b/fastapi/__init__.py
index d04f2ea9..1a4d0016 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.65.2"
+__version__ = "0.77.1"
from starlette import status as status
diff --git a/fastapi/applications.py b/fastapi/applications.py
index 92d041c5..7530ddb9 100644
--- a/fastapi/applications.py
+++ b/fastapi/applications.py
@@ -1,7 +1,18 @@
-from typing import Any, Callable, Coroutine, Dict, List, Optional, Sequence, Type, Union
+from enum import Enum
+from typing import (
+ Any,
+ Awaitable,
+ Callable,
+ Coroutine,
+ Dict,
+ List,
+ Optional,
+ Sequence,
+ Type,
+ Union,
+)
from fastapi import routing
-from fastapi.concurrency import AsyncExitStack
from fastapi.datastructures import Default, DefaultPlaceholder
from fastapi.encoders import DictIntStrAny, SetIntStr
from fastapi.exception_handlers import (
@@ -10,6 +21,7 @@ from fastapi.exception_handlers import (
)
from fastapi.exceptions import RequestValidationError
from fastapi.logger import logger
+from fastapi.middleware.asyncexitstack import AsyncExitStackMiddleware
from fastapi.openapi.docs import (
get_redoc_html,
get_swagger_ui_html,
@@ -18,10 +30,12 @@ from fastapi.openapi.docs import (
from fastapi.openapi.utils import get_openapi
from fastapi.params import Depends
from fastapi.types import DecoratedCallable
+from fastapi.utils import generate_unique_id
from starlette.applications import Starlette
from starlette.datastructures import State
-from starlette.exceptions import HTTPException
+from starlette.exceptions import ExceptionMiddleware, HTTPException
from starlette.middleware import Middleware
+from starlette.middleware.errors import ServerErrorMiddleware
from starlette.requests import Request
from starlette.responses import HTMLResponse, JSONResponse, Response
from starlette.routing import BaseRoute
@@ -55,6 +69,9 @@ class FastAPI(Starlette):
] = None,
on_startup: Optional[Sequence[Callable[[], Any]]] = None,
on_shutdown: Optional[Sequence[Callable[[], Any]]] = None,
+ terms_of_service: Optional[str] = None,
+ contact: Optional[Dict[str, Union[str, Any]]] = None,
+ license_info: Optional[Dict[str, Union[str, Any]]] = None,
openapi_prefix: str = "",
root_path: str = "",
root_path_in_servers: bool = True,
@@ -62,10 +79,45 @@ class FastAPI(Starlette):
callbacks: Optional[List[BaseRoute]] = None,
deprecated: Optional[bool] = None,
include_in_schema: bool = True,
+ swagger_ui_parameters: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[routing.APIRoute], str] = Default(
+ generate_unique_id
+ ),
**extra: Any,
) -> None:
self._debug: bool = debug
+ self.title = title
+ self.description = description
+ self.version = version
+ self.terms_of_service = terms_of_service
+ self.contact = contact
+ self.license_info = license_info
+ self.openapi_url = openapi_url
+ self.openapi_tags = openapi_tags
+ self.root_path_in_servers = root_path_in_servers
+ self.docs_url = docs_url
+ self.redoc_url = redoc_url
+ self.swagger_ui_oauth2_redirect_url = swagger_ui_oauth2_redirect_url
+ self.swagger_ui_init_oauth = swagger_ui_init_oauth
+ self.swagger_ui_parameters = swagger_ui_parameters
+ self.servers = servers or []
+ self.extra = extra
+ self.openapi_version = "3.0.2"
+ self.openapi_schema: Optional[Dict[str, Any]] = None
+ if self.openapi_url:
+ assert self.title, "A title must be provided for OpenAPI, e.g.: 'My API'"
+ assert self.version, "A version must be provided for OpenAPI, e.g.: '2.1.0'"
+ # TODO: remove when discarding the openapi_prefix parameter
+ if openapi_prefix:
+ logger.warning(
+ '"openapi_prefix" has been deprecated in favor of "root_path", which '
+ "follows more closely the ASGI standard, is simpler, and more "
+ "automatic. Check the docs at "
+ "https://fastapi.tiangolo.com/advanced/sub-applications/"
+ )
+ self.root_path = root_path or openapi_prefix
self.state: State = State()
+ self.dependency_overrides: Dict[Callable[..., Any], Callable[..., Any]] = {}
self.router: routing.APIRouter = routing.APIRouter(
routes=routes,
dependency_overrides_provider=self,
@@ -77,13 +129,11 @@ class FastAPI(Starlette):
deprecated=deprecated,
include_in_schema=include_in_schema,
responses=responses,
+ generate_unique_id_function=generate_unique_id_function,
)
self.exception_handlers: Dict[
- Union[int, Type[Exception]],
- Callable[[Request, Any], Coroutine[Any, Any, Response]],
- ] = (
- {} if exception_handlers is None else dict(exception_handlers)
- )
+ Any, Callable[[Request, Any], Union[Response, Awaitable[Response]]]
+ ] = ({} if exception_handlers is None else dict(exception_handlers))
self.exception_handlers.setdefault(HTTPException, http_exception_handler)
self.exception_handlers.setdefault(
RequestValidationError, request_validation_exception_handler
@@ -93,38 +143,57 @@ class FastAPI(Starlette):
[] if middleware is None else list(middleware)
)
self.middleware_stack: ASGIApp = self.build_middleware_stack()
-
- self.title = title
- self.description = description
- self.version = version
- self.servers = servers or []
- self.openapi_url = openapi_url
- self.openapi_tags = openapi_tags
- # TODO: remove when discarding the openapi_prefix parameter
- if openapi_prefix:
- logger.warning(
- '"openapi_prefix" has been deprecated in favor of "root_path", which '
- "follows more closely the ASGI standard, is simpler, and more "
- "automatic. Check the docs at "
- "https://fastapi.tiangolo.com/advanced/sub-applications/"
- )
- self.root_path = root_path or openapi_prefix
- self.root_path_in_servers = root_path_in_servers
- self.docs_url = docs_url
- self.redoc_url = redoc_url
- self.swagger_ui_oauth2_redirect_url = swagger_ui_oauth2_redirect_url
- self.swagger_ui_init_oauth = swagger_ui_init_oauth
- self.extra = extra
- self.dependency_overrides: Dict[Callable[..., Any], Callable[..., Any]] = {}
-
- self.openapi_version = "3.0.2"
-
- if self.openapi_url:
- assert self.title, "A title must be provided for OpenAPI, e.g.: 'My API'"
- assert self.version, "A version must be provided for OpenAPI, e.g.: '2.1.0'"
- self.openapi_schema: Optional[Dict[str, Any]] = None
self.setup()
+ def build_middleware_stack(self) -> ASGIApp:
+ # Duplicate/override from Starlette to add AsyncExitStackMiddleware
+ # inside of ExceptionMiddleware, inside of custom user middlewares
+ debug = self.debug
+ error_handler = None
+ exception_handlers = {}
+
+ for key, value in self.exception_handlers.items():
+ if key in (500, Exception):
+ error_handler = value
+ else:
+ exception_handlers[key] = value
+
+ middleware = (
+ [Middleware(ServerErrorMiddleware, handler=error_handler, debug=debug)]
+ + self.user_middleware
+ + [
+ Middleware(
+ ExceptionMiddleware, handlers=exception_handlers, debug=debug
+ ),
+ # Add FastAPI-specific AsyncExitStackMiddleware for dependencies with
+ # contextvars.
+ # This needs to happen after user middlewares because those create a
+ # new contextvars context copy by using a new AnyIO task group.
+ # The initial part of dependencies with yield is executed in the
+ # FastAPI code, inside all the middlewares, but the teardown part
+ # (after yield) is executed in the AsyncExitStack in this middleware,
+ # if the AsyncExitStack lived outside of the custom middlewares and
+ # contextvars were set in a dependency with yield in that internal
+ # contextvars context, the values would not be available in the
+ # outside context of the AsyncExitStack.
+ # By putting the middleware and the AsyncExitStack here, inside all
+ # user middlewares, the code before and after yield in dependencies
+ # with yield is executed in the same contextvars context, so all values
+ # set in contextvars before yield is still available after yield as
+ # would be expected.
+ # Additionally, by having this AsyncExitStack here, after the
+ # ExceptionMiddleware, now dependencies can catch handled exceptions,
+ # e.g. HTTPException, to customize the teardown code (e.g. DB session
+ # rollback).
+ Middleware(AsyncExitStackMiddleware),
+ ]
+ )
+
+ app = self.router
+ for cls, options in reversed(middleware):
+ app = cls(app=app, **options)
+ return app
+
def openapi(self) -> Dict[str, Any]:
if not self.openapi_schema:
self.openapi_schema = get_openapi(
@@ -132,6 +201,9 @@ class FastAPI(Starlette):
version=self.version,
openapi_version=self.openapi_version,
description=self.description,
+ terms_of_service=self.terms_of_service,
+ contact=self.contact,
+ license_info=self.license_info,
routes=self.routes,
tags=self.openapi_tags,
servers=self.servers,
@@ -165,6 +237,7 @@ class FastAPI(Starlette):
title=self.title + " - Swagger UI",
oauth2_redirect_url=oauth2_redirect_url,
init_oauth=self.swagger_ui_init_oauth,
+ swagger_ui_parameters=self.swagger_ui_parameters,
)
self.add_route(self.docs_url, swagger_ui_html, include_in_schema=False)
@@ -193,12 +266,7 @@ class FastAPI(Starlette):
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
if self.root_path:
scope["root_path"] = self.root_path
- if AsyncExitStack:
- async with AsyncExitStack() as stack:
- scope["fastapi_astack"] = stack
- await super().__call__(scope, receive, send)
- else:
- await super().__call__(scope, receive, send) # pragma: no cover
+ await super().__call__(scope, receive, send)
def add_api_route(
self,
@@ -206,8 +274,8 @@ class FastAPI(Starlette):
endpoint: Callable[..., Coroutine[Any, Any, Response]],
*,
response_model: Optional[Type[Any]] = None,
- status_code: int = 200,
- tags: Optional[List[str]] = None,
+ status_code: Optional[int] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -227,6 +295,10 @@ class FastAPI(Starlette):
JSONResponse
),
name: Optional[str] = None,
+ openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[routing.APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> None:
self.router.add_api_route(
path,
@@ -251,6 +323,8 @@ class FastAPI(Starlette):
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
+ openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
def api_route(
@@ -258,8 +332,8 @@ class FastAPI(Starlette):
path: str,
*,
response_model: Optional[Type[Any]] = None,
- status_code: int = 200,
- tags: Optional[List[str]] = None,
+ status_code: Optional[int] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -277,6 +351,10 @@ class FastAPI(Starlette):
include_in_schema: bool = True,
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
+ openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[routing.APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> Callable[[DecoratedCallable], DecoratedCallable]:
def decorator(func: DecoratedCallable) -> DecoratedCallable:
self.router.add_api_route(
@@ -302,6 +380,8 @@ class FastAPI(Starlette):
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
+ openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
return func
@@ -326,13 +406,16 @@ class FastAPI(Starlette):
router: routing.APIRouter,
*,
prefix: str = "",
- tags: Optional[List[str]] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
deprecated: Optional[bool] = None,
include_in_schema: bool = True,
default_response_class: Type[Response] = Default(JSONResponse),
callbacks: Optional[List[BaseRoute]] = None,
+ generate_unique_id_function: Callable[[routing.APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> None:
self.router.include_router(
router,
@@ -344,6 +427,7 @@ class FastAPI(Starlette):
include_in_schema=include_in_schema,
default_response_class=default_response_class,
callbacks=callbacks,
+ generate_unique_id_function=generate_unique_id_function,
)
def get(
@@ -351,8 +435,8 @@ class FastAPI(Starlette):
path: str,
*,
response_model: Optional[Type[Any]] = None,
- status_code: int = 200,
- tags: Optional[List[str]] = None,
+ status_code: Optional[int] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -370,6 +454,10 @@ class FastAPI(Starlette):
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
+ openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[routing.APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.router.get(
path,
@@ -393,6 +481,8 @@ class FastAPI(Starlette):
response_class=response_class,
name=name,
callbacks=callbacks,
+ openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
def put(
@@ -400,8 +490,8 @@ class FastAPI(Starlette):
path: str,
*,
response_model: Optional[Type[Any]] = None,
- status_code: int = 200,
- tags: Optional[List[str]] = None,
+ status_code: Optional[int] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -419,6 +509,10 @@ class FastAPI(Starlette):
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
+ openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[routing.APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.router.put(
path,
@@ -442,6 +536,8 @@ class FastAPI(Starlette):
response_class=response_class,
name=name,
callbacks=callbacks,
+ openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
def post(
@@ -449,8 +545,8 @@ class FastAPI(Starlette):
path: str,
*,
response_model: Optional[Type[Any]] = None,
- status_code: int = 200,
- tags: Optional[List[str]] = None,
+ status_code: Optional[int] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -468,6 +564,10 @@ class FastAPI(Starlette):
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
+ openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[routing.APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.router.post(
path,
@@ -491,6 +591,8 @@ class FastAPI(Starlette):
response_class=response_class,
name=name,
callbacks=callbacks,
+ openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
def delete(
@@ -498,8 +600,8 @@ class FastAPI(Starlette):
path: str,
*,
response_model: Optional[Type[Any]] = None,
- status_code: int = 200,
- tags: Optional[List[str]] = None,
+ status_code: Optional[int] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -517,6 +619,10 @@ class FastAPI(Starlette):
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
+ openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[routing.APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.router.delete(
path,
@@ -540,6 +646,8 @@ class FastAPI(Starlette):
response_class=response_class,
name=name,
callbacks=callbacks,
+ openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
def options(
@@ -547,8 +655,8 @@ class FastAPI(Starlette):
path: str,
*,
response_model: Optional[Type[Any]] = None,
- status_code: int = 200,
- tags: Optional[List[str]] = None,
+ status_code: Optional[int] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -566,6 +674,10 @@ class FastAPI(Starlette):
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
+ openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[routing.APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.router.options(
path,
@@ -589,6 +701,8 @@ class FastAPI(Starlette):
response_class=response_class,
name=name,
callbacks=callbacks,
+ openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
def head(
@@ -596,8 +710,8 @@ class FastAPI(Starlette):
path: str,
*,
response_model: Optional[Type[Any]] = None,
- status_code: int = 200,
- tags: Optional[List[str]] = None,
+ status_code: Optional[int] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -615,6 +729,10 @@ class FastAPI(Starlette):
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
+ openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[routing.APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.router.head(
path,
@@ -638,6 +756,8 @@ class FastAPI(Starlette):
response_class=response_class,
name=name,
callbacks=callbacks,
+ openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
def patch(
@@ -645,8 +765,8 @@ class FastAPI(Starlette):
path: str,
*,
response_model: Optional[Type[Any]] = None,
- status_code: int = 200,
- tags: Optional[List[str]] = None,
+ status_code: Optional[int] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -664,6 +784,10 @@ class FastAPI(Starlette):
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
+ openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[routing.APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.router.patch(
path,
@@ -687,6 +811,8 @@ class FastAPI(Starlette):
response_class=response_class,
name=name,
callbacks=callbacks,
+ openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
def trace(
@@ -694,8 +820,8 @@ class FastAPI(Starlette):
path: str,
*,
response_model: Optional[Type[Any]] = None,
- status_code: int = 200,
- tags: Optional[List[str]] = None,
+ status_code: Optional[int] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -713,6 +839,10 @@ class FastAPI(Starlette):
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
+ openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[routing.APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.router.trace(
path,
@@ -736,4 +866,6 @@ class FastAPI(Starlette):
response_class=response_class,
name=name,
callbacks=callbacks,
+ openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
diff --git a/fastapi/concurrency.py b/fastapi/concurrency.py
index d1fdfe5f..becac3f3 100644
--- a/fastapi/concurrency.py
+++ b/fastapi/concurrency.py
@@ -1,4 +1,5 @@
-from typing import Any, Callable
+import sys
+from typing import AsyncGenerator, ContextManager, TypeVar
from starlette.concurrency import iterate_in_threadpool as iterate_in_threadpool # noqa
from starlette.concurrency import run_in_threadpool as run_in_threadpool # noqa
@@ -6,45 +7,25 @@ from starlette.concurrency import ( # noqa
run_until_first_complete as run_until_first_complete,
)
-asynccontextmanager_error_message = """
-FastAPI's contextmanager_in_threadpool require Python 3.7 or above,
-or the backport for Python 3.6, installed with:
- pip install async-generator
-"""
+if sys.version_info >= (3, 7):
+ from contextlib import AsyncExitStack as AsyncExitStack
+ from contextlib import asynccontextmanager as asynccontextmanager
+else:
+ from contextlib2 import AsyncExitStack as AsyncExitStack # noqa
+ from contextlib2 import asynccontextmanager as asynccontextmanager # noqa
-def _fake_asynccontextmanager(func: Callable[..., Any]) -> Callable[..., Any]:
- def raiser(*args: Any, **kwargs: Any) -> Any:
- raise RuntimeError(asynccontextmanager_error_message)
-
- return raiser
+_T = TypeVar("_T")
-try:
- from contextlib import asynccontextmanager as asynccontextmanager # type: ignore
-except ImportError:
- try:
- from async_generator import ( # type: ignore # isort: skip
- asynccontextmanager as asynccontextmanager,
- )
- except ImportError: # pragma: no cover
- asynccontextmanager = _fake_asynccontextmanager
-
-try:
- from contextlib import AsyncExitStack as AsyncExitStack # type: ignore
-except ImportError:
- try:
- from async_exit_stack import AsyncExitStack as AsyncExitStack # type: ignore
- except ImportError: # pragma: no cover
- AsyncExitStack = None # type: ignore
-
-
-@asynccontextmanager # type: ignore
-async def contextmanager_in_threadpool(cm: Any) -> Any:
+@asynccontextmanager
+async def contextmanager_in_threadpool(
+ cm: ContextManager[_T],
+) -> AsyncGenerator[_T, None]:
try:
yield await run_in_threadpool(cm.__enter__)
except Exception as e:
- ok = await run_in_threadpool(cm.__exit__, type(e), e, None)
+ ok: bool = await run_in_threadpool(cm.__exit__, type(e), e, None)
if not ok:
raise e
else:
diff --git a/fastapi/datastructures.py b/fastapi/datastructures.py
index f22409c5..b20a25ab 100644
--- a/fastapi/datastructures.py
+++ b/fastapi/datastructures.py
@@ -1,5 +1,10 @@
-from typing import Any, Callable, Iterable, Type, TypeVar
+from typing import Any, Callable, Dict, Iterable, Type, TypeVar
+from starlette.datastructures import URL as URL # noqa: F401
+from starlette.datastructures import Address as Address # noqa: F401
+from starlette.datastructures import FormData as FormData # noqa: F401
+from starlette.datastructures import Headers as Headers # noqa: F401
+from starlette.datastructures import QueryParams as QueryParams # noqa: F401
from starlette.datastructures import State as State # noqa: F401
from starlette.datastructures import UploadFile as StarletteUploadFile
@@ -15,13 +20,17 @@ class UploadFile(StarletteUploadFile):
raise ValueError(f"Expected UploadFile, received: {type(v)}")
return v
+ @classmethod
+ def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
+ field_schema.update({"type": "string", "format": "binary"})
+
class DefaultPlaceholder:
"""
You shouldn't use this class directly.
It's used internally to recognize when a default value has been overwritten, even
- if the overriden default value was truthy.
+ if the overridden default value was truthy.
"""
def __init__(self, value: Any):
@@ -42,6 +51,6 @@ def Default(value: DefaultType) -> DefaultType:
You shouldn't use this function directly.
It's used internally to recognize when a default value has been overwritten, even
- if the overriden default value was truthy.
+ if the overridden default value was truthy.
"""
return DefaultPlaceholder(value) # type: ignore
diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py
index 923669b9..9dccd354 100644
--- a/fastapi/dependencies/utils.py
+++ b/fastapi/dependencies/utils.py
@@ -1,10 +1,11 @@
-import asyncio
+import dataclasses
import inspect
from contextlib import contextmanager
from copy import deepcopy
from typing import (
Any,
Callable,
+ Coroutine,
Dict,
List,
Mapping,
@@ -16,10 +17,10 @@ from typing import (
cast,
)
+import anyio
from fastapi import params
from fastapi.concurrency import (
AsyncExitStack,
- _fake_asynccontextmanager,
asynccontextmanager,
contextmanager_in_threadpool,
)
@@ -217,6 +218,7 @@ def is_scalar_field(field: ModelField) -> bool:
field.shape == SHAPE_SINGLETON
and not lenient_issubclass(field.type_, BaseModel)
and not lenient_issubclass(field.type_, sequence_types + (dict,))
+ and not dataclasses.is_dataclass(field.type_)
and not isinstance(field_info, params.Body)
):
return False
@@ -264,18 +266,6 @@ def get_typed_annotation(param: inspect.Parameter, globalns: Dict[str, Any]) ->
return annotation
-async_contextmanager_dependencies_error = """
-FastAPI dependencies with yield require Python 3.7 or above,
-or the backports for Python 3.6, installed with:
- pip install async-exit-stack async-generator
-"""
-
-
-def check_dependency_contextmanagers() -> None:
- if AsyncExitStack is None or asynccontextmanager == _fake_asynccontextmanager:
- raise RuntimeError(async_contextmanager_dependencies_error) # pragma: no cover
-
-
def get_dependant(
*,
path: str,
@@ -287,8 +277,6 @@ def get_dependant(
path_param_names = get_path_param_names(path)
endpoint_signature = get_typed_signature(call)
signature_params = endpoint_signature.parameters
- if is_gen_callable(call) or is_async_gen_callable(call):
- check_dependency_contextmanagers()
dependant = Dependant(call=call, name=name, path=path, use_cache=use_cache)
for param_name, param in signature_params.items():
if isinstance(param.default, params.Depends):
@@ -402,6 +390,8 @@ def get_param_field(
field.required = required
if not had_schema and not is_scalar_field(field=field):
field.field_info = params.Body(field_info.default)
+ if not had_schema and lenient_issubclass(field.type_, UploadFile):
+ field.field_info = params.File(field_info.default)
return field
@@ -450,14 +440,6 @@ async def solve_generator(
if is_gen_callable(call):
cm = contextmanager_in_threadpool(contextmanager(call)(**sub_values))
elif is_async_gen_callable(call):
- if not inspect.isasyncgenfunction(call):
- # asynccontextmanager from the async_generator backfill pre python3.7
- # does not support callables that are not functions or methods.
- # See https://github.com/python-trio/async_generator/issues/32
- #
- # Expand the callable class into its __call__ method before decorating it.
- # This approach will work on newer python versions as well.
- call = getattr(call, "__call__", None)
cm = asynccontextmanager(call)(**sub_values)
return await stack.enter_async_context(cm)
@@ -480,13 +462,10 @@ async def solve_dependencies(
]:
values: Dict[str, Any] = {}
errors: List[ErrorWrapper] = []
- response = response or Response(
- content=None,
- status_code=None, # type: ignore
- headers=None, # type: ignore # in Starlette
- media_type=None, # type: ignore # in Starlette
- background=None, # type: ignore # in Starlette
- )
+ if response is None:
+ response = Response()
+ del response.headers["content-length"]
+ response.status_code = None # type: ignore
dependency_cache = dependency_cache or {}
sub_dependant: Dependant
for sub_dependant in dependant.dependencies:
@@ -537,10 +516,7 @@ async def solve_dependencies(
solved = dependency_cache[sub_dependant.cache_key]
elif is_gen_callable(call) or is_async_gen_callable(call):
stack = request.scope.get("fastapi_astack")
- if stack is None:
- raise RuntimeError(
- async_contextmanager_dependencies_error
- ) # pragma: no cover
+ assert isinstance(stack, AsyncExitStack)
solved = await solve_generator(
call=call, stack=stack, sub_values=sub_values
)
@@ -695,9 +671,18 @@ async def request_body_to_args(
and lenient_issubclass(field.type_, bytes)
and isinstance(value, sequence_types)
):
- awaitables = [sub_value.read() for sub_value in value]
- contents = await asyncio.gather(*awaitables)
- value = sequence_shape_to_type[field.shape](contents)
+ results: List[Union[bytes, str]] = []
+
+ async def process_fn(
+ fn: Callable[[], Coroutine[Any, Any, Any]]
+ ) -> None:
+ result = await fn()
+ results.append(result)
+
+ async with anyio.create_task_group() as tg:
+ for sub_value in value:
+ tg.start_soon(process_fn, sub_value.read)
+ value = sequence_shape_to_type[field.shape](results)
v_, errors_ = field.validate(value, values, loc=loc)
@@ -715,25 +700,6 @@ def get_missing_field_error(loc: Tuple[str, ...]) -> ErrorWrapper:
return missing_field_error
-def get_schema_compatible_field(*, field: ModelField) -> ModelField:
- out_field = field
- if lenient_issubclass(field.type_, UploadFile):
- use_type: type = bytes
- if field.shape in sequence_shapes:
- use_type = List[bytes]
- out_field = create_response_field(
- name=field.name,
- type_=use_type,
- class_validators=field.class_validators,
- model_config=field.model_config,
- default=field.default,
- required=field.required,
- alias=field.alias,
- field_info=field.field_info,
- )
- return out_field
-
-
def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]:
flat_dependant = get_flat_dependant(dependant)
if not flat_dependant.body_params:
@@ -743,9 +709,8 @@ def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]:
embed = getattr(field_info, "embed", None)
body_param_names_set = {param.name for param in flat_dependant.body_params}
if len(body_param_names_set) == 1 and not embed:
- final_field = get_schema_compatible_field(field=first_param)
- check_file_field(final_field)
- return final_field
+ check_file_field(first_param)
+ return first_param
# If one field requires to embed, all have to be embedded
# in case a sub-dependency is evaluated with a single unique body field
# That is combined (embedded) with other body fields
@@ -754,7 +719,7 @@ def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]:
model_name = "Body_" + name
BodyModel: Type[BaseModel] = create_model(model_name)
for f in flat_dependant.body_params:
- BodyModel.__fields__[f.name] = get_schema_compatible_field(field=f)
+ BodyModel.__fields__[f.name] = f
required = any(True for f in flat_dependant.body_params if f.required)
BodyFieldInfo_kwargs: Dict[str, Any] = dict(default=None)
diff --git a/fastapi/encoders.py b/fastapi/encoders.py
index 6a2a75dd..4b7ffe31 100644
--- a/fastapi/encoders.py
+++ b/fastapi/encoders.py
@@ -1,3 +1,4 @@
+import dataclasses
from collections import defaultdict
from enum import Enum
from pathlib import PurePath
@@ -33,12 +34,20 @@ def jsonable_encoder(
exclude_unset: bool = False,
exclude_defaults: bool = False,
exclude_none: bool = False,
- custom_encoder: Dict[Any, Callable[[Any], Any]] = {},
+ custom_encoder: Optional[Dict[Any, Callable[[Any], Any]]] = None,
sqlalchemy_safe: bool = True,
) -> Any:
- if include is not None and not isinstance(include, set):
+ custom_encoder = custom_encoder or {}
+ if custom_encoder:
+ if type(obj) in custom_encoder:
+ return custom_encoder[type(obj)](obj)
+ else:
+ for encoder_type, encoder_instance in custom_encoder.items():
+ if isinstance(obj, encoder_type):
+ return encoder_instance(obj)
+ if include is not None and not isinstance(include, (set, dict)):
include = set(include)
- if exclude is not None and not isinstance(exclude, set):
+ if exclude is not None and not isinstance(exclude, (set, dict)):
exclude = set(exclude)
if isinstance(obj, BaseModel):
encoder = getattr(obj.__config__, "json_encoders", {})
@@ -61,6 +70,8 @@ def jsonable_encoder(
custom_encoder=encoder,
sqlalchemy_safe=sqlalchemy_safe,
)
+ if dataclasses.is_dataclass(obj):
+ return dataclasses.asdict(obj)
if isinstance(obj, Enum):
return obj.value
if isinstance(obj, PurePath):
@@ -115,14 +126,6 @@ def jsonable_encoder(
)
return encoded_list
- if custom_encoder:
- if type(obj) in custom_encoder:
- return custom_encoder[type(obj)](obj)
- else:
- for encoder_type, encoder in custom_encoder.items():
- if isinstance(obj, encoder_type):
- return encoder(obj)
-
if type(obj) in ENCODERS_BY_TYPE:
return ENCODERS_BY_TYPE[type(obj)](obj)
for encoder, classes_tuple in encoders_by_class_tuples.items():
diff --git a/fastapi/exceptions.py b/fastapi/exceptions.py
index f4a837bb..0f50acc6 100644
--- a/fastapi/exceptions.py
+++ b/fastapi/exceptions.py
@@ -12,8 +12,7 @@ class HTTPException(StarletteHTTPException):
detail: Any = None,
headers: Optional[Dict[str, Any]] = None,
) -> None:
- super().__init__(status_code=status_code, detail=detail)
- self.headers = headers
+ super().__init__(status_code=status_code, detail=detail, headers=headers)
RequestErrorModel: Type[BaseModel] = create_model("Request")
diff --git a/fastapi/middleware/asyncexitstack.py b/fastapi/middleware/asyncexitstack.py
new file mode 100644
index 00000000..503a68ac
--- /dev/null
+++ b/fastapi/middleware/asyncexitstack.py
@@ -0,0 +1,28 @@
+from typing import Optional
+
+from fastapi.concurrency import AsyncExitStack
+from starlette.types import ASGIApp, Receive, Scope, Send
+
+
+class AsyncExitStackMiddleware:
+ def __init__(self, app: ASGIApp, context_name: str = "fastapi_astack") -> None:
+ self.app = app
+ self.context_name = context_name
+
+ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
+ if AsyncExitStack:
+ dependency_exception: Optional[Exception] = None
+ async with AsyncExitStack() as stack:
+ scope[self.context_name] = stack
+ try:
+ await self.app(scope, receive, send)
+ except Exception as e:
+ dependency_exception = e
+ raise e
+ if dependency_exception:
+ # This exception was possibly handled by the dependency but it should
+ # still bubble up so that the ServerErrorMiddleware can return a 500
+ # or the ExceptionMiddleware can catch and handle any other exceptions
+ raise dependency_exception
+ else:
+ await self.app(scope, receive, send) # pragma: no cover
diff --git a/fastapi/openapi/docs.py b/fastapi/openapi/docs.py
index fd22e4e8..d6af17a8 100644
--- a/fastapi/openapi/docs.py
+++ b/fastapi/openapi/docs.py
@@ -4,17 +4,29 @@ from typing import Any, Dict, Optional
from fastapi.encoders import jsonable_encoder
from starlette.responses import HTMLResponse
+swagger_ui_default_parameters = {
+ "dom_id": "#swagger-ui",
+ "layout": "BaseLayout",
+ "deepLinking": True,
+ "showExtensions": True,
+ "showCommonExtensions": True,
+}
+
def get_swagger_ui_html(
*,
openapi_url: str,
title: str,
- swagger_js_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@3/swagger-ui-bundle.js",
- swagger_css_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@3/swagger-ui.css",
+ swagger_js_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@4/swagger-ui-bundle.js",
+ swagger_css_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@4/swagger-ui.css",
swagger_favicon_url: str = "https://fastapi.tiangolo.com/img/favicon.png",
oauth2_redirect_url: Optional[str] = None,
init_oauth: Optional[Dict[str, Any]] = None,
+ swagger_ui_parameters: Optional[Dict[str, Any]] = None,
) -> HTMLResponse:
+ current_swagger_ui_parameters = swagger_ui_default_parameters.copy()
+ if swagger_ui_parameters:
+ current_swagger_ui_parameters.update(swagger_ui_parameters)
html = f"""
@@ -34,19 +46,17 @@ def get_swagger_ui_html(
url: '{openapi_url}',
"""
+ for key, value in current_swagger_ui_parameters.items():
+ html += f"{json.dumps(key)}: {json.dumps(jsonable_encoder(value))},\n"
+
if oauth2_redirect_url:
html += f"oauth2RedirectUrl: window.location.origin + '{oauth2_redirect_url}',"
html += """
- dom_id: '#swagger-ui',
- presets: [
+ presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIBundle.SwaggerUIStandalonePreset
],
- layout: "BaseLayout",
- deepLinking: true,
- showExtensions: true,
- showCommonExtensions: true
})"""
if init_oauth:
diff --git a/fastapi/openapi/models.py b/fastapi/openapi/models.py
index fd480946..9c6598d2 100644
--- a/fastapi/openapi/models.py
+++ b/fastapi/openapi/models.py
@@ -30,11 +30,17 @@ class Contact(BaseModel):
url: Optional[AnyUrl] = None
email: Optional[EmailStr] = None
+ class Config:
+ extra = "allow"
+
class License(BaseModel):
name: str
url: Optional[AnyUrl] = None
+ class Config:
+ extra = "allow"
+
class Info(BaseModel):
title: str
@@ -44,18 +50,27 @@ class Info(BaseModel):
license: Optional[License] = None
version: str
+ class Config:
+ extra = "allow"
+
class ServerVariable(BaseModel):
enum: Optional[List[str]] = None
default: str
description: Optional[str] = None
+ class Config:
+ extra = "allow"
+
class Server(BaseModel):
url: Union[AnyUrl, str]
description: Optional[str] = None
variables: Optional[Dict[str, ServerVariable]] = None
+ class Config:
+ extra = "allow"
+
class Reference(BaseModel):
ref: str = Field(..., alias="$ref")
@@ -73,13 +88,19 @@ class XML(BaseModel):
attribute: Optional[bool] = None
wrapped: Optional[bool] = None
+ class Config:
+ extra = "allow"
+
class ExternalDocumentation(BaseModel):
description: Optional[str] = None
url: AnyUrl
+ class Config:
+ extra = "allow"
-class SchemaBase(BaseModel):
+
+class Schema(BaseModel):
ref: Optional[str] = Field(None, alias="$ref")
title: Optional[str] = None
multipleOf: Optional[float] = None
@@ -98,13 +119,13 @@ class SchemaBase(BaseModel):
required: Optional[List[str]] = None
enum: Optional[List[Any]] = None
type: Optional[str] = None
- allOf: Optional[List[Any]] = None
- oneOf: Optional[List[Any]] = None
- anyOf: Optional[List[Any]] = None
- not_: Optional[Any] = Field(None, alias="not")
- items: Optional[Any] = None
- properties: Optional[Dict[str, Any]] = None
- additionalProperties: Optional[Union[Dict[str, Any], bool]] = None
+ allOf: Optional[List["Schema"]] = None
+ oneOf: Optional[List["Schema"]] = None
+ anyOf: Optional[List["Schema"]] = None
+ not_: Optional["Schema"] = Field(None, alias="not")
+ items: Optional[Union["Schema", List["Schema"]]] = None
+ properties: Optional[Dict[str, "Schema"]] = None
+ additionalProperties: Optional[Union["Schema", Reference, bool]] = None
description: Optional[str] = None
format: Optional[str] = None
default: Optional[Any] = None
@@ -117,15 +138,8 @@ class SchemaBase(BaseModel):
example: Optional[Any] = None
deprecated: Optional[bool] = None
-
-class Schema(SchemaBase):
- allOf: Optional[List[SchemaBase]] = None
- oneOf: Optional[List[SchemaBase]] = None
- anyOf: Optional[List[SchemaBase]] = None
- not_: Optional[SchemaBase] = Field(None, alias="not")
- items: Optional[SchemaBase] = None
- properties: Optional[Dict[str, SchemaBase]] = None
- additionalProperties: Optional[Union[Dict[str, Any], bool]] = None
+ class Config:
+ extra: str = "allow"
class Example(BaseModel):
@@ -134,6 +148,9 @@ class Example(BaseModel):
value: Optional[Any] = None
externalValue: Optional[AnyUrl] = None
+ class Config:
+ extra = "allow"
+
class ParameterInType(Enum):
query = "query"
@@ -144,12 +161,14 @@ class ParameterInType(Enum):
class Encoding(BaseModel):
contentType: Optional[str] = None
- # Workaround OpenAPI recursive reference, using Any
- headers: Optional[Dict[str, Union[Any, Reference]]] = None
+ headers: Optional[Dict[str, Union["Header", Reference]]] = None
style: Optional[str] = None
explode: Optional[bool] = None
allowReserved: Optional[bool] = None
+ class Config:
+ extra = "allow"
+
class MediaType(BaseModel):
schema_: Optional[Union[Schema, Reference]] = Field(None, alias="schema")
@@ -157,6 +176,9 @@ class MediaType(BaseModel):
examples: Optional[Dict[str, Union[Example, Reference]]] = None
encoding: Optional[Dict[str, Encoding]] = None
+ class Config:
+ extra = "allow"
+
class ParameterBase(BaseModel):
description: Optional[str] = None
@@ -172,6 +194,9 @@ class ParameterBase(BaseModel):
# Serialization rules for more complex scenarios
content: Optional[Dict[str, MediaType]] = None
+ class Config:
+ extra = "allow"
+
class Parameter(ParameterBase):
name: str
@@ -182,16 +207,14 @@ class Header(ParameterBase):
pass
-# Workaround OpenAPI recursive reference
-class EncodingWithHeaders(Encoding):
- headers: Optional[Dict[str, Union[Header, Reference]]] = None
-
-
class RequestBody(BaseModel):
description: Optional[str] = None
content: Dict[str, MediaType]
required: Optional[bool] = None
+ class Config:
+ extra = "allow"
+
class Link(BaseModel):
operationRef: Optional[str] = None
@@ -201,6 +224,9 @@ class Link(BaseModel):
description: Optional[str] = None
server: Optional[Server] = None
+ class Config:
+ extra = "allow"
+
class Response(BaseModel):
description: str
@@ -208,6 +234,9 @@ class Response(BaseModel):
content: Optional[Dict[str, MediaType]] = None
links: Optional[Dict[str, Union[Link, Reference]]] = None
+ class Config:
+ extra = "allow"
+
class Operation(BaseModel):
tags: Optional[List[str]] = None
@@ -217,13 +246,16 @@ class Operation(BaseModel):
operationId: Optional[str] = None
parameters: Optional[List[Union[Parameter, Reference]]] = None
requestBody: Optional[Union[RequestBody, Reference]] = None
- responses: Dict[str, Response]
- # Workaround OpenAPI recursive reference
- callbacks: Optional[Dict[str, Union[Dict[str, Any], Reference]]] = None
+ # Using Any for Specification Extensions
+ responses: Dict[str, Union[Response, Any]]
+ callbacks: Optional[Dict[str, Union[Dict[str, "PathItem"], Reference]]] = None
deprecated: Optional[bool] = None
security: Optional[List[Dict[str, List[str]]]] = None
servers: Optional[List[Server]] = None
+ class Config:
+ extra = "allow"
+
class PathItem(BaseModel):
ref: Optional[str] = Field(None, alias="$ref")
@@ -240,10 +272,8 @@ class PathItem(BaseModel):
servers: Optional[List[Server]] = None
parameters: Optional[List[Union[Parameter, Reference]]] = None
-
-# Workaround OpenAPI recursive reference
-class OperationWithCallbacks(BaseModel):
- callbacks: Optional[Dict[str, Union[Dict[str, PathItem], Reference]]] = None
+ class Config:
+ extra = "allow"
class SecuritySchemeType(Enum):
@@ -257,6 +287,9 @@ class SecurityBase(BaseModel):
type_: SecuritySchemeType = Field(..., alias="type")
description: Optional[str] = None
+ class Config:
+ extra = "allow"
+
class APIKeyIn(Enum):
query = "query"
@@ -284,6 +317,9 @@ class OAuthFlow(BaseModel):
refreshUrl: Optional[str] = None
scopes: Dict[str, str] = {}
+ class Config:
+ extra = "allow"
+
class OAuthFlowImplicit(OAuthFlow):
authorizationUrl: str
@@ -308,6 +344,9 @@ class OAuthFlows(BaseModel):
clientCredentials: Optional[OAuthFlowClientCredentials] = None
authorizationCode: Optional[OAuthFlowAuthorizationCode] = None
+ class Config:
+ extra = "allow"
+
class OAuth2(SecurityBase):
type_ = Field(SecuritySchemeType.oauth2, alias="type")
@@ -331,7 +370,11 @@ class Components(BaseModel):
headers: Optional[Dict[str, Union[Header, Reference]]] = None
securitySchemes: Optional[Dict[str, Union[SecurityScheme, Reference]]] = None
links: Optional[Dict[str, Union[Link, Reference]]] = None
- callbacks: Optional[Dict[str, Union[Dict[str, PathItem], Reference]]] = None
+ # Using Any for Specification Extensions
+ callbacks: Optional[Dict[str, Union[Dict[str, PathItem], Reference, Any]]] = None
+
+ class Config:
+ extra = "allow"
class Tag(BaseModel):
@@ -339,13 +382,25 @@ class Tag(BaseModel):
description: Optional[str] = None
externalDocs: Optional[ExternalDocumentation] = None
+ class Config:
+ extra = "allow"
+
class OpenAPI(BaseModel):
openapi: str
info: Info
servers: Optional[List[Server]] = None
- paths: Dict[str, PathItem]
+ # Using Any for Specification Extensions
+ paths: Dict[str, Union[PathItem, Any]]
components: Optional[Components] = None
security: Optional[List[Dict[str, List[str]]]] = None
tags: Optional[List[Tag]] = None
externalDocs: Optional[ExternalDocumentation] = None
+
+ class Config:
+ extra = "allow"
+
+
+Schema.update_forward_refs()
+Operation.update_forward_refs()
+Encoding.update_forward_refs()
diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py
index 6f749ef9..4eb727bd 100644
--- a/fastapi/openapi/utils.py
+++ b/fastapi/openapi/utils.py
@@ -1,4 +1,6 @@
import http.client
+import inspect
+import warnings
from enum import Enum
from typing import Any, Dict, List, Optional, Sequence, Set, Tuple, Type, Union, cast
@@ -36,7 +38,11 @@ validation_error_definition = {
"title": "ValidationError",
"type": "object",
"properties": {
- "loc": {"title": "Location", "type": "array", "items": {"type": "string"}},
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
+ },
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
@@ -91,6 +97,8 @@ def get_openapi_operation_parameters(
for param in all_route_params:
field_info = param.field_info
field_info = cast(Param, field_info)
+ if not field_info.include_in_schema:
+ continue
parameter = {
"name": param.alias,
"in": field_info.in_.value,
@@ -137,7 +145,15 @@ def get_openapi_operation_request_body(
return request_body_oai
-def generate_operation_id(*, route: routing.APIRoute, method: str) -> str:
+def generate_operation_id(
+ *, route: routing.APIRoute, method: str
+) -> str: # pragma: nocover
+ warnings.warn(
+ "fastapi.openapi.utils.generate_operation_id() was deprecated, "
+ "it is not used internally, and will be removed soon",
+ DeprecationWarning,
+ stacklevel=2,
+ )
if route.operation_id:
return route.operation_id
path: str = route.path_format
@@ -151,7 +167,7 @@ def generate_operation_summary(*, route: routing.APIRoute, method: str) -> str:
def get_openapi_operation_metadata(
- *, route: routing.APIRoute, method: str
+ *, route: routing.APIRoute, method: str, operation_ids: Set[str]
) -> Dict[str, Any]:
operation: Dict[str, Any] = {}
if route.tags:
@@ -159,14 +175,25 @@ def get_openapi_operation_metadata(
operation["summary"] = generate_operation_summary(route=route, method=method)
if route.description:
operation["description"] = route.description
- operation["operationId"] = generate_operation_id(route=route, method=method)
+ operation_id = route.operation_id or route.unique_id
+ if operation_id in operation_ids:
+ message = (
+ f"Duplicate Operation ID {operation_id} for function "
+ + f"{route.endpoint.__name__}"
+ )
+ file_name = getattr(route.endpoint, "__globals__", {}).get("__file__")
+ if file_name:
+ message += f" at {file_name}"
+ warnings.warn(message)
+ operation_ids.add(operation_id)
+ operation["operationId"] = operation_id
if route.deprecated:
operation["deprecated"] = route.deprecated
return operation
def get_openapi_path(
- *, route: routing.APIRoute, model_name_map: Dict[type, str]
+ *, route: routing.APIRoute, model_name_map: Dict[type, str], operation_ids: Set[str]
) -> Tuple[Dict[str, Any], Dict[str, Any], Dict[str, Any]]:
path = {}
security_schemes: Dict[str, Any] = {}
@@ -180,7 +207,9 @@ def get_openapi_path(
route_response_media_type: Optional[str] = current_response_class.media_type
if route.include_in_schema:
for method in route.methods:
- operation = get_openapi_operation_metadata(route=route, method=method)
+ operation = get_openapi_operation_metadata(
+ route=route, method=method, operation_ids=operation_ids
+ )
parameters: List[Dict[str, Any]] = []
flat_dependant = get_flat_dependant(route.dependant, skip_repeats=True)
security_definitions, operation_security = get_openapi_security_definitions(
@@ -214,11 +243,25 @@ def get_openapi_path(
cb_security_schemes,
cb_definitions,
) = get_openapi_path(
- route=callback, model_name_map=model_name_map
+ route=callback,
+ model_name_map=model_name_map,
+ operation_ids=operation_ids,
)
callbacks[callback.name] = {callback.path: cb_path}
operation["callbacks"] = callbacks
- status_code = str(route.status_code)
+ if route.status_code is not None:
+ status_code = str(route.status_code)
+ else:
+ # It would probably make more sense for all response classes to have an
+ # explicit default status_code, and to extract it from them, instead of
+ # doing this inspection tricks, that would probably be in the future
+ # TODO: probably make status_code a default class attribute for all
+ # responses in Starlette
+ response_signature = inspect.signature(current_response_class.__init__)
+ status_code_param = response_signature.parameters.get("status_code")
+ if status_code_param is not None:
+ if isinstance(status_code_param.default, int):
+ status_code = str(status_code_param.default)
operation.setdefault("responses", {}).setdefault(status_code, {})[
"description"
] = route.response_description
@@ -304,6 +347,8 @@ def get_openapi_path(
"HTTPValidationError": validation_error_response_definition,
}
)
+ if route.openapi_extra:
+ deep_dict_update(operation, route.openapi_extra)
path[method.lower()] = operation
return path, security_schemes, definitions
@@ -349,15 +394,25 @@ def get_openapi(
routes: Sequence[BaseRoute],
tags: Optional[List[Dict[str, Any]]] = None,
servers: Optional[List[Dict[str, Union[str, Any]]]] = None,
+ terms_of_service: Optional[str] = None,
+ contact: Optional[Dict[str, Union[str, Any]]] = None,
+ license_info: Optional[Dict[str, Union[str, Any]]] = None,
) -> Dict[str, Any]:
- info = {"title": title, "version": version}
+ info: Dict[str, Any] = {"title": title, "version": version}
if description:
info["description"] = description
+ if terms_of_service:
+ info["termsOfService"] = terms_of_service
+ if contact:
+ info["contact"] = contact
+ if license_info:
+ info["license"] = license_info
output: Dict[str, Any] = {"openapi": openapi_version, "info": info}
if servers:
output["servers"] = servers
components: Dict[str, Dict[str, Any]] = {}
paths: Dict[str, Dict[str, Any]] = {}
+ operation_ids: Set[str] = set()
flat_models = get_flat_models_from_routes(routes)
model_name_map = get_model_name_map(flat_models)
definitions = get_model_definitions(
@@ -365,7 +420,9 @@ def get_openapi(
)
for route in routes:
if isinstance(route, routing.APIRoute):
- result = get_openapi_path(route=route, model_name_map=model_name_map)
+ result = get_openapi_path(
+ route=route, model_name_map=model_name_map, operation_ids=operation_ids
+ )
if result:
path, security_schemes, path_definitions = result
if path:
diff --git a/fastapi/param_functions.py b/fastapi/param_functions.py
index ff65d727..a553a146 100644
--- a/fastapi/param_functions.py
+++ b/fastapi/param_functions.py
@@ -20,6 +20,7 @@ def Path( # noqa: N802
example: Any = Undefined,
examples: Optional[Dict[str, Any]] = None,
deprecated: Optional[bool] = None,
+ include_in_schema: bool = True,
**extra: Any,
) -> Any:
return params.Path(
@@ -37,6 +38,7 @@ def Path( # noqa: N802
example=example,
examples=examples,
deprecated=deprecated,
+ include_in_schema=include_in_schema,
**extra,
)
@@ -57,6 +59,7 @@ def Query( # noqa: N802
example: Any = Undefined,
examples: Optional[Dict[str, Any]] = None,
deprecated: Optional[bool] = None,
+ include_in_schema: bool = True,
**extra: Any,
) -> Any:
return params.Query(
@@ -74,6 +77,7 @@ def Query( # noqa: N802
example=example,
examples=examples,
deprecated=deprecated,
+ include_in_schema=include_in_schema,
**extra,
)
@@ -95,6 +99,7 @@ def Header( # noqa: N802
example: Any = Undefined,
examples: Optional[Dict[str, Any]] = None,
deprecated: Optional[bool] = None,
+ include_in_schema: bool = True,
**extra: Any,
) -> Any:
return params.Header(
@@ -113,6 +118,7 @@ def Header( # noqa: N802
example=example,
examples=examples,
deprecated=deprecated,
+ include_in_schema=include_in_schema,
**extra,
)
@@ -133,6 +139,7 @@ def Cookie( # noqa: N802
example: Any = Undefined,
examples: Optional[Dict[str, Any]] = None,
deprecated: Optional[bool] = None,
+ include_in_schema: bool = True,
**extra: Any,
) -> Any:
return params.Cookie(
@@ -150,6 +157,7 @@ def Cookie( # noqa: N802
example=example,
examples=examples,
deprecated=deprecated,
+ include_in_schema=include_in_schema,
**extra,
)
diff --git a/fastapi/params.py b/fastapi/params.py
index 3cab98b7..042bbd42 100644
--- a/fastapi/params.py
+++ b/fastapi/params.py
@@ -31,11 +31,13 @@ class Param(FieldInfo):
example: Any = Undefined,
examples: Optional[Dict[str, Any]] = None,
deprecated: Optional[bool] = None,
+ include_in_schema: bool = True,
**extra: Any,
):
self.deprecated = deprecated
self.example = example
self.examples = examples
+ self.include_in_schema = include_in_schema
super().__init__(
default,
alias=alias,
@@ -75,6 +77,7 @@ class Path(Param):
example: Any = Undefined,
examples: Optional[Dict[str, Any]] = None,
deprecated: Optional[bool] = None,
+ include_in_schema: bool = True,
**extra: Any,
):
self.in_ = self.in_
@@ -93,6 +96,7 @@ class Path(Param):
deprecated=deprecated,
example=example,
examples=examples,
+ include_in_schema=include_in_schema,
**extra,
)
@@ -117,6 +121,7 @@ class Query(Param):
example: Any = Undefined,
examples: Optional[Dict[str, Any]] = None,
deprecated: Optional[bool] = None,
+ include_in_schema: bool = True,
**extra: Any,
):
super().__init__(
@@ -134,6 +139,7 @@ class Query(Param):
deprecated=deprecated,
example=example,
examples=examples,
+ include_in_schema=include_in_schema,
**extra,
)
@@ -159,6 +165,7 @@ class Header(Param):
example: Any = Undefined,
examples: Optional[Dict[str, Any]] = None,
deprecated: Optional[bool] = None,
+ include_in_schema: bool = True,
**extra: Any,
):
self.convert_underscores = convert_underscores
@@ -177,6 +184,7 @@ class Header(Param):
deprecated=deprecated,
example=example,
examples=examples,
+ include_in_schema=include_in_schema,
**extra,
)
@@ -201,6 +209,7 @@ class Cookie(Param):
example: Any = Undefined,
examples: Optional[Dict[str, Any]] = None,
deprecated: Optional[bool] = None,
+ include_in_schema: bool = True,
**extra: Any,
):
super().__init__(
@@ -218,6 +227,7 @@ class Cookie(Param):
deprecated=deprecated,
example=example,
examples=examples,
+ include_in_schema=include_in_schema,
**extra,
)
diff --git a/fastapi/routing.py b/fastapi/routing.py
index 9b51f03c..db39d3ff 100644
--- a/fastapi/routing.py
+++ b/fastapi/routing.py
@@ -1,8 +1,9 @@
import asyncio
+import dataclasses
import email.message
-import enum
import inspect
import json
+from enum import Enum, IntEnum
from typing import (
Any,
Callable,
@@ -12,6 +13,7 @@ from typing import (
Optional,
Sequence,
Set,
+ Tuple,
Type,
Union,
)
@@ -32,7 +34,7 @@ from fastapi.types import DecoratedCallable
from fastapi.utils import (
create_cloned_field,
create_response_field,
- generate_operation_id_for_path,
+ generate_unique_id,
get_value_or_default,
)
from pydantic import BaseModel
@@ -43,7 +45,7 @@ from starlette.concurrency import run_in_threadpool
from starlette.exceptions import HTTPException
from starlette.requests import Request
from starlette.responses import JSONResponse, Response
-from starlette.routing import BaseRoute
+from starlette.routing import BaseRoute, Match
from starlette.routing import Mount as Mount # noqa
from starlette.routing import (
compile_path,
@@ -52,7 +54,7 @@ from starlette.routing import (
websocket_session,
)
from starlette.status import WS_1008_POLICY_VIOLATION
-from starlette.types import ASGIApp
+from starlette.types import ASGIApp, Scope
from starlette.websockets import WebSocket
@@ -64,6 +66,13 @@ def _prepare_response_content(
exclude_none: bool = False,
) -> Any:
if isinstance(res, BaseModel):
+ read_with_orm_mode = getattr(res.__config__, "read_with_orm_mode", None)
+ if read_with_orm_mode:
+ # Let from_orm extract the data from this model instead of converting
+ # it now to a dict.
+ # Otherwise there's no way to extract lazy data that requires attribute
+ # access instead of dict iteration, e.g. lazy relationships.
+ return res
return res.dict(
by_alias=True,
exclude_unset=exclude_unset,
@@ -90,6 +99,8 @@ def _prepare_response_content(
)
for k, v in res.items()
}
+ elif dataclasses.is_dataclass(res):
+ return dataclasses.asdict(res)
return res
@@ -116,7 +127,7 @@ async def serialize_response(
if is_coroutine:
value, errors_ = field.validate(response_content, {}, loc=("response",))
else:
- value, errors_ = await run_in_threadpool(
+ value, errors_ = await run_in_threadpool( # type: ignore[misc]
field.validate, response_content, {}, loc=("response",)
)
if isinstance(errors_, ErrorWrapper):
@@ -154,7 +165,7 @@ async def run_endpoint_function(
def get_request_handler(
dependant: Dependant,
body_field: Optional[ModelField] = None,
- status_code: int = 200,
+ status_code: Optional[int] = None,
response_class: Union[Type[Response], DefaultPlaceholder] = Default(JSONResponse),
response_field: Optional[ModelField] = None,
response_model_include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
@@ -184,7 +195,9 @@ def get_request_handler(
if body_bytes:
json_body: Any = Undefined
content_type_value = request.headers.get("content-type")
- if content_type_value:
+ if not content_type_value:
+ json_body = await request.json()
+ else:
message = email.message.Message()
message["content-type"] = content_type_value
if message.get_content_maintype() == "application":
@@ -230,11 +243,12 @@ def get_request_handler(
exclude_none=response_model_exclude_none,
is_coroutine=is_coroutine,
)
- response = actual_response_class(
- content=response_data,
- status_code=status_code,
- background=background_tasks, # type: ignore # in Starlette
- )
+ response_args: Dict[str, Any] = {"background": background_tasks}
+ # If status_code was set, use it, otherwise use the default from the
+ # response class, in the case of redirect it's 307
+ if status_code is not None:
+ response_args["status_code"] = status_code
+ response = actual_response_class(response_data, **response_args)
response.headers.raw.extend(sub_response.headers.raw)
if sub_response.status_code:
response.status_code = sub_response.status_code
@@ -283,6 +297,12 @@ class APIWebSocketRoute(routing.WebSocketRoute):
)
self.path_regex, self.path_format, self.param_convertors = compile_path(path)
+ def matches(self, scope: Scope) -> Tuple[Match, Scope]:
+ match, child_scope = super().matches(scope)
+ if match != Match.NONE:
+ child_scope["route"] = self
+ return match, child_scope
+
class APIRoute(routing.Route):
def __init__(
@@ -291,8 +311,8 @@ class APIRoute(routing.Route):
endpoint: Callable[..., Any],
*,
response_model: Optional[Type[Any]] = None,
- status_code: int = 200,
- tags: Optional[List[str]] = None,
+ status_code: Optional[int] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -314,21 +334,48 @@ class APIRoute(routing.Route):
),
dependency_overrides_provider: Optional[Any] = None,
callbacks: Optional[List[BaseRoute]] = None,
+ openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Union[
+ Callable[["APIRoute"], str], DefaultPlaceholder
+ ] = Default(generate_unique_id),
) -> None:
- # normalise enums e.g. http.HTTPStatus
- if isinstance(status_code, enum.IntEnum):
- status_code = int(status_code)
self.path = path
self.endpoint = endpoint
+ self.response_model = response_model
+ self.summary = summary
+ self.response_description = response_description
+ self.deprecated = deprecated
+ self.operation_id = operation_id
+ self.response_model_include = response_model_include
+ self.response_model_exclude = response_model_exclude
+ self.response_model_by_alias = response_model_by_alias
+ self.response_model_exclude_unset = response_model_exclude_unset
+ self.response_model_exclude_defaults = response_model_exclude_defaults
+ self.response_model_exclude_none = response_model_exclude_none
+ self.include_in_schema = include_in_schema
+ self.response_class = response_class
+ self.dependency_overrides_provider = dependency_overrides_provider
+ self.callbacks = callbacks
+ self.openapi_extra = openapi_extra
+ self.generate_unique_id_function = generate_unique_id_function
+ self.tags = tags or []
+ self.responses = responses or {}
self.name = get_name(endpoint) if name is None else name
self.path_regex, self.path_format, self.param_convertors = compile_path(path)
if methods is None:
methods = ["GET"]
self.methods: Set[str] = set([method.upper() for method in methods])
- self.unique_id = generate_operation_id_for_path(
- name=self.name, path=self.path_format, method=list(methods)[0]
- )
- self.response_model = response_model
+ if isinstance(generate_unique_id_function, DefaultPlaceholder):
+ current_generate_unique_id: Callable[
+ ["APIRoute"], str
+ ] = generate_unique_id_function.value
+ else:
+ current_generate_unique_id = generate_unique_id_function
+ self.unique_id = self.operation_id or current_generate_unique_id(self)
+ # normalize enums e.g. http.HTTPStatus
+ if isinstance(status_code, IntEnum):
+ status_code = int(status_code)
+ self.status_code = status_code
if self.response_model:
assert (
status_code not in STATUS_CODES_WITH_NO_BODY
@@ -350,19 +397,14 @@ class APIRoute(routing.Route):
else:
self.response_field = None # type: ignore
self.secure_cloned_response_field = None
- self.status_code = status_code
- self.tags = tags or []
if dependencies:
self.dependencies = list(dependencies)
else:
self.dependencies = []
- self.summary = summary
self.description = description or inspect.cleandoc(self.endpoint.__doc__ or "")
# if a "form feed" character (page break) is found in the description text,
# truncate description text to the content preceding the first "form feed"
self.description = self.description.split("\f")[0]
- self.response_description = response_description
- self.responses = responses or {}
response_fields = {}
for additional_status_code, response in self.responses.items():
assert isinstance(response, dict), "An additional response must be a dict"
@@ -378,16 +420,6 @@ class APIRoute(routing.Route):
self.response_fields: Dict[Union[int, str], ModelField] = response_fields
else:
self.response_fields = {}
- self.deprecated = deprecated
- self.operation_id = operation_id
- self.response_model_include = response_model_include
- self.response_model_exclude = response_model_exclude
- self.response_model_by_alias = response_model_by_alias
- self.response_model_exclude_unset = response_model_exclude_unset
- self.response_model_exclude_defaults = response_model_exclude_defaults
- self.response_model_exclude_none = response_model_exclude_none
- self.include_in_schema = include_in_schema
- self.response_class = response_class
assert callable(endpoint), "An endpoint must be a callable"
self.dependant = get_dependant(path=self.path_format, call=self.endpoint)
@@ -397,8 +429,6 @@ class APIRoute(routing.Route):
get_parameterless_sub_dependant(depends=depends, path=self.path_format),
)
self.body_field = get_body_field(dependant=self.dependant, name=self.unique_id)
- self.dependency_overrides_provider = dependency_overrides_provider
- self.callbacks = callbacks
self.app = request_response(self.get_route_handler())
def get_route_handler(self) -> Callable[[Request], Coroutine[Any, Any, Response]]:
@@ -417,13 +447,19 @@ class APIRoute(routing.Route):
dependency_overrides_provider=self.dependency_overrides_provider,
)
+ def matches(self, scope: Scope) -> Tuple[Match, Scope]:
+ match, child_scope = super().matches(scope)
+ if match != Match.NONE:
+ child_scope["route"] = self
+ return match, child_scope
+
class APIRouter(routing.Router):
def __init__(
self,
*,
prefix: str = "",
- tags: Optional[List[str]] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
default_response_class: Type[Response] = Default(JSONResponse),
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
@@ -437,13 +473,16 @@ class APIRouter(routing.Router):
on_shutdown: Optional[Sequence[Callable[[], Any]]] = None,
deprecated: Optional[bool] = None,
include_in_schema: bool = True,
+ generate_unique_id_function: Callable[[APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> None:
super().__init__(
- routes=routes, # type: ignore # in Starlette
+ routes=routes,
redirect_slashes=redirect_slashes,
- default=default, # type: ignore # in Starlette
- on_startup=on_startup, # type: ignore # in Starlette
- on_shutdown=on_shutdown, # type: ignore # in Starlette
+ default=default,
+ on_startup=on_startup,
+ on_shutdown=on_shutdown,
)
if prefix:
assert prefix.startswith("/"), "A path prefix must start with '/'"
@@ -451,7 +490,7 @@ class APIRouter(routing.Router):
"/"
), "A path prefix must not end with '/', as the routes will start with '/'"
self.prefix = prefix
- self.tags: List[str] = tags or []
+ self.tags: List[Union[str, Enum]] = tags or []
self.dependencies = list(dependencies or []) or []
self.deprecated = deprecated
self.include_in_schema = include_in_schema
@@ -460,6 +499,7 @@ class APIRouter(routing.Router):
self.dependency_overrides_provider = dependency_overrides_provider
self.route_class = route_class
self.default_response_class = default_response_class
+ self.generate_unique_id_function = generate_unique_id_function
def add_api_route(
self,
@@ -467,8 +507,8 @@ class APIRouter(routing.Router):
endpoint: Callable[..., Any],
*,
response_model: Optional[Type[Any]] = None,
- status_code: int = 200,
- tags: Optional[List[str]] = None,
+ status_code: Optional[int] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -490,6 +530,10 @@ class APIRouter(routing.Router):
name: Optional[str] = None,
route_class_override: Optional[Type[APIRoute]] = None,
callbacks: Optional[List[BaseRoute]] = None,
+ openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Union[
+ Callable[[APIRoute], str], DefaultPlaceholder
+ ] = Default(generate_unique_id),
) -> None:
route_class = route_class_override or self.route_class
responses = responses or {}
@@ -506,6 +550,9 @@ class APIRouter(routing.Router):
current_callbacks = self.callbacks.copy()
if callbacks:
current_callbacks.extend(callbacks)
+ current_generate_unique_id = get_value_or_default(
+ generate_unique_id_function, self.generate_unique_id_function
+ )
route = route_class(
self.prefix + path,
endpoint=endpoint,
@@ -531,6 +578,8 @@ class APIRouter(routing.Router):
name=name,
dependency_overrides_provider=self.dependency_overrides_provider,
callbacks=current_callbacks,
+ openapi_extra=openapi_extra,
+ generate_unique_id_function=current_generate_unique_id,
)
self.routes.append(route)
@@ -539,8 +588,8 @@ class APIRouter(routing.Router):
path: str,
*,
response_model: Optional[Type[Any]] = None,
- status_code: int = 200,
- tags: Optional[List[str]] = None,
+ status_code: Optional[int] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -559,6 +608,10 @@ class APIRouter(routing.Router):
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
+ openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> Callable[[DecoratedCallable], DecoratedCallable]:
def decorator(func: DecoratedCallable) -> DecoratedCallable:
self.add_api_route(
@@ -585,6 +638,8 @@ class APIRouter(routing.Router):
response_class=response_class,
name=name,
callbacks=callbacks,
+ openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
return func
@@ -594,7 +649,7 @@ class APIRouter(routing.Router):
self, path: str, endpoint: Callable[..., Any], name: Optional[str] = None
) -> None:
route = APIWebSocketRoute(
- path,
+ self.prefix + path,
endpoint=endpoint,
name=name,
dependency_overrides_provider=self.dependency_overrides_provider,
@@ -615,13 +670,16 @@ class APIRouter(routing.Router):
router: "APIRouter",
*,
prefix: str = "",
- tags: Optional[List[str]] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
default_response_class: Type[Response] = Default(JSONResponse),
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
callbacks: Optional[List[BaseRoute]] = None,
deprecated: Optional[bool] = None,
include_in_schema: bool = True,
+ generate_unique_id_function: Callable[[APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> None:
if prefix:
assert prefix.startswith("/"), "A path prefix must start with '/'"
@@ -662,6 +720,12 @@ class APIRouter(routing.Router):
current_callbacks.extend(callbacks)
if route.callbacks:
current_callbacks.extend(route.callbacks)
+ current_generate_unique_id = get_value_or_default(
+ route.generate_unique_id_function,
+ router.generate_unique_id_function,
+ generate_unique_id_function,
+ self.generate_unique_id_function,
+ )
self.add_api_route(
prefix + route.path,
route.endpoint,
@@ -689,9 +753,11 @@ class APIRouter(routing.Router):
name=route.name,
route_class_override=type(route),
callbacks=current_callbacks,
+ openapi_extra=route.openapi_extra,
+ generate_unique_id_function=current_generate_unique_id,
)
elif isinstance(route, routing.Route):
- methods = list(route.methods or []) # type: ignore # in Starlette
+ methods = list(route.methods or [])
self.add_route(
prefix + route.path,
route.endpoint,
@@ -717,8 +783,8 @@ class APIRouter(routing.Router):
path: str,
*,
response_model: Optional[Type[Any]] = None,
- status_code: int = 200,
- tags: Optional[List[str]] = None,
+ status_code: Optional[int] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -736,6 +802,10 @@ class APIRouter(routing.Router):
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
+ openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.api_route(
path=path,
@@ -760,6 +830,8 @@ class APIRouter(routing.Router):
response_class=response_class,
name=name,
callbacks=callbacks,
+ openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
def put(
@@ -767,8 +839,8 @@ class APIRouter(routing.Router):
path: str,
*,
response_model: Optional[Type[Any]] = None,
- status_code: int = 200,
- tags: Optional[List[str]] = None,
+ status_code: Optional[int] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -786,6 +858,10 @@ class APIRouter(routing.Router):
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
+ openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.api_route(
path=path,
@@ -810,6 +886,8 @@ class APIRouter(routing.Router):
response_class=response_class,
name=name,
callbacks=callbacks,
+ openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
def post(
@@ -817,8 +895,8 @@ class APIRouter(routing.Router):
path: str,
*,
response_model: Optional[Type[Any]] = None,
- status_code: int = 200,
- tags: Optional[List[str]] = None,
+ status_code: Optional[int] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -836,6 +914,10 @@ class APIRouter(routing.Router):
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
+ openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.api_route(
path=path,
@@ -860,6 +942,8 @@ class APIRouter(routing.Router):
response_class=response_class,
name=name,
callbacks=callbacks,
+ openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
def delete(
@@ -867,8 +951,8 @@ class APIRouter(routing.Router):
path: str,
*,
response_model: Optional[Type[Any]] = None,
- status_code: int = 200,
- tags: Optional[List[str]] = None,
+ status_code: Optional[int] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -886,6 +970,10 @@ class APIRouter(routing.Router):
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
+ openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.api_route(
path=path,
@@ -910,6 +998,8 @@ class APIRouter(routing.Router):
response_class=response_class,
name=name,
callbacks=callbacks,
+ openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
def options(
@@ -917,8 +1007,8 @@ class APIRouter(routing.Router):
path: str,
*,
response_model: Optional[Type[Any]] = None,
- status_code: int = 200,
- tags: Optional[List[str]] = None,
+ status_code: Optional[int] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -936,6 +1026,10 @@ class APIRouter(routing.Router):
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
+ openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.api_route(
path=path,
@@ -960,6 +1054,8 @@ class APIRouter(routing.Router):
response_class=response_class,
name=name,
callbacks=callbacks,
+ openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
def head(
@@ -967,8 +1063,8 @@ class APIRouter(routing.Router):
path: str,
*,
response_model: Optional[Type[Any]] = None,
- status_code: int = 200,
- tags: Optional[List[str]] = None,
+ status_code: Optional[int] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -986,6 +1082,10 @@ class APIRouter(routing.Router):
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
+ openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.api_route(
path=path,
@@ -1010,6 +1110,8 @@ class APIRouter(routing.Router):
response_class=response_class,
name=name,
callbacks=callbacks,
+ openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
def patch(
@@ -1017,8 +1119,8 @@ class APIRouter(routing.Router):
path: str,
*,
response_model: Optional[Type[Any]] = None,
- status_code: int = 200,
- tags: Optional[List[str]] = None,
+ status_code: Optional[int] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -1036,6 +1138,10 @@ class APIRouter(routing.Router):
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
+ openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.api_route(
path=path,
@@ -1060,6 +1166,8 @@ class APIRouter(routing.Router):
response_class=response_class,
name=name,
callbacks=callbacks,
+ openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
def trace(
@@ -1067,8 +1175,8 @@ class APIRouter(routing.Router):
path: str,
*,
response_model: Optional[Type[Any]] = None,
- status_code: int = 200,
- tags: Optional[List[str]] = None,
+ status_code: Optional[int] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
@@ -1086,6 +1194,10 @@ class APIRouter(routing.Router):
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
callbacks: Optional[List[BaseRoute]] = None,
+ openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[APIRoute], str] = Default(
+ generate_unique_id
+ ),
) -> Callable[[DecoratedCallable], DecoratedCallable]:
return self.api_route(
@@ -1111,4 +1223,6 @@ class APIRouter(routing.Router):
response_class=response_class,
name=name,
callbacks=callbacks,
+ openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
)
diff --git a/fastapi/security/api_key.py b/fastapi/security/api_key.py
index e4dacb38..36ab60e3 100644
--- a/fastapi/security/api_key.py
+++ b/fastapi/security/api_key.py
@@ -13,9 +13,16 @@ class APIKeyBase(SecurityBase):
class APIKeyQuery(APIKeyBase):
def __init__(
- self, *, name: str, scheme_name: Optional[str] = None, auto_error: bool = True
+ self,
+ *,
+ name: str,
+ scheme_name: Optional[str] = None,
+ description: Optional[str] = None,
+ auto_error: bool = True
):
- self.model: APIKey = APIKey(**{"in": APIKeyIn.query}, name=name)
+ self.model: APIKey = APIKey(
+ **{"in": APIKeyIn.query}, name=name, description=description
+ )
self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error
@@ -33,9 +40,16 @@ class APIKeyQuery(APIKeyBase):
class APIKeyHeader(APIKeyBase):
def __init__(
- self, *, name: str, scheme_name: Optional[str] = None, auto_error: bool = True
+ self,
+ *,
+ name: str,
+ scheme_name: Optional[str] = None,
+ description: Optional[str] = None,
+ auto_error: bool = True
):
- self.model: APIKey = APIKey(**{"in": APIKeyIn.header}, name=name)
+ self.model: APIKey = APIKey(
+ **{"in": APIKeyIn.header}, name=name, description=description
+ )
self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error
@@ -53,9 +67,16 @@ class APIKeyHeader(APIKeyBase):
class APIKeyCookie(APIKeyBase):
def __init__(
- self, *, name: str, scheme_name: Optional[str] = None, auto_error: bool = True
+ self,
+ *,
+ name: str,
+ scheme_name: Optional[str] = None,
+ description: Optional[str] = None,
+ auto_error: bool = True
):
- self.model: APIKey = APIKey(**{"in": APIKeyIn.cookie}, name=name)
+ self.model: APIKey = APIKey(
+ **{"in": APIKeyIn.cookie}, name=name, description=description
+ )
self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error
diff --git a/fastapi/security/http.py b/fastapi/security/http.py
index 3258bd05..1b473c69 100644
--- a/fastapi/security/http.py
+++ b/fastapi/security/http.py
@@ -24,9 +24,14 @@ class HTTPAuthorizationCredentials(BaseModel):
class HTTPBase(SecurityBase):
def __init__(
- self, *, scheme: str, scheme_name: Optional[str] = None, auto_error: bool = True
+ self,
+ *,
+ scheme: str,
+ scheme_name: Optional[str] = None,
+ description: Optional[str] = None,
+ auto_error: bool = True,
):
- self.model = HTTPBaseModel(scheme=scheme)
+ self.model = HTTPBaseModel(scheme=scheme, description=description)
self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error
@@ -51,9 +56,10 @@ class HTTPBasic(HTTPBase):
*,
scheme_name: Optional[str] = None,
realm: Optional[str] = None,
+ description: Optional[str] = None,
auto_error: bool = True,
):
- self.model = HTTPBaseModel(scheme="basic")
+ self.model = HTTPBaseModel(scheme="basic", description=description)
self.scheme_name = scheme_name or self.__class__.__name__
self.realm = realm
self.auto_error = auto_error
@@ -97,9 +103,10 @@ class HTTPBearer(HTTPBase):
*,
bearerFormat: Optional[str] = None,
scheme_name: Optional[str] = None,
+ description: Optional[str] = None,
auto_error: bool = True,
):
- self.model = HTTPBearerModel(bearerFormat=bearerFormat)
+ self.model = HTTPBearerModel(bearerFormat=bearerFormat, description=description)
self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error
@@ -127,8 +134,14 @@ class HTTPBearer(HTTPBase):
class HTTPDigest(HTTPBase):
- def __init__(self, *, scheme_name: Optional[str] = None, auto_error: bool = True):
- self.model = HTTPBaseModel(scheme="digest")
+ def __init__(
+ self,
+ *,
+ scheme_name: Optional[str] = None,
+ description: Optional[str] = None,
+ auto_error: bool = True,
+ ):
+ self.model = HTTPBaseModel(scheme="digest", description=description)
self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error
diff --git a/fastapi/security/oauth2.py b/fastapi/security/oauth2.py
index 46571ad5..bdc6e2ea 100644
--- a/fastapi/security/oauth2.py
+++ b/fastapi/security/oauth2.py
@@ -118,9 +118,10 @@ class OAuth2(SecurityBase):
*,
flows: Union[OAuthFlowsModel, Dict[str, Dict[str, Any]]] = OAuthFlowsModel(),
scheme_name: Optional[str] = None,
+ description: Optional[str] = None,
auto_error: Optional[bool] = True
):
- self.model = OAuth2Model(flows=flows)
+ self.model = OAuth2Model(flows=flows, description=description)
self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error
@@ -142,12 +143,18 @@ class OAuth2PasswordBearer(OAuth2):
tokenUrl: str,
scheme_name: Optional[str] = None,
scopes: Optional[Dict[str, str]] = None,
+ description: Optional[str] = None,
auto_error: bool = True,
):
if not scopes:
scopes = {}
flows = OAuthFlowsModel(password={"tokenUrl": tokenUrl, "scopes": scopes})
- super().__init__(flows=flows, scheme_name=scheme_name, auto_error=auto_error)
+ super().__init__(
+ flows=flows,
+ scheme_name=scheme_name,
+ description=description,
+ auto_error=auto_error,
+ )
async def __call__(self, request: Request) -> Optional[str]:
authorization: str = request.headers.get("Authorization")
@@ -172,6 +179,7 @@ class OAuth2AuthorizationCodeBearer(OAuth2):
refreshUrl: Optional[str] = None,
scheme_name: Optional[str] = None,
scopes: Optional[Dict[str, str]] = None,
+ description: Optional[str] = None,
auto_error: bool = True,
):
if not scopes:
@@ -184,7 +192,12 @@ class OAuth2AuthorizationCodeBearer(OAuth2):
"scopes": scopes,
}
)
- super().__init__(flows=flows, scheme_name=scheme_name, auto_error=auto_error)
+ super().__init__(
+ flows=flows,
+ scheme_name=scheme_name,
+ description=description,
+ auto_error=auto_error,
+ )
async def __call__(self, request: Request) -> Optional[str]:
authorization: str = request.headers.get("Authorization")
diff --git a/fastapi/security/open_id_connect_url.py b/fastapi/security/open_id_connect_url.py
index a98c13f8..dfe9f7b2 100644
--- a/fastapi/security/open_id_connect_url.py
+++ b/fastapi/security/open_id_connect_url.py
@@ -13,9 +13,12 @@ class OpenIdConnect(SecurityBase):
*,
openIdConnectUrl: str,
scheme_name: Optional[str] = None,
+ description: Optional[str] = None,
auto_error: bool = True
):
- self.model = OpenIdConnectModel(openIdConnectUrl=openIdConnectUrl)
+ self.model = OpenIdConnectModel(
+ openIdConnectUrl=openIdConnectUrl, description=description
+ )
self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error
diff --git a/fastapi/utils.py b/fastapi/utils.py
index 8913d85b..b9301499 100644
--- a/fastapi/utils.py
+++ b/fastapi/utils.py
@@ -1,8 +1,9 @@
import functools
import re
+import warnings
from dataclasses import is_dataclass
from enum import Enum
-from typing import Any, Dict, Optional, Set, Type, Union, cast
+from typing import TYPE_CHECKING, Any, Dict, Optional, Set, Type, Union, cast
import fastapi
from fastapi.datastructures import DefaultPlaceholder, DefaultType
@@ -13,6 +14,9 @@ from pydantic.fields import FieldInfo, ModelField, UndefinedType
from pydantic.schema import model_process_schema
from pydantic.utils import lenient_issubclass
+if TYPE_CHECKING: # pragma: nocover
+ from .routing import APIRoute
+
def get_model_definitions(
*,
@@ -119,13 +123,29 @@ def create_cloned_field(
return new_field
-def generate_operation_id_for_path(*, name: str, path: str, method: str) -> str:
+def generate_operation_id_for_path(
+ *, name: str, path: str, method: str
+) -> str: # pragma: nocover
+ warnings.warn(
+ "fastapi.utils.generate_operation_id_for_path() was deprecated, "
+ "it is not used internally, and will be removed soon",
+ DeprecationWarning,
+ stacklevel=2,
+ )
operation_id = name + path
operation_id = re.sub("[^0-9a-zA-Z_]", "_", operation_id)
operation_id = operation_id + "_" + method.lower()
return operation_id
+def generate_unique_id(route: "APIRoute") -> str:
+ operation_id = route.name + route.path_format
+ operation_id = re.sub("[^0-9a-zA-Z_]", "_", operation_id)
+ assert route.methods
+ operation_id = operation_id + "_" + list(route.methods)[0].lower()
+ return operation_id
+
+
def deep_dict_update(main_dict: Dict[Any, Any], update_dict: Dict[Any, Any]) -> None:
for key in update_dict:
if (
diff --git a/mypy.ini b/mypy.ini
deleted file mode 100644
index e6a33cff..00000000
--- a/mypy.ini
+++ /dev/null
@@ -1,25 +0,0 @@
-[mypy]
-
-# --strict
-disallow_any_generics = True
-disallow_subclassing_any = True
-disallow_untyped_calls = True
-disallow_untyped_defs = True
-disallow_incomplete_defs = True
-check_untyped_defs = True
-disallow_untyped_decorators = True
-no_implicit_optional = True
-warn_redundant_casts = True
-warn_unused_ignores = True
-warn_return_any = True
-implicit_reexport = False
-strict_equality = True
-# --strict end
-
-[mypy-fastapi.concurrency]
-warn_unused_ignores = False
-ignore_missing_imports = True
-
-[mypy-fastapi.tests.*]
-ignore_missing_imports = True
-check_untyped_defs = True
diff --git a/pyproject.toml b/pyproject.toml
index 25440bde..b23d0db0 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -22,80 +22,125 @@ classifiers = [
"Development Status :: 4 - Beta",
"Environment :: Web Environment",
"Framework :: AsyncIO",
+ "Framework :: FastAPI",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
"Topic :: Internet :: WWW/HTTP :: HTTP Servers",
"Topic :: Internet :: WWW/HTTP",
]
requires = [
- "starlette ==0.14.2",
- "pydantic >=1.6.2,!=1.7,!=1.7.1,!=1.7.2,!=1.7.3,!=1.8,!=1.8.1,<2.0.0"
+ "starlette==0.19.1",
+ "pydantic >=1.6.2,!=1.7,!=1.7.1,!=1.7.2,!=1.7.3,!=1.8,!=1.8.1,<2.0.0",
]
description-file = "README.md"
-requires-python = ">=3.6"
+requires-python = ">=3.6.1"
[tool.flit.metadata.urls]
Documentation = "https://fastapi.tiangolo.com/"
[tool.flit.metadata.requires-extra]
test = [
- "pytest ==5.4.3",
- "pytest-cov ==2.10.0",
- "pytest-asyncio >=0.14.0,<0.15.0",
- "mypy ==0.812",
+ "pytest >=6.2.4,<7.0.0",
+ "pytest-cov >=2.12.0,<4.0.0",
+ "mypy ==0.910",
"flake8 >=3.8.3,<4.0.0",
- "black ==20.8b1",
+ "black == 22.3.0",
"isort >=5.0.6,<6.0.0",
"requests >=2.24.0,<3.0.0",
- "httpx >=0.14.0,<0.15.0",
+ "httpx >=0.14.0,<0.19.0",
"email_validator >=1.1.1,<2.0.0",
- "sqlalchemy >=1.3.18,<1.4.0",
+ "sqlalchemy >=1.3.18,<1.5.0",
"peewee >=3.13.3,<4.0.0",
- "databases[sqlite] >=0.3.2,<0.4.0",
+ "databases[sqlite] >=0.3.2,<0.6.0",
"orjson >=3.2.1,<4.0.0",
- "ujson >=4.0.1,<5.0.0",
- "async_exit_stack >=1.0.1,<2.0.0",
- "async_generator >=1.10,<2.0.0",
+ "ujson >=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0",
"python-multipart >=0.0.5,<0.0.6",
- "aiofiles >=0.5.0,<0.6.0",
- "flask >=1.1.2,<2.0.0"
+ "flask >=1.1.2,<3.0.0",
+ "anyio[trio] >=3.2.1,<4.0.0",
+
+ # types
+ "types-ujson ==4.2.1",
+ "types-orjson ==3.6.2",
+ "types-dataclasses ==0.6.5; python_version<'3.7'",
]
doc = [
"mkdocs >=1.1.2,<2.0.0",
- "mkdocs-material >=7.1.9,<8.0.0",
- "markdown-include >=0.6.0,<0.7.0",
- "mkdocs-markdownextradata-plugin >=0.1.7,<0.2.0",
- "typer-cli >=0.0.12,<0.0.13",
- "pyyaml >=5.3.1,<6.0.0"
+ "mkdocs-material >=8.1.4,<9.0.0",
+ "mdx-include >=1.4.1,<2.0.0",
+ "mkdocs-markdownextradata-plugin >=0.1.7,<0.3.0",
+ # TODO: upgrade and enable typer-cli once it supports Click 8.x.x
+ # "typer-cli >=0.0.12,<0.0.13",
+ "typer >=0.4.1,<0.5.0",
+ "pyyaml >=5.3.1,<7.0.0",
]
dev = [
- "python-jose[cryptography] >=3.1.0,<4.0.0",
+ "python-jose[cryptography] >=3.3.0,<4.0.0",
"passlib[bcrypt] >=1.7.2,<2.0.0",
- "autoflake >=1.3.1,<2.0.0",
+ "autoflake >=1.4.0,<2.0.0",
"flake8 >=3.8.3,<4.0.0",
- "uvicorn[standard] >=0.12.0,<0.14.0",
- "graphene >=2.1.8,<3.0.0"
+ "uvicorn[standard] >=0.12.0,<0.18.0",
]
all = [
"requests >=2.24.0,<3.0.0",
- "aiofiles >=0.5.0,<0.6.0",
- "jinja2 >=2.11.2,<3.0.0",
+ "jinja2 >=2.11.2,<4.0.0",
"python-multipart >=0.0.5,<0.0.6",
- "itsdangerous >=1.1.0,<2.0.0",
- "pyyaml >=5.3.1,<6.0.0",
- "graphene >=2.1.8,<3.0.0",
- "ujson >=4.0.1,<5.0.0",
+ "itsdangerous >=1.1.0,<3.0.0",
+ "pyyaml >=5.3.1,<7.0.0",
+ "ujson >=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0",
"orjson >=3.2.1,<4.0.0",
"email_validator >=1.1.1,<2.0.0",
- "uvicorn[standard] >=0.12.0,<0.14.0",
- "async_exit_stack >=1.0.1,<2.0.0",
- "async_generator >=1.10,<2.0.0"
+ "uvicorn[standard] >=0.12.0,<0.18.0",
]
[tool.isort]
profile = "black"
known_third_party = ["fastapi", "pydantic", "starlette"]
+
+[tool.mypy]
+# --strict
+disallow_any_generics = true
+disallow_subclassing_any = true
+disallow_untyped_calls = true
+disallow_untyped_defs = true
+disallow_incomplete_defs = true
+check_untyped_defs = true
+disallow_untyped_decorators = true
+no_implicit_optional = true
+warn_redundant_casts = true
+warn_unused_ignores = true
+warn_return_any = true
+implicit_reexport = false
+strict_equality = true
+# --strict end
+
+[[tool.mypy.overrides]]
+module = "fastapi.concurrency"
+warn_unused_ignores = false
+ignore_missing_imports = true
+
+[[tool.mypy.overrides]]
+module = "fastapi.tests.*"
+ignore_missing_imports = true
+check_untyped_defs = true
+
+[tool.pytest.ini_options]
+addopts = [
+ "--strict-config",
+ "--strict-markers",
+]
+xfail_strict = true
+junit_family = "xunit2"
+filterwarnings = [
+ "error",
+ # TODO: needed by asyncio in Python 3.9.7 https://bugs.python.org/issue45097, try to remove on 3.9.8
+ 'ignore:The loop argument is deprecated since Python 3\.8, and scheduled for removal in Python 3\.10:DeprecationWarning:asyncio',
+ 'ignore:starlette.middleware.wsgi is deprecated and will be removed in a future release\..*:DeprecationWarning:starlette',
+ # TODO: remove after dropping support for Python 3.6
+ 'ignore:Python 3.6 is no longer supported by the Python core team. Therefore, support for it is deprecated in cryptography and will be removed in a future release.:UserWarning:jose',
+]
diff --git a/scripts/format-imports.sh b/scripts/format-imports.sh
deleted file mode 100755
index 1fe193b9..00000000
--- a/scripts/format-imports.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/sh -e
-set -x
-
-# Sort imports one per line, so autoflake can remove unused imports
-isort fastapi tests docs_src scripts --force-single-line-imports
-sh ./scripts/format.sh
diff --git a/scripts/test.sh b/scripts/test.sh
index b593133d..d445ca17 100755
--- a/scripts/test.sh
+++ b/scripts/test.sh
@@ -3,8 +3,7 @@
set -e
set -x
-bash ./scripts/lint.sh
# Check README.md is up to date
python ./scripts/docs.py verify-readme
export PYTHONPATH=./docs_src
-pytest --cov=fastapi --cov=tests --cov=docs/src --cov-report=term-missing --cov-report=xml tests ${@}
+pytest --cov=fastapi --cov=tests --cov=docs_src --cov-report=term-missing:skip-covered --cov-report=xml tests ${@}
diff --git a/tests/test_additional_properties.py b/tests/test_additional_properties.py
index 9e15e6ed..016c1f73 100644
--- a/tests/test_additional_properties.py
+++ b/tests/test_additional_properties.py
@@ -76,7 +76,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_additional_responses_custom_model_in_callback.py b/tests/test_additional_responses_custom_model_in_callback.py
index 36dd0d6d..a1072cc5 100644
--- a/tests/test_additional_responses_custom_model_in_callback.py
+++ b/tests/test_additional_responses_custom_model_in_callback.py
@@ -119,7 +119,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_additional_responses_default_validationerror.py b/tests/test_additional_responses_default_validationerror.py
index 6ea372ce..cabb536d 100644
--- a/tests/test_additional_responses_default_validationerror.py
+++ b/tests/test_additional_responses_default_validationerror.py
@@ -54,7 +54,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_application.py b/tests/test_application.py
index 5ba73730..d9194c15 100644
--- a/tests/test_application.py
+++ b/tests/test_application.py
@@ -1101,7 +1101,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_custom_schema_fields.py b/tests/test_custom_schema_fields.py
new file mode 100644
index 00000000..10b02608
--- /dev/null
+++ b/tests/test_custom_schema_fields.py
@@ -0,0 +1,51 @@
+from fastapi import FastAPI
+from fastapi.testclient import TestClient
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+
+ class Config:
+ schema_extra = {
+ "x-something-internal": {"level": 4},
+ }
+
+
+@app.get("/foo", response_model=Item)
+def foo():
+ return {"name": "Foo item"}
+
+
+client = TestClient(app)
+
+
+item_schema = {
+ "title": "Item",
+ "required": ["name"],
+ "type": "object",
+ "x-something-internal": {
+ "level": 4,
+ },
+ "properties": {
+ "name": {
+ "title": "Name",
+ "type": "string",
+ }
+ },
+}
+
+
+def test_custom_response_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json()["components"]["schemas"]["Item"] == item_schema
+
+
+def test_response():
+ # For coverage
+ response = client.get("/foo")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"name": "Foo item"}
diff --git a/tests/test_dependency_contextmanager.py b/tests/test_dependency_contextmanager.py
index 3e42b47f..03ef56c4 100644
--- a/tests/test_dependency_contextmanager.py
+++ b/tests/test_dependency_contextmanager.py
@@ -235,7 +235,16 @@ def test_sync_raise_other():
assert "/sync_raise" not in errors
-def test_async_raise():
+def test_async_raise_raises():
+ with pytest.raises(AsyncDependencyError):
+ client.get("/async_raise")
+ assert state["/async_raise"] == "asyncgen raise finalized"
+ assert "/async_raise" in errors
+ errors.clear()
+
+
+def test_async_raise_server_error():
+ client = TestClient(app, raise_server_exceptions=False)
response = client.get("/async_raise")
assert response.status_code == 500, response.text
assert state["/async_raise"] == "asyncgen raise finalized"
@@ -270,7 +279,16 @@ def test_background_tasks():
assert state["bg"] == "bg set - b: started b - a: started a"
-def test_sync_raise():
+def test_sync_raise_raises():
+ with pytest.raises(SyncDependencyError):
+ client.get("/sync_raise")
+ assert state["/sync_raise"] == "generator raise finalized"
+ assert "/sync_raise" in errors
+ errors.clear()
+
+
+def test_sync_raise_server_error():
+ client = TestClient(app, raise_server_exceptions=False)
response = client.get("/sync_raise")
assert response.status_code == 500, response.text
assert state["/sync_raise"] == "generator raise finalized"
@@ -306,7 +324,16 @@ def test_sync_sync_raise_other():
assert "/sync_raise" not in errors
-def test_sync_async_raise():
+def test_sync_async_raise_raises():
+ with pytest.raises(AsyncDependencyError):
+ client.get("/sync_async_raise")
+ assert state["/async_raise"] == "asyncgen raise finalized"
+ assert "/async_raise" in errors
+ errors.clear()
+
+
+def test_sync_async_raise_server_error():
+ client = TestClient(app, raise_server_exceptions=False)
response = client.get("/sync_async_raise")
assert response.status_code == 500, response.text
assert state["/async_raise"] == "asyncgen raise finalized"
@@ -314,7 +341,16 @@ def test_sync_async_raise():
errors.clear()
-def test_sync_sync_raise():
+def test_sync_sync_raise_raises():
+ with pytest.raises(SyncDependencyError):
+ client.get("/sync_sync_raise")
+ assert state["/sync_raise"] == "generator raise finalized"
+ assert "/sync_raise" in errors
+ errors.clear()
+
+
+def test_sync_sync_raise_server_error():
+ client = TestClient(app, raise_server_exceptions=False)
response = client.get("/sync_sync_raise")
assert response.status_code == 500, response.text
assert state["/sync_raise"] == "generator raise finalized"
diff --git a/tests/test_dependency_contextvars.py b/tests/test_dependency_contextvars.py
new file mode 100644
index 00000000..076802df
--- /dev/null
+++ b/tests/test_dependency_contextvars.py
@@ -0,0 +1,51 @@
+from contextvars import ContextVar
+from typing import Any, Awaitable, Callable, Dict, Optional
+
+from fastapi import Depends, FastAPI, Request, Response
+from fastapi.testclient import TestClient
+
+legacy_request_state_context_var: ContextVar[Optional[Dict[str, Any]]] = ContextVar(
+ "legacy_request_state_context_var", default=None
+)
+
+app = FastAPI()
+
+
+async def set_up_request_state_dependency():
+ request_state = {"user": "deadpond"}
+ contextvar_token = legacy_request_state_context_var.set(request_state)
+ yield request_state
+ legacy_request_state_context_var.reset(contextvar_token)
+
+
+@app.middleware("http")
+async def custom_middleware(
+ request: Request, call_next: Callable[[Request], Awaitable[Response]]
+):
+ response = await call_next(request)
+ response.headers["custom"] = "foo"
+ return response
+
+
+@app.get("/user", dependencies=[Depends(set_up_request_state_dependency)])
+def get_user():
+ request_state = legacy_request_state_context_var.get()
+ assert request_state
+ return request_state["user"]
+
+
+client = TestClient(app)
+
+
+def test_dependency_contextvars():
+ """
+ Check that custom middlewares don't affect the contextvar context for dependencies.
+
+ The code before yield and the code after yield should be run in the same contextvar
+ context, so that request_state_context_var.reset(contextvar_token).
+
+ If they are run in a different context, that raises an error.
+ """
+ response = client.get("/user")
+ assert response.json() == "deadpond"
+ assert response.headers["custom"] == "foo"
diff --git a/tests/test_dependency_duplicates.py b/tests/test_dependency_duplicates.py
index 5e15812b..33899134 100644
--- a/tests/test_dependency_duplicates.py
+++ b/tests/test_dependency_duplicates.py
@@ -177,7 +177,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_dependency_normal_exceptions.py b/tests/test_dependency_normal_exceptions.py
new file mode 100644
index 00000000..49a19f46
--- /dev/null
+++ b/tests/test_dependency_normal_exceptions.py
@@ -0,0 +1,71 @@
+import pytest
+from fastapi import Body, Depends, FastAPI, HTTPException
+from fastapi.testclient import TestClient
+
+initial_fake_database = {"rick": "Rick Sanchez"}
+
+fake_database = initial_fake_database.copy()
+
+initial_state = {"except": False, "finally": False}
+
+state = initial_state.copy()
+
+app = FastAPI()
+
+
+async def get_database():
+ temp_database = fake_database.copy()
+ try:
+ yield temp_database
+ fake_database.update(temp_database)
+ except HTTPException:
+ state["except"] = True
+ finally:
+ state["finally"] = True
+
+
+@app.put("/invalid-user/{user_id}")
+def put_invalid_user(
+ user_id: str, name: str = Body(...), db: dict = Depends(get_database)
+):
+ db[user_id] = name
+ raise HTTPException(status_code=400, detail="Invalid user")
+
+
+@app.put("/user/{user_id}")
+def put_user(user_id: str, name: str = Body(...), db: dict = Depends(get_database)):
+ db[user_id] = name
+ return {"message": "OK"}
+
+
+@pytest.fixture(autouse=True)
+def reset_state_and_db():
+ global fake_database
+ global state
+ fake_database = initial_fake_database.copy()
+ state = initial_state.copy()
+
+
+client = TestClient(app)
+
+
+def test_dependency_gets_exception():
+ assert state["except"] is False
+ assert state["finally"] is False
+ response = client.put("/invalid-user/rick", json="Morty")
+ assert response.status_code == 400, response.text
+ assert response.json() == {"detail": "Invalid user"}
+ assert state["except"] is True
+ assert state["finally"] is True
+ assert fake_database["rick"] == "Rick Sanchez"
+
+
+def test_dependency_no_exception():
+ assert state["except"] is False
+ assert state["finally"] is False
+ response = client.put("/user/rick", json="Morty")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"message": "OK"}
+ assert state["except"] is False
+ assert state["finally"] is True
+ assert fake_database["rick"] == "Morty"
diff --git a/tests/test_exception_handlers.py b/tests/test_exception_handlers.py
index 6153f7ab..67a4bece 100644
--- a/tests/test_exception_handlers.py
+++ b/tests/test_exception_handlers.py
@@ -1,3 +1,4 @@
+import pytest
from fastapi import FastAPI, HTTPException
from fastapi.exceptions import RequestValidationError
from fastapi.testclient import TestClient
@@ -12,10 +13,15 @@ def request_validation_exception_handler(request, exception):
return JSONResponse({"exception": "request-validation"})
+def server_error_exception_handler(request, exception):
+ return JSONResponse(status_code=500, content={"exception": "server-error"})
+
+
app = FastAPI(
exception_handlers={
HTTPException: http_exception_handler,
RequestValidationError: request_validation_exception_handler,
+ Exception: server_error_exception_handler,
}
)
@@ -32,6 +38,11 @@ def route_with_request_validation_exception(param: int):
pass # pragma: no cover
+@app.get("/server-error")
+def route_with_server_error():
+ raise RuntimeError("Oops!")
+
+
def test_override_http_exception():
response = client.get("/http-exception")
assert response.status_code == 200
@@ -42,3 +53,15 @@ def test_override_request_validation_exception():
response = client.get("/request-validation/invalid")
assert response.status_code == 200
assert response.json() == {"exception": "request-validation"}
+
+
+def test_override_server_error_exception_raises():
+ with pytest.raises(RuntimeError):
+ client.get("/server-error")
+
+
+def test_override_server_error_exception_response():
+ client = TestClient(app, raise_server_exceptions=False)
+ response = client.get("/server-error")
+ assert response.status_code == 500
+ assert response.json() == {"exception": "server-error"}
diff --git a/tests/test_extra_routes.py b/tests/test_extra_routes.py
index 6aba3e8d..491ba61c 100644
--- a/tests/test_extra_routes.py
+++ b/tests/test_extra_routes.py
@@ -32,12 +32,12 @@ def delete_item(item_id: str, item: Item):
@app.head("/items/{item_id}")
def head_item(item_id: str):
- return JSONResponse(headers={"x-fastapi-item-id": item_id})
+ return JSONResponse(None, headers={"x-fastapi-item-id": item_id})
@app.options("/items/{item_id}")
def options_item(item_id: str):
- return JSONResponse(headers={"x-fastapi-item-id": item_id})
+ return JSONResponse(None, headers={"x-fastapi-item-id": item_id})
@app.patch("/items/{item_id}")
@@ -47,7 +47,7 @@ def patch_item(item_id: str, item: Item):
@app.trace("/items/{item_id}")
def trace_item(item_id: str):
- return JSONResponse(media_type="message/http")
+ return JSONResponse(None, media_type="message/http")
client = TestClient(app)
@@ -292,7 +292,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_fakeasync.py b/tests/test_fakeasync.py
deleted file mode 100644
index 4e146b0f..00000000
--- a/tests/test_fakeasync.py
+++ /dev/null
@@ -1,12 +0,0 @@
-import pytest
-from fastapi.concurrency import _fake_asynccontextmanager
-
-
-@_fake_asynccontextmanager
-def never_run():
- pass # pragma: no cover
-
-
-def test_fake_async():
- with pytest.raises(RuntimeError):
- never_run()
diff --git a/tests/test_filter_pydantic_sub_model.py b/tests/test_filter_pydantic_sub_model.py
index 90a37297..8814356a 100644
--- a/tests/test_filter_pydantic_sub_model.py
+++ b/tests/test_filter_pydantic_sub_model.py
@@ -116,7 +116,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_generate_unique_id_function.py b/tests/test_generate_unique_id_function.py
new file mode 100644
index 00000000..0b519f85
--- /dev/null
+++ b/tests/test_generate_unique_id_function.py
@@ -0,0 +1,1631 @@
+import warnings
+from typing import List
+
+from fastapi import APIRouter, FastAPI
+from fastapi.routing import APIRoute
+from fastapi.testclient import TestClient
+from pydantic import BaseModel
+
+
+def custom_generate_unique_id(route: APIRoute):
+ return f"foo_{route.name}"
+
+
+def custom_generate_unique_id2(route: APIRoute):
+ return f"bar_{route.name}"
+
+
+def custom_generate_unique_id3(route: APIRoute):
+ return f"baz_{route.name}"
+
+
+class Item(BaseModel):
+ name: str
+ price: float
+
+
+class Message(BaseModel):
+ title: str
+ description: str
+
+
+def test_top_level_generate_unique_id():
+ app = FastAPI(generate_unique_id_function=custom_generate_unique_id)
+ router = APIRouter()
+
+ @app.post("/", response_model=List[Item], responses={404: {"model": List[Message]}})
+ def post_root(item1: Item, item2: Item):
+ return item1, item2 # pragma: nocover
+
+ @router.post(
+ "/router", response_model=List[Item], responses={404: {"model": List[Message]}}
+ )
+ def post_router(item1: Item, item2: Item):
+ return item1, item2 # pragma: nocover
+
+ app.include_router(router)
+ client = TestClient(app)
+ response = client.get("/openapi.json")
+ data = response.json()
+ assert data == {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/": {
+ "post": {
+ "summary": "Post Root",
+ "operationId": "foo_post_root",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_foo_post_root"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Foo Post Root",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response 404 Foo Post Root",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Message"
+ },
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ "/router": {
+ "post": {
+ "summary": "Post Router",
+ "operationId": "foo_post_router",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_foo_post_router"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Foo Post Router",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response 404 Foo Post Router",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Message"
+ },
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ },
+ "components": {
+ "schemas": {
+ "Body_foo_post_root": {
+ "title": "Body_foo_post_root",
+ "required": ["item1", "item2"],
+ "type": "object",
+ "properties": {
+ "item1": {"$ref": "#/components/schemas/Item"},
+ "item2": {"$ref": "#/components/schemas/Item"},
+ },
+ },
+ "Body_foo_post_router": {
+ "title": "Body_foo_post_router",
+ "required": ["item1", "item2"],
+ "type": "object",
+ "properties": {
+ "item1": {"$ref": "#/components/schemas/Item"},
+ "item2": {"$ref": "#/components/schemas/Item"},
+ },
+ },
+ "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"},
+ "price": {"title": "Price", "type": "number"},
+ },
+ },
+ "Message": {
+ "title": "Message",
+ "required": ["title", "description"],
+ "type": "object",
+ "properties": {
+ "title": {"title": "Title", "type": "string"},
+ "description": {"title": "Description", "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"},
+ },
+ },
+ }
+ },
+ }
+
+
+def test_router_overrides_generate_unique_id():
+ app = FastAPI(generate_unique_id_function=custom_generate_unique_id)
+ router = APIRouter(generate_unique_id_function=custom_generate_unique_id2)
+
+ @app.post("/", response_model=List[Item], responses={404: {"model": List[Message]}})
+ def post_root(item1: Item, item2: Item):
+ return item1, item2 # pragma: nocover
+
+ @router.post(
+ "/router", response_model=List[Item], responses={404: {"model": List[Message]}}
+ )
+ def post_router(item1: Item, item2: Item):
+ return item1, item2 # pragma: nocover
+
+ app.include_router(router)
+ client = TestClient(app)
+ response = client.get("/openapi.json")
+ data = response.json()
+ assert data == {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/": {
+ "post": {
+ "summary": "Post Root",
+ "operationId": "foo_post_root",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_foo_post_root"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Foo Post Root",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response 404 Foo Post Root",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Message"
+ },
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ "/router": {
+ "post": {
+ "summary": "Post Router",
+ "operationId": "bar_post_router",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_bar_post_router"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Bar Post Router",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response 404 Bar Post Router",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Message"
+ },
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ },
+ "components": {
+ "schemas": {
+ "Body_bar_post_router": {
+ "title": "Body_bar_post_router",
+ "required": ["item1", "item2"],
+ "type": "object",
+ "properties": {
+ "item1": {"$ref": "#/components/schemas/Item"},
+ "item2": {"$ref": "#/components/schemas/Item"},
+ },
+ },
+ "Body_foo_post_root": {
+ "title": "Body_foo_post_root",
+ "required": ["item1", "item2"],
+ "type": "object",
+ "properties": {
+ "item1": {"$ref": "#/components/schemas/Item"},
+ "item2": {"$ref": "#/components/schemas/Item"},
+ },
+ },
+ "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"},
+ "price": {"title": "Price", "type": "number"},
+ },
+ },
+ "Message": {
+ "title": "Message",
+ "required": ["title", "description"],
+ "type": "object",
+ "properties": {
+ "title": {"title": "Title", "type": "string"},
+ "description": {"title": "Description", "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"},
+ },
+ },
+ }
+ },
+ }
+
+
+def test_router_include_overrides_generate_unique_id():
+ app = FastAPI(generate_unique_id_function=custom_generate_unique_id)
+ router = APIRouter(generate_unique_id_function=custom_generate_unique_id2)
+
+ @app.post("/", response_model=List[Item], responses={404: {"model": List[Message]}})
+ def post_root(item1: Item, item2: Item):
+ return item1, item2 # pragma: nocover
+
+ @router.post(
+ "/router", response_model=List[Item], responses={404: {"model": List[Message]}}
+ )
+ def post_router(item1: Item, item2: Item):
+ return item1, item2 # pragma: nocover
+
+ app.include_router(router, generate_unique_id_function=custom_generate_unique_id3)
+ client = TestClient(app)
+ response = client.get("/openapi.json")
+ data = response.json()
+ assert data == {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/": {
+ "post": {
+ "summary": "Post Root",
+ "operationId": "foo_post_root",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_foo_post_root"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Foo Post Root",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response 404 Foo Post Root",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Message"
+ },
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ "/router": {
+ "post": {
+ "summary": "Post Router",
+ "operationId": "bar_post_router",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_bar_post_router"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Bar Post Router",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response 404 Bar Post Router",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Message"
+ },
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ },
+ "components": {
+ "schemas": {
+ "Body_bar_post_router": {
+ "title": "Body_bar_post_router",
+ "required": ["item1", "item2"],
+ "type": "object",
+ "properties": {
+ "item1": {"$ref": "#/components/schemas/Item"},
+ "item2": {"$ref": "#/components/schemas/Item"},
+ },
+ },
+ "Body_foo_post_root": {
+ "title": "Body_foo_post_root",
+ "required": ["item1", "item2"],
+ "type": "object",
+ "properties": {
+ "item1": {"$ref": "#/components/schemas/Item"},
+ "item2": {"$ref": "#/components/schemas/Item"},
+ },
+ },
+ "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"},
+ "price": {"title": "Price", "type": "number"},
+ },
+ },
+ "Message": {
+ "title": "Message",
+ "required": ["title", "description"],
+ "type": "object",
+ "properties": {
+ "title": {"title": "Title", "type": "string"},
+ "description": {"title": "Description", "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"},
+ },
+ },
+ }
+ },
+ }
+
+
+def test_subrouter_top_level_include_overrides_generate_unique_id():
+ app = FastAPI(generate_unique_id_function=custom_generate_unique_id)
+ router = APIRouter()
+ sub_router = APIRouter(generate_unique_id_function=custom_generate_unique_id2)
+
+ @app.post("/", response_model=List[Item], responses={404: {"model": List[Message]}})
+ def post_root(item1: Item, item2: Item):
+ return item1, item2 # pragma: nocover
+
+ @router.post(
+ "/router", response_model=List[Item], responses={404: {"model": List[Message]}}
+ )
+ def post_router(item1: Item, item2: Item):
+ return item1, item2 # pragma: nocover
+
+ @sub_router.post(
+ "/subrouter",
+ response_model=List[Item],
+ responses={404: {"model": List[Message]}},
+ )
+ def post_subrouter(item1: Item, item2: Item):
+ return item1, item2 # pragma: nocover
+
+ router.include_router(sub_router)
+ app.include_router(router, generate_unique_id_function=custom_generate_unique_id3)
+ client = TestClient(app)
+ response = client.get("/openapi.json")
+ data = response.json()
+ assert data == {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/": {
+ "post": {
+ "summary": "Post Root",
+ "operationId": "foo_post_root",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_foo_post_root"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Foo Post Root",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response 404 Foo Post Root",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Message"
+ },
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ "/router": {
+ "post": {
+ "summary": "Post Router",
+ "operationId": "baz_post_router",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_baz_post_router"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Baz Post Router",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response 404 Baz Post Router",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Message"
+ },
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ "/subrouter": {
+ "post": {
+ "summary": "Post Subrouter",
+ "operationId": "bar_post_subrouter",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_bar_post_subrouter"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Bar Post Subrouter",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response 404 Bar Post Subrouter",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Message"
+ },
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ },
+ "components": {
+ "schemas": {
+ "Body_bar_post_subrouter": {
+ "title": "Body_bar_post_subrouter",
+ "required": ["item1", "item2"],
+ "type": "object",
+ "properties": {
+ "item1": {"$ref": "#/components/schemas/Item"},
+ "item2": {"$ref": "#/components/schemas/Item"},
+ },
+ },
+ "Body_baz_post_router": {
+ "title": "Body_baz_post_router",
+ "required": ["item1", "item2"],
+ "type": "object",
+ "properties": {
+ "item1": {"$ref": "#/components/schemas/Item"},
+ "item2": {"$ref": "#/components/schemas/Item"},
+ },
+ },
+ "Body_foo_post_root": {
+ "title": "Body_foo_post_root",
+ "required": ["item1", "item2"],
+ "type": "object",
+ "properties": {
+ "item1": {"$ref": "#/components/schemas/Item"},
+ "item2": {"$ref": "#/components/schemas/Item"},
+ },
+ },
+ "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"},
+ "price": {"title": "Price", "type": "number"},
+ },
+ },
+ "Message": {
+ "title": "Message",
+ "required": ["title", "description"],
+ "type": "object",
+ "properties": {
+ "title": {"title": "Title", "type": "string"},
+ "description": {"title": "Description", "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"},
+ },
+ },
+ }
+ },
+ }
+
+
+def test_router_path_operation_overrides_generate_unique_id():
+ app = FastAPI(generate_unique_id_function=custom_generate_unique_id)
+ router = APIRouter(generate_unique_id_function=custom_generate_unique_id2)
+
+ @app.post("/", response_model=List[Item], responses={404: {"model": List[Message]}})
+ def post_root(item1: Item, item2: Item):
+ return item1, item2 # pragma: nocover
+
+ @router.post(
+ "/router",
+ response_model=List[Item],
+ responses={404: {"model": List[Message]}},
+ generate_unique_id_function=custom_generate_unique_id3,
+ )
+ def post_router(item1: Item, item2: Item):
+ return item1, item2 # pragma: nocover
+
+ app.include_router(router)
+ client = TestClient(app)
+ response = client.get("/openapi.json")
+ data = response.json()
+ assert data == {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/": {
+ "post": {
+ "summary": "Post Root",
+ "operationId": "foo_post_root",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_foo_post_root"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Foo Post Root",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response 404 Foo Post Root",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Message"
+ },
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ "/router": {
+ "post": {
+ "summary": "Post Router",
+ "operationId": "baz_post_router",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_baz_post_router"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Baz Post Router",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response 404 Baz Post Router",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Message"
+ },
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ },
+ "components": {
+ "schemas": {
+ "Body_baz_post_router": {
+ "title": "Body_baz_post_router",
+ "required": ["item1", "item2"],
+ "type": "object",
+ "properties": {
+ "item1": {"$ref": "#/components/schemas/Item"},
+ "item2": {"$ref": "#/components/schemas/Item"},
+ },
+ },
+ "Body_foo_post_root": {
+ "title": "Body_foo_post_root",
+ "required": ["item1", "item2"],
+ "type": "object",
+ "properties": {
+ "item1": {"$ref": "#/components/schemas/Item"},
+ "item2": {"$ref": "#/components/schemas/Item"},
+ },
+ },
+ "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"},
+ "price": {"title": "Price", "type": "number"},
+ },
+ },
+ "Message": {
+ "title": "Message",
+ "required": ["title", "description"],
+ "type": "object",
+ "properties": {
+ "title": {"title": "Title", "type": "string"},
+ "description": {"title": "Description", "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"},
+ },
+ },
+ }
+ },
+ }
+
+
+def test_app_path_operation_overrides_generate_unique_id():
+ app = FastAPI(generate_unique_id_function=custom_generate_unique_id)
+ router = APIRouter(generate_unique_id_function=custom_generate_unique_id2)
+
+ @app.post(
+ "/",
+ response_model=List[Item],
+ responses={404: {"model": List[Message]}},
+ generate_unique_id_function=custom_generate_unique_id3,
+ )
+ def post_root(item1: Item, item2: Item):
+ return item1, item2 # pragma: nocover
+
+ @router.post(
+ "/router",
+ response_model=List[Item],
+ responses={404: {"model": List[Message]}},
+ )
+ def post_router(item1: Item, item2: Item):
+ return item1, item2 # pragma: nocover
+
+ app.include_router(router)
+ client = TestClient(app)
+ response = client.get("/openapi.json")
+ data = response.json()
+ assert data == {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/": {
+ "post": {
+ "summary": "Post Root",
+ "operationId": "baz_post_root",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_baz_post_root"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Baz Post Root",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response 404 Baz Post Root",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Message"
+ },
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ "/router": {
+ "post": {
+ "summary": "Post Router",
+ "operationId": "bar_post_router",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_bar_post_router"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Bar Post Router",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response 404 Bar Post Router",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Message"
+ },
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ },
+ "components": {
+ "schemas": {
+ "Body_bar_post_router": {
+ "title": "Body_bar_post_router",
+ "required": ["item1", "item2"],
+ "type": "object",
+ "properties": {
+ "item1": {"$ref": "#/components/schemas/Item"},
+ "item2": {"$ref": "#/components/schemas/Item"},
+ },
+ },
+ "Body_baz_post_root": {
+ "title": "Body_baz_post_root",
+ "required": ["item1", "item2"],
+ "type": "object",
+ "properties": {
+ "item1": {"$ref": "#/components/schemas/Item"},
+ "item2": {"$ref": "#/components/schemas/Item"},
+ },
+ },
+ "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"},
+ "price": {"title": "Price", "type": "number"},
+ },
+ },
+ "Message": {
+ "title": "Message",
+ "required": ["title", "description"],
+ "type": "object",
+ "properties": {
+ "title": {"title": "Title", "type": "string"},
+ "description": {"title": "Description", "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"},
+ },
+ },
+ }
+ },
+ }
+
+
+def test_callback_override_generate_unique_id():
+ app = FastAPI(generate_unique_id_function=custom_generate_unique_id)
+ callback_router = APIRouter(generate_unique_id_function=custom_generate_unique_id2)
+
+ @callback_router.post(
+ "/post-callback",
+ response_model=List[Item],
+ responses={404: {"model": List[Message]}},
+ generate_unique_id_function=custom_generate_unique_id3,
+ )
+ def post_callback(item1: Item, item2: Item):
+ return item1, item2 # pragma: nocover
+
+ @app.post(
+ "/",
+ response_model=List[Item],
+ responses={404: {"model": List[Message]}},
+ generate_unique_id_function=custom_generate_unique_id3,
+ callbacks=callback_router.routes,
+ )
+ def post_root(item1: Item, item2: Item):
+ return item1, item2 # pragma: nocover
+
+ @app.post(
+ "/tocallback",
+ response_model=List[Item],
+ responses={404: {"model": List[Message]}},
+ )
+ def post_with_callback(item1: Item, item2: Item):
+ return item1, item2 # pragma: nocover
+
+ client = TestClient(app)
+ response = client.get("/openapi.json")
+ data = response.json()
+ assert data == {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/": {
+ "post": {
+ "summary": "Post Root",
+ "operationId": "baz_post_root",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_baz_post_root"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Baz Post Root",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response 404 Baz Post Root",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Message"
+ },
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "callbacks": {
+ "post_callback": {
+ "/post-callback": {
+ "post": {
+ "summary": "Post Callback",
+ "operationId": "baz_post_callback",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_baz_post_callback"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Baz Post Callback",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Item"
+ },
+ }
+ }
+ },
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response 404 Baz Post Callback",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Message"
+ },
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ }
+ }
+ },
+ }
+ },
+ "/tocallback": {
+ "post": {
+ "summary": "Post With Callback",
+ "operationId": "foo_post_with_callback",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_foo_post_with_callback"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Foo Post With Callback",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ },
+ "404": {
+ "description": "Not Found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response 404 Foo Post With Callback",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Message"
+ },
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ },
+ "components": {
+ "schemas": {
+ "Body_baz_post_callback": {
+ "title": "Body_baz_post_callback",
+ "required": ["item1", "item2"],
+ "type": "object",
+ "properties": {
+ "item1": {"$ref": "#/components/schemas/Item"},
+ "item2": {"$ref": "#/components/schemas/Item"},
+ },
+ },
+ "Body_baz_post_root": {
+ "title": "Body_baz_post_root",
+ "required": ["item1", "item2"],
+ "type": "object",
+ "properties": {
+ "item1": {"$ref": "#/components/schemas/Item"},
+ "item2": {"$ref": "#/components/schemas/Item"},
+ },
+ },
+ "Body_foo_post_with_callback": {
+ "title": "Body_foo_post_with_callback",
+ "required": ["item1", "item2"],
+ "type": "object",
+ "properties": {
+ "item1": {"$ref": "#/components/schemas/Item"},
+ "item2": {"$ref": "#/components/schemas/Item"},
+ },
+ },
+ "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"},
+ "price": {"title": "Price", "type": "number"},
+ },
+ },
+ "Message": {
+ "title": "Message",
+ "required": ["title", "description"],
+ "type": "object",
+ "properties": {
+ "title": {"title": "Title", "type": "string"},
+ "description": {"title": "Description", "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"},
+ },
+ },
+ }
+ },
+ }
+
+
+def test_warn_duplicate_operation_id():
+ def broken_operation_id(route: APIRoute):
+ return "foo"
+
+ app = FastAPI(generate_unique_id_function=broken_operation_id)
+
+ @app.post("/")
+ def post_root(item1: Item):
+ return item1 # pragma: nocover
+
+ @app.post("/second")
+ def post_second(item1: Item):
+ return item1 # pragma: nocover
+
+ @app.post("/third")
+ def post_third(item1: Item):
+ return item1 # pragma: nocover
+
+ client = TestClient(app)
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter("always")
+ client.get("/openapi.json")
+ assert len(w) == 2
+ assert issubclass(w[-1].category, UserWarning)
+ assert "Duplicate Operation ID" in str(w[-1].message)
diff --git a/tests/test_get_request_body.py b/tests/test_get_request_body.py
index b12f499e..88b9d839 100644
--- a/tests/test_get_request_body.py
+++ b/tests/test_get_request_body.py
@@ -85,7 +85,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_include_router_defaults_overrides.py b/tests/test_include_router_defaults_overrides.py
index c46cb670..ccb6c722 100644
--- a/tests/test_include_router_defaults_overrides.py
+++ b/tests/test_include_router_defaults_overrides.py
@@ -1,3 +1,5 @@
+import warnings
+
import pytest
from fastapi import APIRouter, Depends, FastAPI, Response
from fastapi.responses import JSONResponse
@@ -343,7 +345,11 @@ client = TestClient(app)
def test_openapi():
client = TestClient(app)
- response = client.get("/openapi.json")
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter("always")
+ response = client.get("/openapi.json")
+ assert issubclass(w[-1].category, UserWarning)
+ assert "Duplicate Operation ID" in str(w[-1].message)
assert response.json() == openapi_schema
@@ -6606,7 +6612,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_jsonable_encoder.py b/tests/test_jsonable_encoder.py
index e2aa8adf..fa82b5ea 100644
--- a/tests/test_jsonable_encoder.py
+++ b/tests/test_jsonable_encoder.py
@@ -161,6 +161,21 @@ def test_custom_encoders():
assert encoded_instance["dt_field"] == instance.dt_field.isoformat()
+def test_custom_enum_encoders():
+ def custom_enum_encoder(v: Enum):
+ return v.value.lower()
+
+ class MyEnum(Enum):
+ ENUM_VAL_1 = "ENUM_VAL_1"
+
+ instance = MyEnum.ENUM_VAL_1
+
+ encoded_instance = jsonable_encoder(
+ instance, custom_encoder={MyEnum: custom_enum_encoder}
+ )
+ assert encoded_instance == custom_enum_encoder(instance)
+
+
def test_encode_model_with_path(model_with_path):
if isinstance(model_with_path.path, PureWindowsPath):
expected = "\\foo\\bar"
diff --git a/tests/test_modules_same_name_body/test_main.py b/tests/test_modules_same_name_body/test_main.py
index b0d3330c..8b1aea03 100644
--- a/tests/test_modules_same_name_body/test_main.py
+++ b/tests/test_modules_same_name_body/test_main.py
@@ -101,7 +101,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_multi_body_errors.py b/tests/test_multi_body_errors.py
index c1be8280..31308ea8 100644
--- a/tests/test_multi_body_errors.py
+++ b/tests/test_multi_body_errors.py
@@ -79,7 +79,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_multi_query_errors.py b/tests/test_multi_query_errors.py
index 69ea87a9..0a15833f 100644
--- a/tests/test_multi_query_errors.py
+++ b/tests/test_multi_query_errors.py
@@ -63,7 +63,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_openapi_route_extensions.py b/tests/test_openapi_route_extensions.py
new file mode 100644
index 00000000..8a1080d6
--- /dev/null
+++ b/tests/test_openapi_route_extensions.py
@@ -0,0 +1,45 @@
+from fastapi import FastAPI
+from fastapi.testclient import TestClient
+
+app = FastAPI()
+
+
+@app.get("/", openapi_extra={"x-custom-extension": "value"})
+def route_with_extras():
+ return {}
+
+
+client = TestClient(app)
+
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ },
+ "summary": "Route With Extras",
+ "operationId": "route_with_extras__get",
+ "x-custom-extension": "value",
+ }
+ },
+ },
+}
+
+
+def test_openapi():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+def test_get_route():
+ response = client.get("/")
+ assert response.status_code == 200, response.text
+ assert response.json() == {}
diff --git a/tests/test_param_in_path_and_dependency.py b/tests/test_param_in_path_and_dependency.py
index 0a94c215..4d85afbc 100644
--- a/tests/test_param_in_path_and_dependency.py
+++ b/tests/test_param_in_path_and_dependency.py
@@ -71,7 +71,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_param_include_in_schema.py b/tests/test_param_include_in_schema.py
new file mode 100644
index 00000000..26aa6389
--- /dev/null
+++ b/tests/test_param_include_in_schema.py
@@ -0,0 +1,239 @@
+from typing import Optional
+
+import pytest
+from fastapi import Cookie, FastAPI, Header, Path, Query
+from fastapi.testclient import TestClient
+
+app = FastAPI()
+
+
+@app.get("/hidden_cookie")
+async def hidden_cookie(
+ hidden_cookie: Optional[str] = Cookie(None, include_in_schema=False)
+):
+ return {"hidden_cookie": hidden_cookie}
+
+
+@app.get("/hidden_header")
+async def hidden_header(
+ hidden_header: Optional[str] = Header(None, include_in_schema=False)
+):
+ return {"hidden_header": hidden_header}
+
+
+@app.get("/hidden_path/{hidden_path}")
+async def hidden_path(hidden_path: str = Path(..., include_in_schema=False)):
+ return {"hidden_path": hidden_path}
+
+
+@app.get("/hidden_query")
+async def hidden_query(
+ hidden_query: Optional[str] = Query(None, include_in_schema=False)
+):
+ return {"hidden_query": hidden_query}
+
+
+client = TestClient(app)
+
+openapi_shema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/hidden_cookie": {
+ "get": {
+ "summary": "Hidden Cookie",
+ "operationId": "hidden_cookie_hidden_cookie_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ "/hidden_header": {
+ "get": {
+ "summary": "Hidden Header",
+ "operationId": "hidden_header_hidden_header_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ "/hidden_path/{hidden_path}": {
+ "get": {
+ "summary": "Hidden Path",
+ "operationId": "hidden_path_hidden_path__hidden_path__get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ "/hidden_query": {
+ "get": {
+ "summary": "Hidden Query",
+ "operationId": "hidden_query_hidden_query_get",
+ "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"},
+ }
+ },
+ },
+ "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"},
+ },
+ },
+ }
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200
+ assert response.json() == openapi_shema
+
+
+@pytest.mark.parametrize(
+ "path,cookies,expected_status,expected_response",
+ [
+ (
+ "/hidden_cookie",
+ {},
+ 200,
+ {"hidden_cookie": None},
+ ),
+ (
+ "/hidden_cookie",
+ {"hidden_cookie": "somevalue"},
+ 200,
+ {"hidden_cookie": "somevalue"},
+ ),
+ ],
+)
+def test_hidden_cookie(path, cookies, expected_status, expected_response):
+ response = client.get(path, cookies=cookies)
+ assert response.status_code == expected_status
+ assert response.json() == expected_response
+
+
+@pytest.mark.parametrize(
+ "path,headers,expected_status,expected_response",
+ [
+ (
+ "/hidden_header",
+ {},
+ 200,
+ {"hidden_header": None},
+ ),
+ (
+ "/hidden_header",
+ {"Hidden-Header": "somevalue"},
+ 200,
+ {"hidden_header": "somevalue"},
+ ),
+ ],
+)
+def test_hidden_header(path, headers, expected_status, expected_response):
+ response = client.get(path, headers=headers)
+ assert response.status_code == expected_status
+ assert response.json() == expected_response
+
+
+def test_hidden_path():
+ response = client.get("/hidden_path/hidden_path")
+ assert response.status_code == 200
+ assert response.json() == {"hidden_path": "hidden_path"}
+
+
+@pytest.mark.parametrize(
+ "path,expected_status,expected_response",
+ [
+ (
+ "/hidden_query",
+ 200,
+ {"hidden_query": None},
+ ),
+ (
+ "/hidden_query?hidden_query=somevalue",
+ 200,
+ {"hidden_query": "somevalue"},
+ ),
+ ],
+)
+def test_hidden_query(path, expected_status, expected_response):
+ response = client.get(path)
+ assert response.status_code == expected_status
+ assert response.json() == expected_response
diff --git a/tests/test_put_no_body.py b/tests/test_put_no_body.py
index 1c2cfac8..3da294cc 100644
--- a/tests/test_put_no_body.py
+++ b/tests/test_put_no_body.py
@@ -57,7 +57,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_read_with_orm_mode.py b/tests/test_read_with_orm_mode.py
new file mode 100644
index 00000000..360ad250
--- /dev/null
+++ b/tests/test_read_with_orm_mode.py
@@ -0,0 +1,53 @@
+from typing import Any
+
+from fastapi import FastAPI
+from fastapi.testclient import TestClient
+from pydantic import BaseModel
+
+
+class PersonBase(BaseModel):
+ name: str
+ lastname: str
+
+
+class Person(PersonBase):
+ @property
+ def full_name(self) -> str:
+ return f"{self.name} {self.lastname}"
+
+ class Config:
+ orm_mode = True
+ read_with_orm_mode = True
+
+
+class PersonCreate(PersonBase):
+ pass
+
+
+class PersonRead(PersonBase):
+ full_name: str
+
+ class Config:
+ orm_mode = True
+
+
+app = FastAPI()
+
+
+@app.post("/people/", response_model=PersonRead)
+def create_person(person: PersonCreate) -> Any:
+ db_person = Person.from_orm(person)
+ return db_person
+
+
+client = TestClient(app)
+
+
+def test_read_with_orm_mode() -> None:
+ person_data = {"name": "Dive", "lastname": "Wilson"}
+ response = client.post("/people/", json=person_data)
+ data = response.json()
+ assert response.status_code == 200, response.text
+ assert data["name"] == person_data["name"]
+ assert data["lastname"] == person_data["lastname"]
+ assert data["full_name"] == person_data["name"] + " " + person_data["lastname"]
diff --git a/tests/test_repeated_dependency_schema.py b/tests/test_repeated_dependency_schema.py
index fd616e12..00441694 100644
--- a/tests/test_repeated_dependency_schema.py
+++ b/tests/test_repeated_dependency_schema.py
@@ -36,7 +36,7 @@ schema = {
"ValidationError": {
"properties": {
"loc": {
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
"title": "Location",
"type": "array",
},
diff --git a/tests/test_response_model_include_exclude.py b/tests/test_response_model_include_exclude.py
new file mode 100644
index 00000000..73c3591e
--- /dev/null
+++ b/tests/test_response_model_include_exclude.py
@@ -0,0 +1,175 @@
+from fastapi import FastAPI
+from fastapi.testclient import TestClient
+from pydantic import BaseModel
+
+
+class Model1(BaseModel):
+ foo: str
+ bar: str
+
+
+class Model2(BaseModel):
+ ref: Model1
+ baz: str
+
+
+class Model3(BaseModel):
+ name: str
+ age: int
+ ref2: Model2
+
+
+app = FastAPI()
+
+
+@app.get(
+ "/simple_include",
+ response_model=Model2,
+ response_model_include={"baz": ..., "ref": {"foo"}},
+)
+def simple_include():
+ return Model2(
+ ref=Model1(foo="simple_include model foo", bar="simple_include model bar"),
+ baz="simple_include model2 baz",
+ )
+
+
+@app.get(
+ "/simple_include_dict",
+ response_model=Model2,
+ response_model_include={"baz": ..., "ref": {"foo"}},
+)
+def simple_include_dict():
+ return {
+ "ref": {
+ "foo": "simple_include_dict model foo",
+ "bar": "simple_include_dict model bar",
+ },
+ "baz": "simple_include_dict model2 baz",
+ }
+
+
+@app.get(
+ "/simple_exclude",
+ response_model=Model2,
+ response_model_exclude={"ref": {"bar"}},
+)
+def simple_exclude():
+ return Model2(
+ ref=Model1(foo="simple_exclude model foo", bar="simple_exclude model bar"),
+ baz="simple_exclude model2 baz",
+ )
+
+
+@app.get(
+ "/simple_exclude_dict",
+ response_model=Model2,
+ response_model_exclude={"ref": {"bar"}},
+)
+def simple_exclude_dict():
+ return {
+ "ref": {
+ "foo": "simple_exclude_dict model foo",
+ "bar": "simple_exclude_dict model bar",
+ },
+ "baz": "simple_exclude_dict model2 baz",
+ }
+
+
+@app.get(
+ "/mixed",
+ response_model=Model3,
+ response_model_include={"ref2", "name"},
+ response_model_exclude={"ref2": {"baz"}},
+)
+def mixed():
+ return Model3(
+ name="mixed model3 name",
+ age=3,
+ ref2=Model2(
+ ref=Model1(foo="mixed model foo", bar="mixed model bar"),
+ baz="mixed model2 baz",
+ ),
+ )
+
+
+@app.get(
+ "/mixed_dict",
+ response_model=Model3,
+ response_model_include={"ref2", "name"},
+ response_model_exclude={"ref2": {"baz"}},
+)
+def mixed_dict():
+ return {
+ "name": "mixed_dict model3 name",
+ "age": 3,
+ "ref2": {
+ "ref": {"foo": "mixed_dict model foo", "bar": "mixed_dict model bar"},
+ "baz": "mixed_dict model2 baz",
+ },
+ }
+
+
+client = TestClient(app)
+
+
+def test_nested_include_simple():
+ response = client.get("/simple_include")
+
+ assert response.status_code == 200, response.text
+
+ assert response.json() == {
+ "baz": "simple_include model2 baz",
+ "ref": {"foo": "simple_include model foo"},
+ }
+
+
+def test_nested_include_simple_dict():
+ response = client.get("/simple_include_dict")
+
+ assert response.status_code == 200, response.text
+
+ assert response.json() == {
+ "baz": "simple_include_dict model2 baz",
+ "ref": {"foo": "simple_include_dict model foo"},
+ }
+
+
+def test_nested_exclude_simple():
+ response = client.get("/simple_exclude")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "baz": "simple_exclude model2 baz",
+ "ref": {"foo": "simple_exclude model foo"},
+ }
+
+
+def test_nested_exclude_simple_dict():
+ response = client.get("/simple_exclude_dict")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "baz": "simple_exclude_dict model2 baz",
+ "ref": {"foo": "simple_exclude_dict model foo"},
+ }
+
+
+def test_nested_include_mixed():
+ response = client.get("/mixed")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "name": "mixed model3 name",
+ "ref2": {
+ "ref": {"foo": "mixed model foo", "bar": "mixed model bar"},
+ },
+ }
+
+
+def test_nested_include_mixed_dict():
+ response = client.get("/mixed_dict")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "name": "mixed_dict model3 name",
+ "ref2": {
+ "ref": {"foo": "mixed_dict model foo", "bar": "mixed_dict model bar"},
+ },
+ }
diff --git a/tests/test_route_scope.py b/tests/test_route_scope.py
new file mode 100644
index 00000000..a188e9a5
--- /dev/null
+++ b/tests/test_route_scope.py
@@ -0,0 +1,50 @@
+import pytest
+from fastapi import FastAPI, Request, WebSocket, WebSocketDisconnect
+from fastapi.routing import APIRoute, APIWebSocketRoute
+from fastapi.testclient import TestClient
+
+app = FastAPI()
+
+
+@app.get("/users/{user_id}")
+async def get_user(user_id: str, request: Request):
+ route: APIRoute = request.scope["route"]
+ return {"user_id": user_id, "path": route.path}
+
+
+@app.websocket("/items/{item_id}")
+async def websocket_item(item_id: str, websocket: WebSocket):
+ route: APIWebSocketRoute = websocket.scope["route"]
+ await websocket.accept()
+ await websocket.send_json({"item_id": item_id, "path": route.path})
+
+
+client = TestClient(app)
+
+
+def test_get():
+ response = client.get("/users/rick")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"user_id": "rick", "path": "/users/{user_id}"}
+
+
+def test_invalid_method_doesnt_match():
+ response = client.post("/users/rick")
+ assert response.status_code == 405, response.text
+
+
+def test_invalid_path_doesnt_match():
+ response = client.post("/usersx/rick")
+ assert response.status_code == 404, response.text
+
+
+def test_websocket():
+ with client.websocket_connect("/items/portal-gun") as websocket:
+ data = websocket.receive_json()
+ assert data == {"item_id": "portal-gun", "path": "/items/{item_id}"}
+
+
+def test_websocket_invalid_path_doesnt_match():
+ with pytest.raises(WebSocketDisconnect):
+ with client.websocket_connect("/itemsx/portal-gun") as websocket:
+ websocket.receive_json()
diff --git a/tests/test_schema_extra_examples.py b/tests/test_schema_extra_examples.py
index 3e0d846c..444e350a 100644
--- a/tests/test_schema_extra_examples.py
+++ b/tests/test_schema_extra_examples.py
@@ -830,7 +830,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_security_api_key_cookie_description.py b/tests/test_security_api_key_cookie_description.py
new file mode 100644
index 00000000..2cd3565b
--- /dev/null
+++ b/tests/test_security_api_key_cookie_description.py
@@ -0,0 +1,73 @@
+from fastapi import Depends, FastAPI, Security
+from fastapi.security import APIKeyCookie
+from fastapi.testclient import TestClient
+from pydantic import BaseModel
+
+app = FastAPI()
+
+api_key = APIKeyCookie(name="key", description="An API Cookie Key")
+
+
+class User(BaseModel):
+ username: str
+
+
+def get_current_user(oauth_header: str = Security(api_key)):
+ user = User(username=oauth_header)
+ return user
+
+
+@app.get("/users/me")
+def read_current_user(current_user: User = Depends(get_current_user)):
+ return current_user
+
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "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": [{"APIKeyCookie": []}],
+ }
+ }
+ },
+ "components": {
+ "securitySchemes": {
+ "APIKeyCookie": {
+ "type": "apiKey",
+ "name": "key",
+ "in": "cookie",
+ "description": "An API Cookie Key",
+ }
+ }
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+def test_security_api_key():
+ response = client.get("/users/me", cookies={"key": "secret"})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"username": "secret"}
+
+
+def test_security_api_key_no_key():
+ response = client.get("/users/me")
+ assert response.status_code == 403, response.text
+ assert response.json() == {"detail": "Not authenticated"}
diff --git a/tests/test_security_api_key_header_description.py b/tests/test_security_api_key_header_description.py
new file mode 100644
index 00000000..cc980270
--- /dev/null
+++ b/tests/test_security_api_key_header_description.py
@@ -0,0 +1,73 @@
+from fastapi import Depends, FastAPI, Security
+from fastapi.security import APIKeyHeader
+from fastapi.testclient import TestClient
+from pydantic import BaseModel
+
+app = FastAPI()
+
+api_key = APIKeyHeader(name="key", description="An API Key Header")
+
+
+class User(BaseModel):
+ username: str
+
+
+def get_current_user(oauth_header: str = Security(api_key)):
+ user = User(username=oauth_header)
+ return user
+
+
+@app.get("/users/me")
+def read_current_user(current_user: User = Depends(get_current_user)):
+ return current_user
+
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "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": [{"APIKeyHeader": []}],
+ }
+ }
+ },
+ "components": {
+ "securitySchemes": {
+ "APIKeyHeader": {
+ "type": "apiKey",
+ "name": "key",
+ "in": "header",
+ "description": "An API Key Header",
+ }
+ }
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+def test_security_api_key():
+ response = client.get("/users/me", headers={"key": "secret"})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"username": "secret"}
+
+
+def test_security_api_key_no_key():
+ response = client.get("/users/me")
+ assert response.status_code == 403, response.text
+ assert response.json() == {"detail": "Not authenticated"}
diff --git a/tests/test_security_api_key_query_description.py b/tests/test_security_api_key_query_description.py
new file mode 100644
index 00000000..9b608233
--- /dev/null
+++ b/tests/test_security_api_key_query_description.py
@@ -0,0 +1,73 @@
+from fastapi import Depends, FastAPI, Security
+from fastapi.security import APIKeyQuery
+from fastapi.testclient import TestClient
+from pydantic import BaseModel
+
+app = FastAPI()
+
+api_key = APIKeyQuery(name="key", description="API Key Query")
+
+
+class User(BaseModel):
+ username: str
+
+
+def get_current_user(oauth_header: str = Security(api_key)):
+ user = User(username=oauth_header)
+ return user
+
+
+@app.get("/users/me")
+def read_current_user(current_user: User = Depends(get_current_user)):
+ return current_user
+
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "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": [{"APIKeyQuery": []}],
+ }
+ }
+ },
+ "components": {
+ "securitySchemes": {
+ "APIKeyQuery": {
+ "type": "apiKey",
+ "name": "key",
+ "in": "query",
+ "description": "API Key Query",
+ }
+ }
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+def test_security_api_key():
+ response = client.get("/users/me?key=secret")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"username": "secret"}
+
+
+def test_security_api_key_no_key():
+ response = client.get("/users/me")
+ assert response.status_code == 403, response.text
+ assert response.json() == {"detail": "Not authenticated"}
diff --git a/tests/test_security_http_base_description.py b/tests/test_security_http_base_description.py
new file mode 100644
index 00000000..5855e8df
--- /dev/null
+++ b/tests/test_security_http_base_description.py
@@ -0,0 +1,62 @@
+from fastapi import FastAPI, Security
+from fastapi.security.http import HTTPAuthorizationCredentials, HTTPBase
+from fastapi.testclient import TestClient
+
+app = FastAPI()
+
+security = HTTPBase(scheme="Other", description="Other Security Scheme")
+
+
+@app.get("/users/me")
+def read_current_user(credentials: HTTPAuthorizationCredentials = Security(security)):
+ return {"scheme": credentials.scheme, "credentials": credentials.credentials}
+
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "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": [{"HTTPBase": []}],
+ }
+ }
+ },
+ "components": {
+ "securitySchemes": {
+ "HTTPBase": {
+ "type": "http",
+ "scheme": "Other",
+ "description": "Other Security Scheme",
+ }
+ }
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+def test_security_http_base():
+ response = client.get("/users/me", headers={"Authorization": "Other foobar"})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"scheme": "Other", "credentials": "foobar"}
+
+
+def test_security_http_base_no_credentials():
+ response = client.get("/users/me")
+ assert response.status_code == 403, response.text
+ assert response.json() == {"detail": "Not authenticated"}
diff --git a/tests/test_security_http_basic_realm_description.py b/tests/test_security_http_basic_realm_description.py
new file mode 100644
index 00000000..6ff9d9d0
--- /dev/null
+++ b/tests/test_security_http_basic_realm_description.py
@@ -0,0 +1,85 @@
+from base64 import b64encode
+
+from fastapi import FastAPI, Security
+from fastapi.security import HTTPBasic, HTTPBasicCredentials
+from fastapi.testclient import TestClient
+from requests.auth import HTTPBasicAuth
+
+app = FastAPI()
+
+security = HTTPBasic(realm="simple", description="HTTPBasic scheme")
+
+
+@app.get("/users/me")
+def read_current_user(credentials: HTTPBasicCredentials = Security(security)):
+ return {"username": credentials.username, "password": credentials.password}
+
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "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",
+ "description": "HTTPBasic scheme",
+ }
+ }
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+def test_security_http_basic():
+ auth = HTTPBasicAuth(username="john", password="secret")
+ response = client.get("/users/me", auth=auth)
+ 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 realm="simple"'
+
+
+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 realm="simple"'
+ 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 realm="simple"'
+ assert response.json() == {"detail": "Invalid authentication credentials"}
diff --git a/tests/test_security_http_bearer_description.py b/tests/test_security_http_bearer_description.py
new file mode 100644
index 00000000..132e720f
--- /dev/null
+++ b/tests/test_security_http_bearer_description.py
@@ -0,0 +1,68 @@
+from fastapi import FastAPI, Security
+from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
+from fastapi.testclient import TestClient
+
+app = FastAPI()
+
+security = HTTPBearer(description="HTTP Bearer token scheme")
+
+
+@app.get("/users/me")
+def read_current_user(credentials: HTTPAuthorizationCredentials = Security(security)):
+ return {"scheme": credentials.scheme, "credentials": credentials.credentials}
+
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "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": [{"HTTPBearer": []}],
+ }
+ }
+ },
+ "components": {
+ "securitySchemes": {
+ "HTTPBearer": {
+ "type": "http",
+ "scheme": "bearer",
+ "description": "HTTP Bearer token scheme",
+ }
+ }
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+def test_security_http_bearer():
+ response = client.get("/users/me", headers={"Authorization": "Bearer foobar"})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"scheme": "Bearer", "credentials": "foobar"}
+
+
+def test_security_http_bearer_no_credentials():
+ response = client.get("/users/me")
+ assert response.status_code == 403, response.text
+ assert response.json() == {"detail": "Not authenticated"}
+
+
+def test_security_http_bearer_incorrect_scheme_credentials():
+ response = client.get("/users/me", headers={"Authorization": "Basic notreally"})
+ assert response.status_code == 403, response.text
+ assert response.json() == {"detail": "Invalid authentication credentials"}
diff --git a/tests/test_security_http_digest_description.py b/tests/test_security_http_digest_description.py
new file mode 100644
index 00000000..d00aa1b6
--- /dev/null
+++ b/tests/test_security_http_digest_description.py
@@ -0,0 +1,70 @@
+from fastapi import FastAPI, Security
+from fastapi.security import HTTPAuthorizationCredentials, HTTPDigest
+from fastapi.testclient import TestClient
+
+app = FastAPI()
+
+security = HTTPDigest(description="HTTPDigest scheme")
+
+
+@app.get("/users/me")
+def read_current_user(credentials: HTTPAuthorizationCredentials = Security(security)):
+ return {"scheme": credentials.scheme, "credentials": credentials.credentials}
+
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "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": [{"HTTPDigest": []}],
+ }
+ }
+ },
+ "components": {
+ "securitySchemes": {
+ "HTTPDigest": {
+ "type": "http",
+ "scheme": "digest",
+ "description": "HTTPDigest scheme",
+ }
+ }
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+def test_security_http_digest():
+ response = client.get("/users/me", headers={"Authorization": "Digest foobar"})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"scheme": "Digest", "credentials": "foobar"}
+
+
+def test_security_http_digest_no_credentials():
+ response = client.get("/users/me")
+ assert response.status_code == 403, response.text
+ assert response.json() == {"detail": "Not authenticated"}
+
+
+def test_security_http_digest_incorrect_scheme_credentials():
+ response = client.get(
+ "/users/me", headers={"Authorization": "Other invalidauthorization"}
+ )
+ assert response.status_code == 403, response.text
+ assert response.json() == {"detail": "Invalid authentication credentials"}
diff --git a/tests/test_security_oauth2.py b/tests/test_security_oauth2.py
index b7ada7ca..b9ac488e 100644
--- a/tests/test_security_oauth2.py
+++ b/tests/test_security_oauth2.py
@@ -117,7 +117,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_security_oauth2_authorization_code_bearer_description.py b/tests/test_security_oauth2_authorization_code_bearer_description.py
new file mode 100644
index 00000000..bdaa543f
--- /dev/null
+++ b/tests/test_security_oauth2_authorization_code_bearer_description.py
@@ -0,0 +1,81 @@
+from typing import Optional
+
+from fastapi import FastAPI, Security
+from fastapi.security import OAuth2AuthorizationCodeBearer
+from fastapi.testclient import TestClient
+
+app = FastAPI()
+
+oauth2_scheme = OAuth2AuthorizationCodeBearer(
+ authorizationUrl="authorize",
+ tokenUrl="token",
+ description="OAuth2 Code Bearer",
+ auto_error=True,
+)
+
+
+@app.get("/items/")
+async def read_items(token: Optional[str] = Security(oauth2_scheme)):
+ return {"token": token}
+
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "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": [{"OAuth2AuthorizationCodeBearer": []}],
+ }
+ }
+ },
+ "components": {
+ "securitySchemes": {
+ "OAuth2AuthorizationCodeBearer": {
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "authorize",
+ "tokenUrl": "token",
+ "scopes": {},
+ }
+ },
+ "description": "OAuth2 Code Bearer",
+ }
+ }
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+def test_no_token():
+ response = client.get("/items")
+ assert response.status_code == 401, response.text
+ assert response.json() == {"detail": "Not authenticated"}
+
+
+def test_incorrect_token():
+ response = client.get("/items", headers={"Authorization": "Non-existent testtoken"})
+ assert response.status_code == 401, response.text
+ assert response.json() == {"detail": "Not authenticated"}
+
+
+def test_token():
+ response = client.get("/items", headers={"Authorization": "Bearer testtoken"})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"token": "testtoken"}
diff --git a/tests/test_security_oauth2_optional.py b/tests/test_security_oauth2_optional.py
index ecc76651..a5fd49b8 100644
--- a/tests/test_security_oauth2_optional.py
+++ b/tests/test_security_oauth2_optional.py
@@ -121,7 +121,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_security_oauth2_optional_description.py b/tests/test_security_oauth2_optional_description.py
new file mode 100644
index 00000000..171f96b7
--- /dev/null
+++ b/tests/test_security_oauth2_optional_description.py
@@ -0,0 +1,255 @@
+from typing import Optional
+
+import pytest
+from fastapi import Depends, FastAPI, Security
+from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict
+from fastapi.testclient import TestClient
+from pydantic import BaseModel
+
+app = FastAPI()
+
+reusable_oauth2 = OAuth2(
+ flows={
+ "password": {
+ "tokenUrl": "token",
+ "scopes": {"read:users": "Read the users", "write:users": "Create users"},
+ }
+ },
+ description="OAuth2 security scheme",
+ auto_error=False,
+)
+
+
+class User(BaseModel):
+ username: str
+
+
+def get_current_user(oauth_header: Optional[str] = Security(reusable_oauth2)):
+ if oauth_header is None:
+ return None
+ user = User(username=oauth_header)
+ return user
+
+
+@app.post("/login")
+def login(form_data: OAuth2PasswordRequestFormStrict = Depends()):
+ return form_data
+
+
+@app.get("/users/me")
+def read_users_me(current_user: Optional[User] = Depends(get_current_user)):
+ if current_user is None:
+ return {"msg": "Create an account first"}
+ return current_user
+
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "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/Body_login_login_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": [{"OAuth2": []}],
+ }
+ },
+ },
+ "components": {
+ "schemas": {
+ "Body_login_login_post": {
+ "title": "Body_login_login_post",
+ "required": ["grant_type", "username", "password"],
+ "type": "object",
+ "properties": {
+ "grant_type": {
+ "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": {"title": "Client Id", "type": "string"},
+ "client_secret": {"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": {
+ "OAuth2": {
+ "type": "oauth2",
+ "flows": {
+ "password": {
+ "scopes": {
+ "read:users": "Read the users",
+ "write:users": "Create users",
+ },
+ "tokenUrl": "token",
+ }
+ },
+ "description": "OAuth2 security scheme",
+ }
+ },
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+def test_security_oauth2():
+ response = client.get("/users/me", headers={"Authorization": "Bearer footokenbar"})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"username": "Bearer footokenbar"}
+
+
+def test_security_oauth2_password_other_header():
+ response = client.get("/users/me", headers={"Authorization": "Other footokenbar"})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"username": "Other footokenbar"}
+
+
+def test_security_oauth2_password_bearer_no_header():
+ response = client.get("/users/me")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"msg": "Create an account first"}
+
+
+required_params = {
+ "detail": [
+ {
+ "loc": ["body", "grant_type"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ {
+ "loc": ["body", "username"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ {
+ "loc": ["body", "password"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ ]
+}
+
+grant_type_required = {
+ "detail": [
+ {
+ "loc": ["body", "grant_type"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ }
+ ]
+}
+
+grant_type_incorrect = {
+ "detail": [
+ {
+ "loc": ["body", "grant_type"],
+ "msg": 'string does not match regex "password"',
+ "type": "value_error.str.regex",
+ "ctx": {"pattern": "password"},
+ }
+ ]
+}
+
+
+@pytest.mark.parametrize(
+ "data,expected_status,expected_response",
+ [
+ (None, 422, required_params),
+ ({"username": "johndoe", "password": "secret"}, 422, grant_type_required),
+ (
+ {"username": "johndoe", "password": "secret", "grant_type": "incorrect"},
+ 422,
+ grant_type_incorrect,
+ ),
+ (
+ {"username": "johndoe", "password": "secret", "grant_type": "password"},
+ 200,
+ {
+ "grant_type": "password",
+ "username": "johndoe",
+ "password": "secret",
+ "scopes": [],
+ "client_id": None,
+ "client_secret": None,
+ },
+ ),
+ ],
+)
+def test_strict_login(data, expected_status, expected_response):
+ response = client.post("/login", data=data)
+ assert response.status_code == expected_status
+ assert response.json() == expected_response
diff --git a/tests/test_security_oauth2_password_bearer_optional_description.py b/tests/test_security_oauth2_password_bearer_optional_description.py
new file mode 100644
index 00000000..9d6a862e
--- /dev/null
+++ b/tests/test_security_oauth2_password_bearer_optional_description.py
@@ -0,0 +1,76 @@
+from typing import Optional
+
+from fastapi import FastAPI, Security
+from fastapi.security import OAuth2PasswordBearer
+from fastapi.testclient import TestClient
+
+app = FastAPI()
+
+oauth2_scheme = OAuth2PasswordBearer(
+ tokenUrl="/token",
+ description="OAuth2PasswordBearer security scheme",
+ auto_error=False,
+)
+
+
+@app.get("/items/")
+async def read_items(token: Optional[str] = Security(oauth2_scheme)):
+ if token is None:
+ return {"msg": "Create an account first"}
+ return {"token": token}
+
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "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"}},
+ "description": "OAuth2PasswordBearer security scheme",
+ }
+ }
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+def test_no_token():
+ response = client.get("/items")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"msg": "Create an account first"}
+
+
+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 == 200, response.text
+ assert response.json() == {"msg": "Create an account first"}
diff --git a/tests/test_security_openid_connect_description.py b/tests/test_security_openid_connect_description.py
new file mode 100644
index 00000000..218cbfc8
--- /dev/null
+++ b/tests/test_security_openid_connect_description.py
@@ -0,0 +1,80 @@
+from fastapi import Depends, FastAPI, Security
+from fastapi.security.open_id_connect_url import OpenIdConnect
+from fastapi.testclient import TestClient
+from pydantic import BaseModel
+
+app = FastAPI()
+
+oid = OpenIdConnect(
+ openIdConnectUrl="/openid", description="OpenIdConnect security scheme"
+)
+
+
+class User(BaseModel):
+ username: str
+
+
+def get_current_user(oauth_header: str = Security(oid)):
+ user = User(username=oauth_header)
+ return user
+
+
+@app.get("/users/me")
+def read_current_user(current_user: User = Depends(get_current_user)):
+ return current_user
+
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "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": [{"OpenIdConnect": []}],
+ }
+ }
+ },
+ "components": {
+ "securitySchemes": {
+ "OpenIdConnect": {
+ "type": "openIdConnect",
+ "openIdConnectUrl": "/openid",
+ "description": "OpenIdConnect security scheme",
+ }
+ }
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+def test_security_oauth2():
+ response = client.get("/users/me", headers={"Authorization": "Bearer footokenbar"})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"username": "Bearer footokenbar"}
+
+
+def test_security_oauth2_password_other_header():
+ response = client.get("/users/me", headers={"Authorization": "Other footokenbar"})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"username": "Other footokenbar"}
+
+
+def test_security_oauth2_password_bearer_no_header():
+ response = client.get("/users/me")
+ assert response.status_code == 403, response.text
+ assert response.json() == {"detail": "Not authenticated"}
diff --git a/tests/test_serialize_response_dataclass.py b/tests/test_serialize_response_dataclass.py
index d1b64c4e..e520338e 100644
--- a/tests/test_serialize_response_dataclass.py
+++ b/tests/test_serialize_response_dataclass.py
@@ -19,6 +19,11 @@ def get_valid():
return {"name": "valid", "price": 1.0}
+@app.get("/items/object", response_model=Item)
+def get_object():
+ return Item(name="object", price=1.0, owner_ids=[1, 2, 3])
+
+
@app.get("/items/coerce", response_model=Item)
def get_coerce():
return {"name": "coerce", "price": "1.0"}
@@ -33,6 +38,29 @@ def get_validlist():
]
+@app.get("/items/objectlist", response_model=List[Item])
+def get_objectlist():
+ return [
+ Item(name="foo"),
+ Item(name="bar", price=1.0),
+ Item(name="baz", price=2.0, owner_ids=[1, 2, 3]),
+ ]
+
+
+@app.get("/items/no-response-model/object")
+def get_no_response_model_object():
+ return Item(name="object", price=1.0, owner_ids=[1, 2, 3])
+
+
+@app.get("/items/no-response-model/objectlist")
+def get_no_response_model_objectlist():
+ return [
+ Item(name="foo"),
+ Item(name="bar", price=1.0),
+ Item(name="baz", price=2.0, owner_ids=[1, 2, 3]),
+ ]
+
+
client = TestClient(app)
@@ -42,6 +70,12 @@ def test_valid():
assert response.json() == {"name": "valid", "price": 1.0, "owner_ids": None}
+def test_object():
+ response = client.get("/items/object")
+ response.raise_for_status()
+ assert response.json() == {"name": "object", "price": 1.0, "owner_ids": [1, 2, 3]}
+
+
def test_coerce():
response = client.get("/items/coerce")
response.raise_for_status()
@@ -56,3 +90,29 @@ def test_validlist():
{"name": "bar", "price": 1.0, "owner_ids": None},
{"name": "baz", "price": 2.0, "owner_ids": [1, 2, 3]},
]
+
+
+def test_objectlist():
+ response = client.get("/items/objectlist")
+ response.raise_for_status()
+ assert response.json() == [
+ {"name": "foo", "price": None, "owner_ids": None},
+ {"name": "bar", "price": 1.0, "owner_ids": None},
+ {"name": "baz", "price": 2.0, "owner_ids": [1, 2, 3]},
+ ]
+
+
+def test_no_response_model_object():
+ response = client.get("/items/no-response-model/object")
+ response.raise_for_status()
+ assert response.json() == {"name": "object", "price": 1.0, "owner_ids": [1, 2, 3]}
+
+
+def test_no_response_model_objectlist():
+ response = client.get("/items/no-response-model/objectlist")
+ response.raise_for_status()
+ assert response.json() == [
+ {"name": "foo", "price": None, "owner_ids": None},
+ {"name": "bar", "price": 1.0, "owner_ids": None},
+ {"name": "baz", "price": 2.0, "owner_ids": [1, 2, 3]},
+ ]
diff --git a/tests/test_starlette_exception.py b/tests/test_starlette_exception.py
index 5759a93f..859169d3 100644
--- a/tests/test_starlette_exception.py
+++ b/tests/test_starlette_exception.py
@@ -102,7 +102,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_sub_callbacks.py b/tests/test_sub_callbacks.py
index 16644b55..7574d6fb 100644
--- a/tests/test_sub_callbacks.py
+++ b/tests/test_sub_callbacks.py
@@ -256,7 +256,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tuples.py b/tests/test_tuples.py
new file mode 100644
index 00000000..2085dc36
--- /dev/null
+++ b/tests/test_tuples.py
@@ -0,0 +1,267 @@
+from typing import List, Tuple
+
+from fastapi import FastAPI, Form
+from fastapi.testclient import TestClient
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class ItemGroup(BaseModel):
+ items: List[Tuple[str, str]]
+
+
+class Coordinate(BaseModel):
+ x: float
+ y: float
+
+
+@app.post("/model-with-tuple/")
+def post_model_with_tuple(item_group: ItemGroup):
+ return item_group
+
+
+@app.post("/tuple-of-models/")
+def post_tuple_of_models(square: Tuple[Coordinate, Coordinate]):
+ return square
+
+
+@app.post("/tuple-form/")
+def hello(values: Tuple[int, int] = Form(...)):
+ return values
+
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/model-with-tuple/": {
+ "post": {
+ "summary": "Post Model With Tuple",
+ "operationId": "post_model_with_tuple_model_with_tuple__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/ItemGroup"}
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ "/tuple-of-models/": {
+ "post": {
+ "summary": "Post Tuple Of Models",
+ "operationId": "post_tuple_of_models_tuple_of_models__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Square",
+ "maxItems": 2,
+ "minItems": 2,
+ "type": "array",
+ "items": [
+ {"$ref": "#/components/schemas/Coordinate"},
+ {"$ref": "#/components/schemas/Coordinate"},
+ ],
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ "/tuple-form/": {
+ "post": {
+ "summary": "Hello",
+ "operationId": "hello_tuple_form__post",
+ "requestBody": {
+ "content": {
+ "application/x-www-form-urlencoded": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_hello_tuple_form__post"
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ },
+ "components": {
+ "schemas": {
+ "Body_hello_tuple_form__post": {
+ "title": "Body_hello_tuple_form__post",
+ "required": ["values"],
+ "type": "object",
+ "properties": {
+ "values": {
+ "title": "Values",
+ "maxItems": 2,
+ "minItems": 2,
+ "type": "array",
+ "items": [{"type": "integer"}, {"type": "integer"}],
+ }
+ },
+ },
+ "Coordinate": {
+ "title": "Coordinate",
+ "required": ["x", "y"],
+ "type": "object",
+ "properties": {
+ "x": {"title": "X", "type": "number"},
+ "y": {"title": "Y", "type": "number"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ "ItemGroup": {
+ "title": "ItemGroup",
+ "required": ["items"],
+ "type": "object",
+ "properties": {
+ "items": {
+ "title": "Items",
+ "type": "array",
+ "items": {
+ "maxItems": 2,
+ "minItems": 2,
+ "type": "array",
+ "items": [{"type": "string"}, {"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"},
+ },
+ },
+ }
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+def test_model_with_tuple_valid():
+ data = {"items": [["foo", "bar"], ["baz", "whatelse"]]}
+ response = client.post("/model-with-tuple/", json=data)
+ assert response.status_code == 200, response.text
+ assert response.json() == data
+
+
+def test_model_with_tuple_invalid():
+ data = {"items": [["foo", "bar"], ["baz", "whatelse", "too", "much"]]}
+ response = client.post("/model-with-tuple/", json=data)
+ assert response.status_code == 422, response.text
+
+ data = {"items": [["foo", "bar"], ["baz"]]}
+ response = client.post("/model-with-tuple/", json=data)
+ assert response.status_code == 422, response.text
+
+
+def test_tuple_with_model_valid():
+ data = [{"x": 1, "y": 2}, {"x": 3, "y": 4}]
+ response = client.post("/tuple-of-models/", json=data)
+ assert response.status_code == 200, response.text
+ assert response.json() == data
+
+
+def test_tuple_with_model_invalid():
+ data = [{"x": 1, "y": 2}, {"x": 3, "y": 4}, {"x": 5, "y": 6}]
+ response = client.post("/tuple-of-models/", json=data)
+ assert response.status_code == 422, response.text
+
+ data = [{"x": 1, "y": 2}]
+ response = client.post("/tuple-of-models/", json=data)
+ assert response.status_code == 422, response.text
+
+
+def test_tuple_form_valid():
+ response = client.post("/tuple-form/", data=[("values", "1"), ("values", "2")])
+ assert response.status_code == 200, response.text
+ assert response.json() == [1, 2]
+
+
+def test_tuple_form_invalid():
+ response = client.post(
+ "/tuple-form/", data=[("values", "1"), ("values", "2"), ("values", "3")]
+ )
+ assert response.status_code == 422, response.text
+
+ response = client.post("/tuple-form/", data=[("values", "1")])
+ assert response.status_code == 422, response.text
diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial001.py b/tests/test_tutorial/test_additional_responses/test_tutorial001.py
index 8342dd78..1a8acb52 100644
--- a/tests/test_tutorial/test_additional_responses/test_tutorial001.py
+++ b/tests/test_tutorial/test_additional_responses/test_tutorial001.py
@@ -76,7 +76,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial002.py b/tests/test_tutorial/test_additional_responses/test_tutorial002.py
index 57f87797..2adcf15d 100644
--- a/tests/test_tutorial/test_additional_responses/test_tutorial002.py
+++ b/tests/test_tutorial/test_additional_responses/test_tutorial002.py
@@ -72,7 +72,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial003.py b/tests/test_tutorial/test_additional_responses/test_tutorial003.py
index 37190b36..8b2167de 100644
--- a/tests/test_tutorial/test_additional_responses/test_tutorial003.py
+++ b/tests/test_tutorial/test_additional_responses/test_tutorial003.py
@@ -77,7 +77,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial004.py b/tests/test_tutorial/test_additional_responses/test_tutorial004.py
index c44a18f6..990d5235 100644
--- a/tests/test_tutorial/test_additional_responses/test_tutorial004.py
+++ b/tests/test_tutorial/test_additional_responses/test_tutorial004.py
@@ -75,7 +75,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_async_sql_databases/test_tutorial001.py b/tests/test_tutorial/test_async_sql_databases/test_tutorial001.py
index 90feb017..1ad625db 100644
--- a/tests/test_tutorial/test_async_sql_databases/test_tutorial001.py
+++ b/tests/test_tutorial/test_async_sql_databases/test_tutorial001.py
@@ -88,7 +88,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_async_tests/test_main.py b/tests/test_tutorial/test_async_tests/test_main.py
index 8104c905..1f5d7186 100644
--- a/tests/test_tutorial/test_async_tests/test_main.py
+++ b/tests/test_tutorial/test_async_tests/test_main.py
@@ -3,6 +3,6 @@ import pytest
from docs_src.async_tests.test_main import test_root
-@pytest.mark.asyncio
+@pytest.mark.anyio
async def test_async_testing():
await test_root()
diff --git a/tests/test_tutorial/test_background_tasks/test_tutorial002_py310.py b/tests/test_tutorial/test_background_tasks/test_tutorial002_py310.py
new file mode 100644
index 00000000..6937c8fa
--- /dev/null
+++ b/tests/test_tutorial/test_background_tasks/test_tutorial002_py310.py
@@ -0,0 +1,21 @@
+import os
+from pathlib import Path
+
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+
+@needs_py310
+def test():
+ from docs_src.background_tasks.tutorial002_py310 import app
+
+ client = TestClient(app)
+ log = Path("log.txt")
+ if log.is_file():
+ os.remove(log) # pragma: no cover
+ response = client.post("/send-notification/foo@example.com?q=some-query")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"message": "Message sent"}
+ with open("./log.txt") as f:
+ assert "found query: some-query\nmessage to foo@example.com" in f.read()
diff --git a/tests/test_tutorial/test_bigger_applications/test_main.py b/tests/test_tutorial/test_bigger_applications/test_main.py
index 3670e25c..cd6d7b5c 100644
--- a/tests/test_tutorial/test_bigger_applications/test_main.py
+++ b/tests/test_tutorial/test_bigger_applications/test_main.py
@@ -323,7 +323,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
@@ -359,6 +359,12 @@ no_jessica = {
("/users/foo", 422, no_jessica, {}),
("/users/me?token=jessica", 200, {"username": "fakecurrentuser"}, {}),
("/users/me", 422, no_jessica, {}),
+ (
+ "/users?token=monica",
+ 400,
+ {"detail": "No Jessica token provided"},
+ {},
+ ),
(
"/items?token=jessica",
200,
@@ -372,6 +378,12 @@ no_jessica = {
{"name": "Plumbus", "item_id": "plumbus"},
{"X-Token": "fake-super-secret-token"},
),
+ (
+ "/items/bar?token=jessica",
+ 404,
+ {"detail": "Item not found"},
+ {"X-Token": "fake-super-secret-token"},
+ ),
("/items/plumbus", 422, no_jessica, {"X-Token": "fake-super-secret-token"}),
(
"/items?token=jessica",
diff --git a/tests/test_tutorial/test_body/test_tutorial001.py b/tests/test_tutorial/test_body/test_tutorial001.py
index c90240ae..8dbaf15d 100644
--- a/tests/test_tutorial/test_body/test_tutorial001.py
+++ b/tests/test_tutorial/test_body/test_tutorial001.py
@@ -63,7 +63,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
@@ -229,6 +229,20 @@ def test_geo_json():
assert response.status_code == 200, response.text
+def test_no_content_type_is_json():
+ response = client.post(
+ "/items/",
+ data='{"name": "Foo", "price": 50.5}',
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "name": "Foo",
+ "description": None,
+ "price": 50.5,
+ "tax": None,
+ }
+
+
def test_wrong_headers():
data = '{"name": "Foo", "price": 50.5}'
invalid_dict = {
diff --git a/tests/test_tutorial/test_body/test_tutorial001_py310.py b/tests/test_tutorial/test_body/test_tutorial001_py310.py
new file mode 100644
index 00000000..dd9d9911
--- /dev/null
+++ b/tests/test_tutorial/test_body/test_tutorial001_py310.py
@@ -0,0 +1,292 @@
+from unittest.mock import patch
+
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "post": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Create Item",
+ "operationId": "create_item_items__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ "required": True,
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Item": {
+ "title": "Item",
+ "required": ["name", "price"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "price": {"title": "Price", "type": "number"},
+ "description": {"title": "Description", "type": "string"},
+ "tax": {"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"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture
+def client():
+ from docs_src.body.tutorial001_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+price_missing = {
+ "detail": [
+ {
+ "loc": ["body", "price"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ }
+ ]
+}
+
+price_not_float = {
+ "detail": [
+ {
+ "loc": ["body", "price"],
+ "msg": "value is not a valid float",
+ "type": "type_error.float",
+ }
+ ]
+}
+
+name_price_missing = {
+ "detail": [
+ {
+ "loc": ["body", "name"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ {
+ "loc": ["body", "price"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ ]
+}
+
+body_missing = {
+ "detail": [
+ {"loc": ["body"], "msg": "field required", "type": "value_error.missing"}
+ ]
+}
+
+
+@needs_py310
+@pytest.mark.parametrize(
+ "path,body,expected_status,expected_response",
+ [
+ (
+ "/items/",
+ {"name": "Foo", "price": 50.5},
+ 200,
+ {"name": "Foo", "price": 50.5, "description": None, "tax": None},
+ ),
+ (
+ "/items/",
+ {"name": "Foo", "price": "50.5"},
+ 200,
+ {"name": "Foo", "price": 50.5, "description": None, "tax": None},
+ ),
+ (
+ "/items/",
+ {"name": "Foo", "price": "50.5", "description": "Some Foo"},
+ 200,
+ {"name": "Foo", "price": 50.5, "description": "Some Foo", "tax": None},
+ ),
+ (
+ "/items/",
+ {"name": "Foo", "price": "50.5", "description": "Some Foo", "tax": 0.3},
+ 200,
+ {"name": "Foo", "price": 50.5, "description": "Some Foo", "tax": 0.3},
+ ),
+ ("/items/", {"name": "Foo"}, 422, price_missing),
+ ("/items/", {"name": "Foo", "price": "twenty"}, 422, price_not_float),
+ ("/items/", {}, 422, name_price_missing),
+ ("/items/", None, 422, body_missing),
+ ],
+)
+def test_post_body(path, body, expected_status, expected_response, client: TestClient):
+ response = client.post(path, json=body)
+ assert response.status_code == expected_status
+ assert response.json() == expected_response
+
+
+@needs_py310
+def test_post_broken_body(client: TestClient):
+ response = client.post(
+ "/items/",
+ headers={"content-type": "application/json"},
+ data="{some broken json}",
+ )
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["body", 1],
+ "msg": "Expecting property name enclosed in double quotes: line 1 column 2 (char 1)",
+ "type": "value_error.jsondecode",
+ "ctx": {
+ "msg": "Expecting property name enclosed in double quotes",
+ "doc": "{some broken json}",
+ "pos": 1,
+ "lineno": 1,
+ "colno": 2,
+ },
+ }
+ ]
+ }
+
+
+@needs_py310
+def test_post_form_for_json(client: TestClient):
+ response = client.post("/items/", data={"name": "Foo", "price": 50.5})
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["body"],
+ "msg": "value is not a valid dict",
+ "type": "type_error.dict",
+ }
+ ]
+ }
+
+
+@needs_py310
+def test_explicit_content_type(client: TestClient):
+ response = client.post(
+ "/items/",
+ data='{"name": "Foo", "price": 50.5}',
+ headers={"Content-Type": "application/json"},
+ )
+ assert response.status_code == 200, response.text
+
+
+@needs_py310
+def test_geo_json(client: TestClient):
+ response = client.post(
+ "/items/",
+ data='{"name": "Foo", "price": 50.5}',
+ headers={"Content-Type": "application/geo+json"},
+ )
+ assert response.status_code == 200, response.text
+
+
+@needs_py310
+def test_no_content_type_is_json(client: TestClient):
+ response = client.post(
+ "/items/",
+ data='{"name": "Foo", "price": 50.5}',
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "name": "Foo",
+ "description": None,
+ "price": 50.5,
+ "tax": None,
+ }
+
+
+@needs_py310
+def test_wrong_headers(client: TestClient):
+ data = '{"name": "Foo", "price": 50.5}'
+ invalid_dict = {
+ "detail": [
+ {
+ "loc": ["body"],
+ "msg": "value is not a valid dict",
+ "type": "type_error.dict",
+ }
+ ]
+ }
+
+ response = client.post("/items/", data=data, headers={"Content-Type": "text/plain"})
+ assert response.status_code == 422, response.text
+ assert response.json() == invalid_dict
+
+ response = client.post(
+ "/items/", data=data, headers={"Content-Type": "application/geo+json-seq"}
+ )
+ assert response.status_code == 422, response.text
+ assert response.json() == invalid_dict
+ response = client.post(
+ "/items/", data=data, headers={"Content-Type": "application/not-really-json"}
+ )
+ assert response.status_code == 422, response.text
+ assert response.json() == invalid_dict
+
+
+@needs_py310
+def test_other_exceptions(client: TestClient):
+ with patch("json.loads", side_effect=Exception):
+ response = client.post("/items/", json={"test": "test2"})
+ assert response.status_code == 400, response.text
diff --git a/tests/test_tutorial/test_body_fields/test_tutorial001.py b/tests/test_tutorial/test_body_fields/test_tutorial001.py
index 9de4907c..fe5a270f 100644
--- a/tests/test_tutorial/test_body_fields/test_tutorial001.py
+++ b/tests/test_tutorial/test_body_fields/test_tutorial001.py
@@ -87,7 +87,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_body_fields/test_tutorial001_py310.py b/tests/test_tutorial/test_body_fields/test_tutorial001_py310.py
new file mode 100644
index 00000000..993e2a91
--- /dev/null
+++ b/tests/test_tutorial/test_body_fields/test_tutorial001_py310.py
@@ -0,0 +1,176 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "put": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "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": {
+ "$ref": "#/components/schemas/Body_update_item_items__item_id__put"
+ }
+ }
+ },
+ "required": True,
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Item": {
+ "title": "Item",
+ "required": ["name", "price"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "description": {
+ "title": "The description of the item",
+ "maxLength": 300,
+ "type": "string",
+ },
+ "price": {
+ "title": "Price",
+ "exclusiveMinimum": 0.0,
+ "type": "number",
+ "description": "The price must be greater than zero",
+ },
+ "tax": {"title": "Tax", "type": "number"},
+ },
+ },
+ "Body_update_item_items__item_id__put": {
+ "title": "Body_update_item_items__item_id__put",
+ "required": ["item"],
+ "type": "object",
+ "properties": {"item": {"$ref": "#/components/schemas/Item"}},
+ },
+ "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"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.body_fields.tutorial001_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+price_not_greater = {
+ "detail": [
+ {
+ "ctx": {"limit_value": 0},
+ "loc": ["body", "item", "price"],
+ "msg": "ensure this value is greater than 0",
+ "type": "value_error.number.not_gt",
+ }
+ ]
+}
+
+
+@needs_py310
+@pytest.mark.parametrize(
+ "path,body,expected_status,expected_response",
+ [
+ (
+ "/items/5",
+ {"item": {"name": "Foo", "price": 3.0}},
+ 200,
+ {
+ "item_id": 5,
+ "item": {"name": "Foo", "price": 3.0, "description": None, "tax": None},
+ },
+ ),
+ (
+ "/items/6",
+ {
+ "item": {
+ "name": "Bar",
+ "price": 0.2,
+ "description": "Some bar",
+ "tax": "5.4",
+ }
+ },
+ 200,
+ {
+ "item_id": 6,
+ "item": {
+ "name": "Bar",
+ "price": 0.2,
+ "description": "Some bar",
+ "tax": 5.4,
+ },
+ },
+ ),
+ ("/items/5", {"item": {"name": "Foo", "price": -3.0}}, 422, price_not_greater),
+ ],
+)
+def test(path, body, expected_status, expected_response, client: TestClient):
+ response = client.put(path, json=body)
+ assert response.status_code == expected_status
+ assert response.json() == expected_response
diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py
index b11ecdda..8dc710d7 100644
--- a/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py
+++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py
@@ -79,7 +79,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial001_py310.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial001_py310.py
new file mode 100644
index 00000000..5114ccea
--- /dev/null
+++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial001_py310.py
@@ -0,0 +1,155 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "put": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Update Item",
+ "operationId": "update_item_items__item_id__put",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {
+ "title": "The ID of the item to get",
+ "maximum": 1000.0,
+ "minimum": 0.0,
+ "type": "integer",
+ },
+ "name": "item_id",
+ "in": "path",
+ },
+ {
+ "required": False,
+ "schema": {"title": "Q", "type": "string"},
+ "name": "q",
+ "in": "query",
+ },
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ }
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Item": {
+ "title": "Item",
+ "required": ["name", "price"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "price": {"title": "Price", "type": "number"},
+ "description": {"title": "Description", "type": "string"},
+ "tax": {"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"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.body_multiple_params.tutorial001_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+item_id_not_int = {
+ "detail": [
+ {
+ "loc": ["path", "item_id"],
+ "msg": "value is not a valid integer",
+ "type": "type_error.integer",
+ }
+ ]
+}
+
+
+@needs_py310
+@pytest.mark.parametrize(
+ "path,body,expected_status,expected_response",
+ [
+ (
+ "/items/5?q=bar",
+ {"name": "Foo", "price": 50.5},
+ 200,
+ {
+ "item_id": 5,
+ "item": {
+ "name": "Foo",
+ "price": 50.5,
+ "description": None,
+ "tax": None,
+ },
+ "q": "bar",
+ },
+ ),
+ ("/items/5?q=bar", None, 200, {"item_id": 5, "q": "bar"}),
+ ("/items/5", None, 200, {"item_id": 5}),
+ ("/items/foo", None, 422, item_id_not_int),
+ ],
+)
+def test_post_body(path, body, expected_status, expected_response, client: TestClient):
+ response = client.put(path, json=body)
+ assert response.status_code == expected_status
+ assert response.json() == expected_response
diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py
index d98e3e41..64aa9c43 100644
--- a/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py
+++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py
@@ -90,7 +90,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_py310.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003_py310.py
new file mode 100644
index 00000000..fc019d8b
--- /dev/null
+++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial003_py310.py
@@ -0,0 +1,206 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "put": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "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": {
+ "$ref": "#/components/schemas/Body_update_item_items__item_id__put"
+ }
+ }
+ },
+ "required": True,
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Item": {
+ "title": "Item",
+ "required": ["name", "price"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "price": {"title": "Price", "type": "number"},
+ "description": {"title": "Description", "type": "string"},
+ "tax": {"title": "Tax", "type": "number"},
+ },
+ },
+ "User": {
+ "title": "User",
+ "required": ["username"],
+ "type": "object",
+ "properties": {
+ "username": {"title": "Username", "type": "string"},
+ "full_name": {"title": "Full Name", "type": "string"},
+ },
+ },
+ "Body_update_item_items__item_id__put": {
+ "title": "Body_update_item_items__item_id__put",
+ "required": ["item", "user", "importance"],
+ "type": "object",
+ "properties": {
+ "item": {"$ref": "#/components/schemas/Item"},
+ "user": {"$ref": "#/components/schemas/User"},
+ "importance": {"title": "Importance", "type": "integer"},
+ },
+ },
+ "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"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.body_multiple_params.tutorial003_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+# Test required and embedded body parameters with no bodies sent
+@needs_py310
+@pytest.mark.parametrize(
+ "path,body,expected_status,expected_response",
+ [
+ (
+ "/items/5",
+ {
+ "importance": 2,
+ "item": {"name": "Foo", "price": 50.5},
+ "user": {"username": "Dave"},
+ },
+ 200,
+ {
+ "item_id": 5,
+ "importance": 2,
+ "item": {
+ "name": "Foo",
+ "price": 50.5,
+ "description": None,
+ "tax": None,
+ },
+ "user": {"username": "Dave", "full_name": None},
+ },
+ ),
+ (
+ "/items/5",
+ None,
+ 422,
+ {
+ "detail": [
+ {
+ "loc": ["body", "item"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ {
+ "loc": ["body", "user"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ {
+ "loc": ["body", "importance"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ ]
+ },
+ ),
+ (
+ "/items/5",
+ [],
+ 422,
+ {
+ "detail": [
+ {
+ "loc": ["body", "item"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ {
+ "loc": ["body", "user"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ {
+ "loc": ["body", "importance"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ ]
+ },
+ ),
+ ],
+)
+def test_post_body(path, body, expected_status, expected_response, client: TestClient):
+ response = client.put(path, json=body)
+ assert response.status_code == expected_status
+ assert response.json() == expected_response
diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial009.py b/tests/test_tutorial/test_body_nested_models/test_tutorial009.py
index 8eb0ad13..c56d41b5 100644
--- a/tests/test_tutorial/test_body_nested_models/test_tutorial009.py
+++ b/tests/test_tutorial/test_body_nested_models/test_tutorial009.py
@@ -53,7 +53,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial009_py39.py b/tests/test_tutorial/test_body_nested_models/test_tutorial009_py39.py
new file mode 100644
index 00000000..5b8d8286
--- /dev/null
+++ b/tests/test_tutorial/test_body_nested_models/test_tutorial009_py39.py
@@ -0,0 +1,113 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py39
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/index-weights/": {
+ "post": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Create Index Weights",
+ "operationId": "create_index_weights_index_weights__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Weights",
+ "type": "object",
+ "additionalProperties": {"type": "number"},
+ }
+ }
+ },
+ "required": True,
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "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"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.body_nested_models.tutorial009_py39 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py39
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+@needs_py39
+def test_post_body(client: TestClient):
+ data = {"2": 2.2, "3": 3.3}
+ response = client.post("/index-weights/", json=data)
+ assert response.status_code == 200, response.text
+ assert response.json() == data
+
+
+@needs_py39
+def test_post_invalid_body(client: TestClient):
+ data = {"foo": 2.2, "3": 3.3}
+ response = client.post("/index-weights/", json=data)
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["body", "__key__"],
+ "msg": "value is not a valid integer",
+ "type": "type_error.integer",
+ }
+ ]
+ }
diff --git a/tests/test_tutorial/test_body_updates/test_tutorial001.py b/tests/test_tutorial/test_body_updates/test_tutorial001.py
index 5e92ef7e..efd0e467 100644
--- a/tests/test_tutorial/test_body_updates/test_tutorial001.py
+++ b/tests/test_tutorial/test_body_updates/test_tutorial001.py
@@ -109,7 +109,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_body_updates/test_tutorial001_py310.py b/tests/test_tutorial/test_body_updates/test_tutorial001_py310.py
new file mode 100644
index 00000000..49279b32
--- /dev/null
+++ b/tests/test_tutorial/test_body_updates/test_tutorial001_py310.py
@@ -0,0 +1,172 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Item",
+ "operationId": "read_item_items__item_id__get",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "Item Id", "type": "string"},
+ "name": "item_id",
+ "in": "path",
+ }
+ ],
+ },
+ "put": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Update Item",
+ "operationId": "update_item_items__item_id__put",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "Item Id", "type": "string"},
+ "name": "item_id",
+ "in": "path",
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ "required": True,
+ },
+ },
+ }
+ },
+ "components": {
+ "schemas": {
+ "Item": {
+ "title": "Item",
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "description": {"title": "Description", "type": "string"},
+ "price": {"title": "Price", "type": "number"},
+ "tax": {"title": "Tax", "type": "number", "default": 10.5},
+ "tags": {
+ "title": "Tags",
+ "type": "array",
+ "items": {"type": "string"},
+ "default": [],
+ },
+ },
+ },
+ "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"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.body_updates.tutorial001_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+@needs_py310
+def test_get(client: TestClient):
+ response = client.get("/items/baz")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "name": "Baz",
+ "description": None,
+ "price": 50.2,
+ "tax": 10.5,
+ "tags": [],
+ }
+
+
+@needs_py310
+def test_put(client: TestClient):
+ response = client.put(
+ "/items/bar", json={"name": "Barz", "price": 3, "description": None}
+ )
+ assert response.json() == {
+ "name": "Barz",
+ "description": None,
+ "price": 3,
+ "tax": 10.5,
+ "tags": [],
+ }
diff --git a/tests/test_tutorial/test_body_updates/test_tutorial001_py39.py b/tests/test_tutorial/test_body_updates/test_tutorial001_py39.py
new file mode 100644
index 00000000..872530bc
--- /dev/null
+++ b/tests/test_tutorial/test_body_updates/test_tutorial001_py39.py
@@ -0,0 +1,172 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py39
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Item",
+ "operationId": "read_item_items__item_id__get",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "Item Id", "type": "string"},
+ "name": "item_id",
+ "in": "path",
+ }
+ ],
+ },
+ "put": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Update Item",
+ "operationId": "update_item_items__item_id__put",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "Item Id", "type": "string"},
+ "name": "item_id",
+ "in": "path",
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ "required": True,
+ },
+ },
+ }
+ },
+ "components": {
+ "schemas": {
+ "Item": {
+ "title": "Item",
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "description": {"title": "Description", "type": "string"},
+ "price": {"title": "Price", "type": "number"},
+ "tax": {"title": "Tax", "type": "number", "default": 10.5},
+ "tags": {
+ "title": "Tags",
+ "type": "array",
+ "items": {"type": "string"},
+ "default": [],
+ },
+ },
+ },
+ "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"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.body_updates.tutorial001_py39 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py39
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+@needs_py39
+def test_get(client: TestClient):
+ response = client.get("/items/baz")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "name": "Baz",
+ "description": None,
+ "price": 50.2,
+ "tax": 10.5,
+ "tags": [],
+ }
+
+
+@needs_py39
+def test_put(client: TestClient):
+ response = client.put(
+ "/items/bar", json={"name": "Barz", "price": 3, "description": None}
+ )
+ assert response.json() == {
+ "name": "Barz",
+ "description": None,
+ "price": 3,
+ "tax": 10.5,
+ "tags": [],
+ }
diff --git a/tests/test_tutorial/test_conditional_openapi/test_tutorial001.py b/tests/test_tutorial/test_conditional_openapi/test_tutorial001.py
index 828cd62c..93c8775c 100644
--- a/tests/test_tutorial/test_conditional_openapi/test_tutorial001.py
+++ b/tests/test_tutorial/test_conditional_openapi/test_tutorial001.py
@@ -44,3 +44,10 @@ def test_disable_openapi(monkeypatch):
assert response.status_code == 404, response.text
response = client.get("/redoc")
assert response.status_code == 404, response.text
+
+
+def test_root():
+ client = TestClient(tutorial001.app)
+ response = client.get("/")
+ assert response.status_code == 200
+ assert response.json() == {"message": "Hello World"}
diff --git a/tests/test_tutorial/test_cookie_params/test_tutorial001.py b/tests/test_tutorial/test_cookie_params/test_tutorial001.py
index 3451dc19..edccffec 100644
--- a/tests/test_tutorial/test_cookie_params/test_tutorial001.py
+++ b/tests/test_tutorial/test_cookie_params/test_tutorial001.py
@@ -50,7 +50,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_cookie_params/test_tutorial001_py310.py b/tests/test_tutorial/test_cookie_params/test_tutorial001_py310.py
new file mode 100644
index 00000000..5caa5c44
--- /dev/null
+++ b/tests/test_tutorial/test_cookie_params/test_tutorial001_py310.py
@@ -0,0 +1,100 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ "parameters": [
+ {
+ "required": False,
+ "schema": {"title": "Ads Id", "type": "string"},
+ "name": "ads_id",
+ "in": "cookie",
+ }
+ ],
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "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"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.cookie_params.tutorial001_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+@pytest.mark.parametrize(
+ "path,cookies,expected_status,expected_response",
+ [
+ ("/openapi.json", None, 200, openapi_schema),
+ ("/items", None, 200, {"ads_id": None}),
+ ("/items", {"ads_id": "ads_track"}, 200, {"ads_id": "ads_track"}),
+ (
+ "/items",
+ {"ads_id": "ads_track", "session": "cookiesession"},
+ 200,
+ {"ads_id": "ads_track"},
+ ),
+ ("/items", {"session": "cookiesession"}, 200, {"ads_id": None}),
+ ],
+)
+def test(path, cookies, expected_status, expected_response, client: TestClient):
+ response = client.get(path, cookies=cookies)
+ assert response.status_code == expected_status
+ assert response.json() == expected_response
diff --git a/tests/test_tutorial/test_custom_response/test_tutorial006.py b/tests/test_tutorial/test_custom_response/test_tutorial006.py
index 33a17ea4..72bbfd27 100644
--- a/tests/test_tutorial/test_custom_response/test_tutorial006.py
+++ b/tests/test_tutorial/test_custom_response/test_tutorial006.py
@@ -5,6 +5,32 @@ from docs_src.custom_response.tutorial006 import app
client = TestClient(app)
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/typer": {
+ "get": {
+ "summary": "Redirect Typer",
+ "operationId": "redirect_typer_typer_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ }
+ }
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
def test_get():
response = client.get("/typer", allow_redirects=False)
assert response.status_code == 307, response.text
diff --git a/tests/test_tutorial/test_custom_response/test_tutorial006b.py b/tests/test_tutorial/test_custom_response/test_tutorial006b.py
new file mode 100644
index 00000000..ac5a76d3
--- /dev/null
+++ b/tests/test_tutorial/test_custom_response/test_tutorial006b.py
@@ -0,0 +1,32 @@
+from fastapi.testclient import TestClient
+
+from docs_src.custom_response.tutorial006b import app
+
+client = TestClient(app)
+
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/fastapi": {
+ "get": {
+ "summary": "Redirect Fastapi",
+ "operationId": "redirect_fastapi_fastapi_get",
+ "responses": {"307": {"description": "Successful Response"}},
+ }
+ }
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+def test_redirect_response_class():
+ response = client.get("/fastapi", allow_redirects=False)
+ assert response.status_code == 307
+ assert response.headers["location"] == "https://fastapi.tiangolo.com"
diff --git a/tests/test_tutorial/test_custom_response/test_tutorial006c.py b/tests/test_tutorial/test_custom_response/test_tutorial006c.py
new file mode 100644
index 00000000..009225e8
--- /dev/null
+++ b/tests/test_tutorial/test_custom_response/test_tutorial006c.py
@@ -0,0 +1,32 @@
+from fastapi.testclient import TestClient
+
+from docs_src.custom_response.tutorial006c import app
+
+client = TestClient(app)
+
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/pydantic": {
+ "get": {
+ "summary": "Redirect Pydantic",
+ "operationId": "redirect_pydantic_pydantic_get",
+ "responses": {"302": {"description": "Successful Response"}},
+ }
+ }
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+def test_redirect_status_code():
+ response = client.get("/pydantic", allow_redirects=False)
+ assert response.status_code == 302
+ assert response.headers["location"] == "https://pydantic-docs.helpmanual.io/"
diff --git a/tests/test_tutorial/test_custom_response/test_tutorial009.py b/tests/test_tutorial/test_custom_response/test_tutorial009.py
new file mode 100644
index 00000000..ac20f89e
--- /dev/null
+++ b/tests/test_tutorial/test_custom_response/test_tutorial009.py
@@ -0,0 +1,17 @@
+from pathlib import Path
+
+from fastapi.testclient import TestClient
+
+from docs_src.custom_response import tutorial009
+from docs_src.custom_response.tutorial009 import app
+
+client = TestClient(app)
+
+
+def test_get(tmp_path: Path):
+ file_path: Path = tmp_path / "large-video-file.mp4"
+ tutorial009.some_file_path = str(file_path)
+ test_content = b"Fake video bytes"
+ file_path.write_bytes(test_content)
+ response = client.get("/")
+ assert response.content == test_content
diff --git a/tests/test_tutorial/test_custom_response/test_tutorial009b.py b/tests/test_tutorial/test_custom_response/test_tutorial009b.py
new file mode 100644
index 00000000..4f56e2f3
--- /dev/null
+++ b/tests/test_tutorial/test_custom_response/test_tutorial009b.py
@@ -0,0 +1,17 @@
+from pathlib import Path
+
+from fastapi.testclient import TestClient
+
+from docs_src.custom_response import tutorial009b
+from docs_src.custom_response.tutorial009b import app
+
+client = TestClient(app)
+
+
+def test_get(tmp_path: Path):
+ file_path: Path = tmp_path / "large-video-file.mp4"
+ tutorial009b.some_file_path = str(file_path)
+ test_content = b"Fake video bytes"
+ file_path.write_bytes(test_content)
+ response = client.get("/")
+ assert response.content == test_content
diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial001.py b/tests/test_tutorial/test_dataclasses/test_tutorial001.py
new file mode 100644
index 00000000..bf156419
--- /dev/null
+++ b/tests/test_tutorial/test_dataclasses/test_tutorial001.py
@@ -0,0 +1,113 @@
+from fastapi.testclient import TestClient
+
+from docs_src.dataclasses.tutorial001 import app
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "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": {
+ "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"},
+ "price": {"title": "Price", "type": "number"},
+ "description": {"title": "Description", "type": "string"},
+ "tax": {"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"},
+ },
+ },
+ }
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200
+ assert response.json() == openapi_schema
+
+
+def test_post_item():
+ response = client.post("/items/", json={"name": "Foo", "price": 3})
+ assert response.status_code == 200
+ assert response.json() == {
+ "name": "Foo",
+ "price": 3,
+ "description": None,
+ "tax": None,
+ }
+
+
+def test_post_invalid_item():
+ response = client.post("/items/", json={"name": "Foo", "price": "invalid price"})
+ assert response.status_code == 422
+ assert response.json() == {
+ "detail": [
+ {
+ "loc": ["body", "price"],
+ "msg": "value is not a valid float",
+ "type": "type_error.float",
+ }
+ ]
+ }
diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial002.py b/tests/test_tutorial/test_dataclasses/test_tutorial002.py
new file mode 100644
index 00000000..34aeb0be
--- /dev/null
+++ b/tests/test_tutorial/test_dataclasses/test_tutorial002.py
@@ -0,0 +1,79 @@
+from copy import deepcopy
+
+from fastapi.testclient import TestClient
+
+from docs_src.dataclasses.tutorial002 import app
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/next": {
+ "get": {
+ "summary": "Read Next Item",
+ "operationId": "read_next_item_items_next_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ }
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Item": {
+ "title": "Item",
+ "required": ["name", "price"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "price": {"title": "Price", "type": "number"},
+ "tags": {
+ "title": "Tags",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "description": {"title": "Description", "type": "string"},
+ "tax": {"title": "Tax", "type": "number"},
+ },
+ }
+ }
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200
+ # TODO: remove this once Pydantic 1.9 is released
+ # Ref: https://github.com/samuelcolvin/pydantic/pull/2557
+ data = response.json()
+ alternative_data1 = deepcopy(data)
+ alternative_data2 = deepcopy(data)
+ alternative_data1["components"]["schemas"]["Item"]["required"] = ["name", "price"]
+ alternative_data2["components"]["schemas"]["Item"]["required"] = [
+ "name",
+ "price",
+ "tags",
+ ]
+ assert alternative_data1 == openapi_schema or alternative_data2 == openapi_schema
+
+
+def test_get_item():
+ response = client.get("/items/next")
+ assert response.status_code == 200
+ assert response.json() == {
+ "name": "Island In The Moon",
+ "price": 12.99,
+ "description": "A place to be be playin' and havin' fun",
+ "tags": ["breater"],
+ "tax": None,
+ }
diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial003.py b/tests/test_tutorial/test_dataclasses/test_tutorial003.py
new file mode 100644
index 00000000..2d86f7b9
--- /dev/null
+++ b/tests/test_tutorial/test_dataclasses/test_tutorial003.py
@@ -0,0 +1,181 @@
+from fastapi.testclient import TestClient
+
+from docs_src.dataclasses.tutorial003 import app
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/authors/{author_id}/items/": {
+ "post": {
+ "summary": "Create Author Items",
+ "operationId": "create_author_items_authors__author_id__items__post",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "Author Id", "type": "string"},
+ "name": "author_id",
+ "in": "path",
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Items",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Author"}
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ },
+ "/authors/": {
+ "get": {
+ "summary": "Get Authors",
+ "operationId": "get_authors_authors__get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Get Authors Authors Get",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Author"},
+ }
+ }
+ },
+ }
+ },
+ }
+ },
+ },
+ "components": {
+ "schemas": {
+ "Author": {
+ "title": "Author",
+ "required": ["name"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "items": {
+ "title": "Items",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ },
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ "Item": {
+ "title": "Item",
+ "required": ["name"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "description": {"title": "Description", "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"},
+ },
+ },
+ }
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200
+ assert response.json() == openapi_schema
+
+
+def test_post_authors_item():
+ response = client.post(
+ "/authors/foo/items/",
+ json=[{"name": "Bar"}, {"name": "Baz", "description": "Drop the Baz"}],
+ )
+ assert response.status_code == 200
+ assert response.json() == {
+ "name": "foo",
+ "items": [
+ {"name": "Bar", "description": None},
+ {"name": "Baz", "description": "Drop the Baz"},
+ ],
+ }
+
+
+def test_get_authors():
+ response = client.get("/authors/")
+ assert response.status_code == 200
+ assert response.json() == [
+ {
+ "name": "Breaters",
+ "items": [
+ {
+ "name": "Island In The Moon",
+ "description": "A place to be be playin' and havin' fun",
+ },
+ {"name": "Holy Buddies", "description": None},
+ ],
+ },
+ {
+ "name": "System of an Up",
+ "items": [
+ {
+ "name": "Salt",
+ "description": "The kombucha mushroom people's favorite",
+ },
+ {"name": "Pad Thai", "description": None},
+ {
+ "name": "Lonely Night",
+ "description": "The mostests lonliest nightiest of allest",
+ },
+ ],
+ },
+ ]
diff --git a/tests/test_tutorial/test_dependencies/test_tutorial001.py b/tests/test_tutorial/test_dependencies/test_tutorial001.py
index 8b53157c..c3bca5d5 100644
--- a/tests/test_tutorial/test_dependencies/test_tutorial001.py
+++ b/tests/test_tutorial/test_dependencies/test_tutorial001.py
@@ -104,7 +104,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_dependencies/test_tutorial001_py310.py b/tests/test_tutorial/test_dependencies/test_tutorial001_py310.py
new file mode 100644
index 00000000..32a61c82
--- /dev/null
+++ b/tests/test_tutorial/test_dependencies/test_tutorial001_py310.py
@@ -0,0 +1,157 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ "parameters": [
+ {
+ "required": False,
+ "schema": {"title": "Q", "type": "string"},
+ "name": "q",
+ "in": "query",
+ },
+ {
+ "required": False,
+ "schema": {"title": "Skip", "type": "integer", "default": 0},
+ "name": "skip",
+ "in": "query",
+ },
+ {
+ "required": False,
+ "schema": {"title": "Limit", "type": "integer", "default": 100},
+ "name": "limit",
+ "in": "query",
+ },
+ ],
+ }
+ },
+ "/users/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Users",
+ "operationId": "read_users_users__get",
+ "parameters": [
+ {
+ "required": False,
+ "schema": {"title": "Q", "type": "string"},
+ "name": "q",
+ "in": "query",
+ },
+ {
+ "required": False,
+ "schema": {"title": "Skip", "type": "integer", "default": 0},
+ "name": "skip",
+ "in": "query",
+ },
+ {
+ "required": False,
+ "schema": {"title": "Limit", "type": "integer", "default": 100},
+ "name": "limit",
+ "in": "query",
+ },
+ ],
+ }
+ },
+ },
+ "components": {
+ "schemas": {
+ "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"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.dependencies.tutorial001_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+@needs_py310
+@pytest.mark.parametrize(
+ "path,expected_status,expected_response",
+ [
+ ("/items", 200, {"q": None, "skip": 0, "limit": 100}),
+ ("/items?q=foo", 200, {"q": "foo", "skip": 0, "limit": 100}),
+ ("/items?q=foo&skip=5", 200, {"q": "foo", "skip": 5, "limit": 100}),
+ ("/items?q=foo&skip=5&limit=30", 200, {"q": "foo", "skip": 5, "limit": 30}),
+ ("/users", 200, {"q": None, "skip": 0, "limit": 100}),
+ ("/openapi.json", 200, openapi_schema),
+ ],
+)
+def test_get(path, expected_status, expected_response, client: TestClient):
+ response = client.get(path)
+ assert response.status_code == expected_status
+ assert response.json() == expected_response
diff --git a/tests/test_tutorial/test_dependencies/test_tutorial004.py b/tests/test_tutorial/test_dependencies/test_tutorial004.py
index eb21f652..f2b1878d 100644
--- a/tests/test_tutorial/test_dependencies/test_tutorial004.py
+++ b/tests/test_tutorial/test_dependencies/test_tutorial004.py
@@ -62,7 +62,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_dependencies/test_tutorial004_py310.py b/tests/test_tutorial/test_dependencies/test_tutorial004_py310.py
new file mode 100644
index 00000000..e3ae0c74
--- /dev/null
+++ b/tests/test_tutorial/test_dependencies/test_tutorial004_py310.py
@@ -0,0 +1,152 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ "parameters": [
+ {
+ "required": False,
+ "schema": {"title": "Q", "type": "string"},
+ "name": "q",
+ "in": "query",
+ },
+ {
+ "required": False,
+ "schema": {"title": "Skip", "type": "integer", "default": 0},
+ "name": "skip",
+ "in": "query",
+ },
+ {
+ "required": False,
+ "schema": {"title": "Limit", "type": "integer", "default": 100},
+ "name": "limit",
+ "in": "query",
+ },
+ ],
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "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"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.dependencies.tutorial004_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+@needs_py310
+@pytest.mark.parametrize(
+ "path,expected_status,expected_response",
+ [
+ (
+ "/items",
+ 200,
+ {
+ "items": [
+ {"item_name": "Foo"},
+ {"item_name": "Bar"},
+ {"item_name": "Baz"},
+ ]
+ },
+ ),
+ (
+ "/items?q=foo",
+ 200,
+ {
+ "items": [
+ {"item_name": "Foo"},
+ {"item_name": "Bar"},
+ {"item_name": "Baz"},
+ ],
+ "q": "foo",
+ },
+ ),
+ (
+ "/items?q=foo&skip=1",
+ 200,
+ {"items": [{"item_name": "Bar"}, {"item_name": "Baz"}], "q": "foo"},
+ ),
+ (
+ "/items?q=bar&limit=2",
+ 200,
+ {"items": [{"item_name": "Foo"}, {"item_name": "Bar"}], "q": "bar"},
+ ),
+ (
+ "/items?q=bar&skip=1&limit=1",
+ 200,
+ {"items": [{"item_name": "Bar"}], "q": "bar"},
+ ),
+ (
+ "/items?limit=1&q=bar&skip=1",
+ 200,
+ {"items": [{"item_name": "Bar"}], "q": "bar"},
+ ),
+ ],
+)
+def test_get(path, expected_status, expected_response, client: TestClient):
+ response = client.get(path)
+ assert response.status_code == expected_status
+ assert response.json() == expected_response
diff --git a/tests/test_tutorial/test_dependencies/test_tutorial006.py b/tests/test_tutorial/test_dependencies/test_tutorial006.py
index c08992ec..2916577a 100644
--- a/tests/test_tutorial/test_dependencies/test_tutorial006.py
+++ b/tests/test_tutorial/test_dependencies/test_tutorial006.py
@@ -55,7 +55,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_dependencies/test_tutorial012.py b/tests/test_tutorial/test_dependencies/test_tutorial012.py
index ada83c62..e4e07395 100644
--- a/tests/test_tutorial/test_dependencies/test_tutorial012.py
+++ b/tests/test_tutorial/test_dependencies/test_tutorial012.py
@@ -102,7 +102,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_events/test_tutorial001.py b/tests/test_tutorial/test_events/test_tutorial001.py
index e3587a0e..d52dd1a0 100644
--- a/tests/test_tutorial/test_events/test_tutorial001.py
+++ b/tests/test_tutorial/test_events/test_tutorial001.py
@@ -47,7 +47,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_extending_openapi/test_tutorial003.py b/tests/test_tutorial/test_extending_openapi/test_tutorial003.py
new file mode 100644
index 00000000..0184dd9f
--- /dev/null
+++ b/tests/test_tutorial/test_extending_openapi/test_tutorial003.py
@@ -0,0 +1,41 @@
+from fastapi.testclient import TestClient
+
+from docs_src.extending_openapi.tutorial003 import app
+
+client = TestClient(app)
+
+
+def test_swagger_ui():
+ response = client.get("/docs")
+ assert response.status_code == 200, response.text
+ assert (
+ '"syntaxHighlight": false' in response.text
+ ), "syntaxHighlight should be included and converted to JSON"
+ assert (
+ '"dom_id": "#swagger-ui"' in response.text
+ ), "default configs should be preserved"
+ assert "presets: [" in response.text, "default configs should be preserved"
+ assert (
+ "SwaggerUIBundle.presets.apis," in response.text
+ ), "default configs should be preserved"
+ assert (
+ "SwaggerUIBundle.SwaggerUIStandalonePreset" in response.text
+ ), "default configs should be preserved"
+ assert (
+ '"layout": "BaseLayout",' in response.text
+ ), "default configs should be preserved"
+ assert (
+ '"deepLinking": true,' in response.text
+ ), "default configs should be preserved"
+ assert (
+ '"showExtensions": true,' in response.text
+ ), "default configs should be preserved"
+ assert (
+ '"showCommonExtensions": true,' in response.text
+ ), "default configs should be preserved"
+
+
+def test_get_users():
+ response = client.get("/users/foo")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"message": "Hello foo"}
diff --git a/tests/test_tutorial/test_extending_openapi/test_tutorial004.py b/tests/test_tutorial/test_extending_openapi/test_tutorial004.py
new file mode 100644
index 00000000..4f761512
--- /dev/null
+++ b/tests/test_tutorial/test_extending_openapi/test_tutorial004.py
@@ -0,0 +1,44 @@
+from fastapi.testclient import TestClient
+
+from docs_src.extending_openapi.tutorial004 import app
+
+client = TestClient(app)
+
+
+def test_swagger_ui():
+ response = client.get("/docs")
+ assert response.status_code == 200, response.text
+ assert (
+ '"syntaxHighlight": false' not in response.text
+ ), "not used parameters should not be included"
+ assert (
+ '"syntaxHighlight.theme": "obsidian"' in response.text
+ ), "parameters with middle dots should be included in a JSON compatible way"
+ assert (
+ '"dom_id": "#swagger-ui"' in response.text
+ ), "default configs should be preserved"
+ assert "presets: [" in response.text, "default configs should be preserved"
+ assert (
+ "SwaggerUIBundle.presets.apis," in response.text
+ ), "default configs should be preserved"
+ assert (
+ "SwaggerUIBundle.SwaggerUIStandalonePreset" in response.text
+ ), "default configs should be preserved"
+ assert (
+ '"layout": "BaseLayout",' in response.text
+ ), "default configs should be preserved"
+ assert (
+ '"deepLinking": true,' in response.text
+ ), "default configs should be preserved"
+ assert (
+ '"showExtensions": true,' in response.text
+ ), "default configs should be preserved"
+ assert (
+ '"showCommonExtensions": true,' in response.text
+ ), "default configs should be preserved"
+
+
+def test_get_users():
+ response = client.get("/users/foo")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"message": "Hello foo"}
diff --git a/tests/test_tutorial/test_extending_openapi/test_tutorial005.py b/tests/test_tutorial/test_extending_openapi/test_tutorial005.py
new file mode 100644
index 00000000..24aeb93d
--- /dev/null
+++ b/tests/test_tutorial/test_extending_openapi/test_tutorial005.py
@@ -0,0 +1,44 @@
+from fastapi.testclient import TestClient
+
+from docs_src.extending_openapi.tutorial005 import app
+
+client = TestClient(app)
+
+
+def test_swagger_ui():
+ response = client.get("/docs")
+ assert response.status_code == 200, response.text
+ assert (
+ '"deepLinking": false,' in response.text
+ ), "overridden configs should be preserved"
+ assert (
+ '"deepLinking": true' not in response.text
+ ), "overridden configs should not include the old value"
+ assert (
+ '"syntaxHighlight": false' not in response.text
+ ), "not used parameters should not be included"
+ assert (
+ '"dom_id": "#swagger-ui"' in response.text
+ ), "default configs should be preserved"
+ assert "presets: [" in response.text, "default configs should be preserved"
+ assert (
+ "SwaggerUIBundle.presets.apis," in response.text
+ ), "default configs should be preserved"
+ assert (
+ "SwaggerUIBundle.SwaggerUIStandalonePreset" in response.text
+ ), "default configs should be preserved"
+ assert (
+ '"layout": "BaseLayout",' in response.text
+ ), "default configs should be preserved"
+ assert (
+ '"showExtensions": true,' in response.text
+ ), "default configs should be preserved"
+ assert (
+ '"showCommonExtensions": true,' in response.text
+ ), "default configs should be preserved"
+
+
+def test_get_users():
+ response = client.get("/users/foo")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"message": "Hello foo"}
diff --git a/tests/test_tutorial/test_extra_data_types/test_tutorial001.py b/tests/test_tutorial/test_extra_data_types/test_tutorial001.py
index 68b7d61d..8522d7b9 100644
--- a/tests/test_tutorial/test_extra_data_types/test_tutorial001.py
+++ b/tests/test_tutorial/test_extra_data_types/test_tutorial001.py
@@ -89,7 +89,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_extra_data_types/test_tutorial001_py310.py b/tests/test_tutorial/test_extra_data_types/test_tutorial001_py310.py
new file mode 100644
index 00000000..4efdecc5
--- /dev/null
+++ b/tests/test_tutorial/test_extra_data_types/test_tutorial001_py310.py
@@ -0,0 +1,146 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "put": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Items",
+ "operationId": "read_items_items__item_id__put",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {
+ "title": "Item Id",
+ "type": "string",
+ "format": "uuid",
+ },
+ "name": "item_id",
+ "in": "path",
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Body_read_items_items__item_id__put"
+ }
+ }
+ }
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Body_read_items_items__item_id__put": {
+ "title": "Body_read_items_items__item_id__put",
+ "type": "object",
+ "properties": {
+ "start_datetime": {
+ "title": "Start Datetime",
+ "type": "string",
+ "format": "date-time",
+ },
+ "end_datetime": {
+ "title": "End Datetime",
+ "type": "string",
+ "format": "date-time",
+ },
+ "repeat_at": {
+ "title": "Repeat At",
+ "type": "string",
+ "format": "time",
+ },
+ "process_after": {
+ "title": "Process After",
+ "type": "number",
+ "format": "time-delta",
+ },
+ },
+ },
+ "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"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.extra_data_types.tutorial001_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+@needs_py310
+def test_extra_types(client: TestClient):
+ item_id = "ff97dd87-a4a5-4a12-b412-cde99f33e00e"
+ data = {
+ "start_datetime": "2018-12-22T14:00:00+00:00",
+ "end_datetime": "2018-12-24T15:00:00+00:00",
+ "repeat_at": "15:30:00",
+ "process_after": 300,
+ }
+ expected_response = data.copy()
+ expected_response.update(
+ {
+ "start_process": "2018-12-22T14:05:00+00:00",
+ "duration": 176_100,
+ "item_id": item_id,
+ }
+ )
+ response = client.put(f"/items/{item_id}", json=data)
+ assert response.status_code == 200, response.text
+ assert response.json() == expected_response
diff --git a/tests/test_tutorial/test_extra_models/test_tutorial003.py b/tests/test_tutorial/test_extra_models/test_tutorial003.py
index a2a325c7..f1433470 100644
--- a/tests/test_tutorial/test_extra_models/test_tutorial003.py
+++ b/tests/test_tutorial/test_extra_models/test_tutorial003.py
@@ -78,7 +78,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_extra_models/test_tutorial003_py310.py b/tests/test_tutorial/test_extra_models/test_tutorial003_py310.py
new file mode 100644
index 00000000..56fd83ad
--- /dev/null
+++ b/tests/test_tutorial/test_extra_models/test_tutorial003_py310.py
@@ -0,0 +1,135 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Read Item Items Item Id Get",
+ "anyOf": [
+ {"$ref": "#/components/schemas/PlaneItem"},
+ {"$ref": "#/components/schemas/CarItem"},
+ ],
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Item",
+ "operationId": "read_item_items__item_id__get",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "Item Id", "type": "string"},
+ "name": "item_id",
+ "in": "path",
+ }
+ ],
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "PlaneItem": {
+ "title": "PlaneItem",
+ "required": ["description", "size"],
+ "type": "object",
+ "properties": {
+ "description": {"title": "Description", "type": "string"},
+ "type": {"title": "Type", "type": "string", "default": "plane"},
+ "size": {"title": "Size", "type": "integer"},
+ },
+ },
+ "CarItem": {
+ "title": "CarItem",
+ "required": ["description"],
+ "type": "object",
+ "properties": {
+ "description": {"title": "Description", "type": "string"},
+ "type": {"title": "Type", "type": "string", "default": "car"},
+ },
+ },
+ "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"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.extra_models.tutorial003_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+@needs_py310
+def test_get_car(client: TestClient):
+ response = client.get("/items/item1")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "description": "All my friends drive a low rider",
+ "type": "car",
+ }
+
+
+@needs_py310
+def test_get_plane(client: TestClient):
+ response = client.get("/items/item2")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "description": "Music is my aeroplane, it's my aeroplane",
+ "type": "plane",
+ "size": 5,
+ }
diff --git a/tests/test_tutorial/test_extra_models/test_tutorial004_py39.py b/tests/test_tutorial/test_extra_models/test_tutorial004_py39.py
new file mode 100644
index 00000000..7f4f5b9b
--- /dev/null
+++ b/tests/test_tutorial/test_extra_models/test_tutorial004_py39.py
@@ -0,0 +1,69 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py39
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Read Items Items Get",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ }
+ },
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Item": {
+ "title": "Item",
+ "required": ["name", "description"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "description": {"title": "Description", "type": "string"},
+ },
+ }
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.extra_models.tutorial004_py39 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py39
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+@needs_py39
+def test_get_items(client: TestClient):
+ response = client.get("/items/")
+ assert response.status_code == 200, response.text
+ assert response.json() == [
+ {"name": "Foo", "description": "There comes my hero"},
+ {"name": "Red", "description": "It's my aeroplane"},
+ ]
diff --git a/tests/test_tutorial/test_extra_models/test_tutorial005_py39.py b/tests/test_tutorial/test_extra_models/test_tutorial005_py39.py
new file mode 100644
index 00000000..3bb5a99f
--- /dev/null
+++ b/tests/test_tutorial/test_extra_models/test_tutorial005_py39.py
@@ -0,0 +1,53 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py39
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/keyword-weights/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Read Keyword Weights Keyword Weights Get",
+ "type": "object",
+ "additionalProperties": {"type": "number"},
+ }
+ }
+ },
+ }
+ },
+ "summary": "Read Keyword Weights",
+ "operationId": "read_keyword_weights_keyword_weights__get",
+ }
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.extra_models.tutorial005_py39 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py39
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+@needs_py39
+def test_get_items(client: TestClient):
+ response = client.get("/keyword-weights/")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"foo": 2.3, "bar": 3.4}
diff --git a/tests/test_tutorial/test_generate_clients/__init__.py b/tests/test_tutorial/test_generate_clients/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/test_tutorial/test_generate_clients/test_tutorial003.py b/tests/test_tutorial/test_generate_clients/test_tutorial003.py
new file mode 100644
index 00000000..128fcea3
--- /dev/null
+++ b/tests/test_tutorial/test_generate_clients/test_tutorial003.py
@@ -0,0 +1,188 @@
+from fastapi.testclient import TestClient
+
+from docs_src.generate_clients.tutorial003 import app
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "tags": ["items"],
+ "summary": "Get Items",
+ "operationId": "items-get_items",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "title": "Response Items-Get Items",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Item"},
+ }
+ }
+ },
+ }
+ },
+ },
+ "post": {
+ "tags": ["items"],
+ "summary": "Create Item",
+ "operationId": "items-create_item",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ResponseMessage"
+ }
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ },
+ },
+ "/users/": {
+ "post": {
+ "tags": ["users"],
+ "summary": "Create User",
+ "operationId": "users-create_user",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/User"}
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ResponseMessage"
+ }
+ }
+ },
+ },
+ "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"},
+ "price": {"title": "Price", "type": "number"},
+ },
+ },
+ "ResponseMessage": {
+ "title": "ResponseMessage",
+ "required": ["message"],
+ "type": "object",
+ "properties": {"message": {"title": "Message", "type": "string"}},
+ },
+ "User": {
+ "title": "User",
+ "required": ["username", "email"],
+ "type": "object",
+ "properties": {
+ "username": {"title": "Username", "type": "string"},
+ "email": {"title": "Email", "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"},
+ },
+ },
+ }
+ },
+}
+
+
+def test_openapi():
+ with client:
+ response = client.get("/openapi.json")
+
+ assert response.json() == openapi_schema
+
+
+def test_post_items():
+ response = client.post("/items/", json={"name": "Foo", "price": 5})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"message": "Item received"}
+
+
+def test_post_users():
+ response = client.post(
+ "/users/", json={"username": "Foo", "email": "foo@example.com"}
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {"message": "User received"}
+
+
+def test_get_items():
+ response = client.get("/items/")
+ assert response.status_code == 200, response.text
+ assert response.json() == [
+ {"name": "Plumbus", "price": 3},
+ {"name": "Portal Gun", "price": 9001},
+ ]
diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial001.py b/tests/test_tutorial/test_handling_errors/test_tutorial001.py
index 6b62293d..ffd79ccf 100644
--- a/tests/test_tutorial/test_handling_errors/test_tutorial001.py
+++ b/tests/test_tutorial/test_handling_errors/test_tutorial001.py
@@ -49,7 +49,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial002.py b/tests/test_tutorial/test_handling_errors/test_tutorial002.py
index d2ce0bf9..e678499c 100644
--- a/tests/test_tutorial/test_handling_errors/test_tutorial002.py
+++ b/tests/test_tutorial/test_handling_errors/test_tutorial002.py
@@ -49,7 +49,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial003.py b/tests/test_tutorial/test_handling_errors/test_tutorial003.py
index ca9d94e3..a01726dc 100644
--- a/tests/test_tutorial/test_handling_errors/test_tutorial003.py
+++ b/tests/test_tutorial/test_handling_errors/test_tutorial003.py
@@ -49,7 +49,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial004.py b/tests/test_tutorial/test_handling_errors/test_tutorial004.py
index d95debf3..0b5f7479 100644
--- a/tests/test_tutorial/test_handling_errors/test_tutorial004.py
+++ b/tests/test_tutorial/test_handling_errors/test_tutorial004.py
@@ -49,7 +49,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial005.py b/tests/test_tutorial/test_handling_errors/test_tutorial005.py
index cedcaae7..253f3d00 100644
--- a/tests/test_tutorial/test_handling_errors/test_tutorial005.py
+++ b/tests/test_tutorial/test_handling_errors/test_tutorial005.py
@@ -69,7 +69,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial006.py b/tests/test_tutorial/test_handling_errors/test_tutorial006.py
index 8b6c1e7e..21233d7b 100644
--- a/tests/test_tutorial/test_handling_errors/test_tutorial006.py
+++ b/tests/test_tutorial/test_handling_errors/test_tutorial006.py
@@ -49,7 +49,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_header_params/test_tutorial001.py b/tests/test_tutorial/test_header_params/test_tutorial001.py
index 0f05b9e8..273cf324 100644
--- a/tests/test_tutorial/test_header_params/test_tutorial001.py
+++ b/tests/test_tutorial/test_header_params/test_tutorial001.py
@@ -51,7 +51,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_header_params/test_tutorial001_py310.py b/tests/test_tutorial/test_header_params/test_tutorial001_py310.py
new file mode 100644
index 00000000..77a60eb9
--- /dev/null
+++ b/tests/test_tutorial/test_header_params/test_tutorial001_py310.py
@@ -0,0 +1,94 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ "parameters": [
+ {
+ "required": False,
+ "schema": {"title": "User-Agent", "type": "string"},
+ "name": "user-agent",
+ "in": "header",
+ }
+ ],
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "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"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.header_params.tutorial001_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+@pytest.mark.parametrize(
+ "path,headers,expected_status,expected_response",
+ [
+ ("/openapi.json", None, 200, openapi_schema),
+ ("/items", None, 200, {"User-Agent": "testclient"}),
+ ("/items", {"X-Header": "notvalid"}, 200, {"User-Agent": "testclient"}),
+ ("/items", {"User-Agent": "FastAPI test"}, 200, {"User-Agent": "FastAPI test"}),
+ ],
+)
+def test(path, headers, expected_status, expected_response, client: TestClient):
+ response = client.get(path, headers=headers)
+ assert response.status_code == expected_status
+ assert response.json() == expected_response
diff --git a/tests/test_tutorial/test_metadata/test_tutorial001.py b/tests/test_tutorial/test_metadata/test_tutorial001.py
index aae79dcc..b7281e29 100644
--- a/tests/test_tutorial/test_metadata/test_tutorial001.py
+++ b/tests/test_tutorial/test_metadata/test_tutorial001.py
@@ -7,21 +7,31 @@ client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {
- "title": "My Super Project",
- "version": "2.5.0",
- "description": "This is a very fancy project, with auto docs for the API and everything",
+ "title": "ChimichangApp",
+ "description": "\nChimichangApp API helps you do awesome stuff. 🚀\n\n## Items\n\nYou can **read items**.\n\n## Users\n\nYou will be able to:\n\n* **Create users** (_not implemented_).\n* **Read users** (_not implemented_).\n",
+ "termsOfService": "http://example.com/terms/",
+ "contact": {
+ "name": "Deadpoolio the Amazing",
+ "url": "http://x-force.example.com/contact/",
+ "email": "dp@x-force.example.com",
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "https://www.apache.org/licenses/LICENSE-2.0.html",
+ },
+ "version": "0.0.1",
},
"paths": {
"/items/": {
"get": {
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
}
},
- "summary": "Read Items",
- "operationId": "read_items_items__get",
}
}
},
@@ -37,4 +47,4 @@ def test_openapi_schema():
def test_items():
response = client.get("/items/")
assert response.status_code == 200, response.text
- assert response.json() == [{"name": "Foo"}]
+ assert response.json() == [{"name": "Katana"}]
diff --git a/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py b/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py
index b30427d0..e773e7f8 100644
--- a/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py
+++ b/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py
@@ -143,7 +143,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py
index f2ec2c7e..456e509d 100644
--- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py
+++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py
@@ -72,7 +72,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial005.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial005.py
new file mode 100644
index 00000000..5042d183
--- /dev/null
+++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial005.py
@@ -0,0 +1,36 @@
+from fastapi.testclient import TestClient
+
+from docs_src.path_operation_advanced_configuration.tutorial005 import app
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "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",
+ "x-aperture-labs-portal": "blue",
+ }
+ }
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+def test_get():
+ response = client.get("/items/")
+ assert response.status_code == 200, response.text
diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial006.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial006.py
new file mode 100644
index 00000000..5533b295
--- /dev/null
+++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial006.py
@@ -0,0 +1,59 @@
+from fastapi.testclient import TestClient
+
+from docs_src.path_operation_advanced_configuration.tutorial006 import app
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "post": {
+ "summary": "Create Item",
+ "operationId": "create_item_items__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "required": ["name", "price"],
+ "type": "object",
+ "properties": {
+ "name": {"type": "string"},
+ "price": {"type": "number"},
+ "description": {"type": "string"},
+ },
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ }
+ }
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+def test_post():
+ response = client.post("/items/", data=b"this is actually not validated")
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "size": 30,
+ "content": {
+ "name": "Maaaagic",
+ "price": 42,
+ "description": "Just kiddin', no magic here. ✨",
+ },
+ }
diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py
new file mode 100644
index 00000000..cb5dbc8e
--- /dev/null
+++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py
@@ -0,0 +1,97 @@
+from fastapi.testclient import TestClient
+
+from docs_src.path_operation_advanced_configuration.tutorial007 import app
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "post": {
+ "summary": "Create Item",
+ "operationId": "create_item_items__post",
+ "requestBody": {
+ "content": {
+ "application/x-yaml": {
+ "schema": {
+ "title": "Item",
+ "required": ["name", "tags"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "tags": {
+ "title": "Tags",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ },
+ }
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ }
+ }
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+def test_post():
+ yaml_data = """
+ name: Deadpoolio
+ tags:
+ - x-force
+ - x-men
+ - x-avengers
+ """
+ response = client.post("/items/", data=yaml_data)
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "name": "Deadpoolio",
+ "tags": ["x-force", "x-men", "x-avengers"],
+ }
+
+
+def test_post_broken_yaml():
+ yaml_data = """
+ name: Deadpoolio
+ tags:
+ x - x-force
+ x - x-men
+ x - x-avengers
+ """
+ response = client.post("/items/", data=yaml_data)
+ assert response.status_code == 422, response.text
+ assert response.json() == {"detail": "Invalid YAML"}
+
+
+def test_post_invalid():
+ yaml_data = """
+ name: Deadpoolio
+ tags:
+ - x-force
+ - x-men
+ - x-avengers
+ - sneaky: object
+ """
+ response = client.post("/items/", data=yaml_data)
+ assert response.status_code == 422, response.text
+ assert response.json() == {
+ "detail": [
+ {"loc": ["tags", 3], "msg": "str type expected", "type": "type_error.str"}
+ ]
+ }
diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial002b.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial002b.py
new file mode 100644
index 00000000..be9f2afe
--- /dev/null
+++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial002b.py
@@ -0,0 +1,56 @@
+from fastapi.testclient import TestClient
+
+from docs_src.path_operation_configuration.tutorial002b import app
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "tags": ["items"],
+ "summary": "Get Items",
+ "operationId": "get_items_items__get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ }
+ },
+ "/users/": {
+ "get": {
+ "tags": ["users"],
+ "summary": "Read Users",
+ "operationId": "read_users_users__get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ }
+ },
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+def test_get_items():
+ response = client.get("/items/")
+ assert response.status_code == 200, response.text
+ assert response.json() == ["Portal gun", "Plumbus"]
+
+
+def test_get_users():
+ response = client.get("/users/")
+ assert response.status_code == 200, response.text
+ assert response.json() == ["Rick", "Morty"]
diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py
index d2164094..e587519a 100644
--- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py
+++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py
@@ -72,7 +72,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py310.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py310.py
new file mode 100644
index 00000000..43a7a610
--- /dev/null
+++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py310.py
@@ -0,0 +1,121 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "post": {
+ "responses": {
+ "200": {
+ "description": "The created item",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Create an item",
+ "description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item",
+ "operationId": "create_item_items__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ "required": True,
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Item": {
+ "title": "Item",
+ "required": ["name", "price"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "price": {"title": "Price", "type": "number"},
+ "description": {"title": "Description", "type": "string"},
+ "tax": {"title": "Tax", "type": "number"},
+ "tags": {
+ "title": "Tags",
+ "uniqueItems": True,
+ "type": "array",
+ "items": {"type": "string"},
+ "default": [],
+ },
+ },
+ },
+ "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"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.path_operation_configuration.tutorial005_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+@needs_py310
+def test_query_params_str_validations(client: TestClient):
+ response = client.post("/items/", json={"name": "Foo", "price": 42})
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "name": "Foo",
+ "price": 42,
+ "description": None,
+ "tax": None,
+ "tags": [],
+ }
diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py39.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py39.py
new file mode 100644
index 00000000..62aa73ac
--- /dev/null
+++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py39.py
@@ -0,0 +1,121 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py39
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "post": {
+ "responses": {
+ "200": {
+ "description": "The created item",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Create an item",
+ "description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item",
+ "operationId": "create_item_items__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ "required": True,
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Item": {
+ "title": "Item",
+ "required": ["name", "price"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "price": {"title": "Price", "type": "number"},
+ "description": {"title": "Description", "type": "string"},
+ "tax": {"title": "Tax", "type": "number"},
+ "tags": {
+ "title": "Tags",
+ "uniqueItems": True,
+ "type": "array",
+ "items": {"type": "string"},
+ "default": [],
+ },
+ },
+ },
+ "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"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.path_operation_configuration.tutorial005_py39 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py39
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+@needs_py39
+def test_query_params_str_validations(client: TestClient):
+ response = client.post("/items/", json={"name": "Foo", "price": 42})
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "name": "Foo",
+ "price": 42,
+ "description": None,
+ "tax": None,
+ "tags": [],
+ }
diff --git a/tests/test_tutorial/test_path_params/test_tutorial004.py b/tests/test_tutorial/test_path_params/test_tutorial004.py
index 131bf773..7f0227ec 100644
--- a/tests/test_tutorial/test_path_params/test_tutorial004.py
+++ b/tests/test_tutorial/test_path_params/test_tutorial004.py
@@ -49,7 +49,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_path_params/test_tutorial005.py b/tests/test_tutorial/test_path_params/test_tutorial005.py
index ed9d2032..eae3637b 100644
--- a/tests/test_tutorial/test_path_params/test_tutorial005.py
+++ b/tests/test_tutorial/test_path_params/test_tutorial005.py
@@ -54,7 +54,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
@@ -138,7 +138,7 @@ openapi_schema2 = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_query_params/test_tutorial005.py b/tests/test_tutorial/test_query_params/test_tutorial005.py
index aabc0af4..07178f8a 100644
--- a/tests/test_tutorial/test_query_params/test_tutorial005.py
+++ b/tests/test_tutorial/test_query_params/test_tutorial005.py
@@ -56,7 +56,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_query_params/test_tutorial006.py b/tests/test_tutorial/test_query_params/test_tutorial006.py
index 042a0e1f..73c5302e 100644
--- a/tests/test_tutorial/test_query_params/test_tutorial006.py
+++ b/tests/test_tutorial/test_query_params/test_tutorial006.py
@@ -68,7 +68,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_query_params/test_tutorial006_py310.py b/tests/test_tutorial/test_query_params/test_tutorial006_py310.py
new file mode 100644
index 00000000..141525f1
--- /dev/null
+++ b/tests/test_tutorial/test_query_params/test_tutorial006_py310.py
@@ -0,0 +1,148 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read User Item",
+ "operationId": "read_user_item_items__item_id__get",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "Item Id", "type": "string"},
+ "name": "item_id",
+ "in": "path",
+ },
+ {
+ "required": True,
+ "schema": {"title": "Needy", "type": "string"},
+ "name": "needy",
+ "in": "query",
+ },
+ {
+ "required": False,
+ "schema": {"title": "Skip", "type": "integer", "default": 0},
+ "name": "skip",
+ "in": "query",
+ },
+ {
+ "required": False,
+ "schema": {"title": "Limit", "type": "integer"},
+ "name": "limit",
+ "in": "query",
+ },
+ ],
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "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"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+query_required = {
+ "detail": [
+ {
+ "loc": ["query", "needy"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ }
+ ]
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.query_params.tutorial006_py310 import app
+
+ c = TestClient(app)
+ return c
+
+
+@needs_py310
+@pytest.mark.parametrize(
+ "path,expected_status,expected_response",
+ [
+ ("/openapi.json", 200, openapi_schema),
+ (
+ "/items/foo?needy=very",
+ 200,
+ {"item_id": "foo", "needy": "very", "skip": 0, "limit": None},
+ ),
+ (
+ "/items/foo?skip=a&limit=b",
+ 422,
+ {
+ "detail": [
+ {
+ "loc": ["query", "needy"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ {
+ "loc": ["query", "skip"],
+ "msg": "value is not a valid integer",
+ "type": "type_error.integer",
+ },
+ {
+ "loc": ["query", "limit"],
+ "msg": "value is not a valid integer",
+ "type": "type_error.integer",
+ },
+ ]
+ },
+ ),
+ ],
+)
+def test(path, expected_status, expected_response, client: TestClient):
+ response = client.get(path)
+ assert response.status_code == expected_status
+ assert response.json() == expected_response
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial001.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial001.py
index 709bf695..f8d7f85c 100644
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial001.py
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial001.py
@@ -59,7 +59,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial001_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial001_py310.py
new file mode 100644
index 00000000..298b5d61
--- /dev/null
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial001_py310.py
@@ -0,0 +1,132 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ "parameters": [
+ {
+ "description": "Query string for the items to search in the database that have a good match",
+ "required": False,
+ "deprecated": True,
+ "schema": {
+ "title": "Query string",
+ "maxLength": 50,
+ "minLength": 3,
+ "pattern": "^fixedquery$",
+ "type": "string",
+ "description": "Query string for the items to search in the database that have a good match",
+ },
+ "name": "item-query",
+ "in": "query",
+ }
+ ],
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "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"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.query_params_str_validations.tutorial010_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+regex_error = {
+ "detail": [
+ {
+ "ctx": {"pattern": "^fixedquery$"},
+ "loc": ["query", "item-query"],
+ "msg": 'string does not match regex "^fixedquery$"',
+ "type": "value_error.str.regex",
+ }
+ ]
+}
+
+
+@needs_py310
+@pytest.mark.parametrize(
+ "q_name,q,expected_status,expected_response",
+ [
+ (None, None, 200, {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}),
+ (
+ "item-query",
+ "fixedquery",
+ 200,
+ {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}], "q": "fixedquery"},
+ ),
+ ("q", "fixedquery", 200, {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}),
+ ("item-query", "nonregexquery", 422, regex_error),
+ ],
+)
+def test_query_params_str_validations(
+ q_name, q, expected_status, expected_response, client: TestClient
+):
+ url = "/items/"
+ if q_name and q:
+ url = f"{url}?{q_name}={q}"
+ response = client.get(url)
+ assert response.status_code == expected_status
+ assert response.json() == expected_response
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py
index 6ae10296..ad3645f3 100644
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py
@@ -53,7 +53,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py310.py
new file mode 100644
index 00000000..9330037e
--- /dev/null
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py310.py
@@ -0,0 +1,105 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ "parameters": [
+ {
+ "required": False,
+ "schema": {
+ "title": "Q",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "name": "q",
+ "in": "query",
+ }
+ ],
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "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"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.query_params_str_validations.tutorial011_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+@needs_py310
+def test_multi_query_values(client: TestClient):
+ url = "/items/?q=foo&q=bar"
+ response = client.get(url)
+ assert response.status_code == 200, response.text
+ assert response.json() == {"q": ["foo", "bar"]}
+
+
+@needs_py310
+def test_query_no_values(client: TestClient):
+ url = "/items/"
+ response = client.get(url)
+ assert response.status_code == 200, response.text
+ assert response.json() == {"q": None}
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py39.py
new file mode 100644
index 00000000..11f23be2
--- /dev/null
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py39.py
@@ -0,0 +1,105 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py39
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ "parameters": [
+ {
+ "required": False,
+ "schema": {
+ "title": "Q",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "name": "q",
+ "in": "query",
+ }
+ ],
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "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"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.query_params_str_validations.tutorial011_py39 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py39
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+@needs_py39
+def test_multi_query_values(client: TestClient):
+ url = "/items/?q=foo&q=bar"
+ response = client.get(url)
+ assert response.status_code == 200, response.text
+ assert response.json() == {"q": ["foo", "bar"]}
+
+
+@needs_py39
+def test_query_no_values(client: TestClient):
+ url = "/items/"
+ response = client.get(url)
+ assert response.status_code == 200, response.text
+ assert response.json() == {"q": None}
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py
index 724c975f..d69139dd 100644
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py
@@ -54,7 +54,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_py39.py
new file mode 100644
index 00000000..b25bb284
--- /dev/null
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_py39.py
@@ -0,0 +1,106 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py39
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Items",
+ "operationId": "read_items_items__get",
+ "parameters": [
+ {
+ "required": False,
+ "schema": {
+ "title": "Q",
+ "type": "array",
+ "items": {"type": "string"},
+ "default": ["foo", "bar"],
+ },
+ "name": "q",
+ "in": "query",
+ }
+ ],
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "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"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.query_params_str_validations.tutorial012_py39 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py39
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+@needs_py39
+def test_default_query_values(client: TestClient):
+ url = "/items/"
+ response = client.get(url)
+ assert response.status_code == 200, response.text
+ assert response.json() == {"q": ["foo", "bar"]}
+
+
+@needs_py39
+def test_multi_query_values(client: TestClient):
+ url = "/items/?q=baz&q=foobar"
+ response = client.get(url)
+ assert response.status_code == 200, response.text
+ assert response.json() == {"q": ["baz", "foobar"]}
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py
index ad559791..1b2e3635 100644
--- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py
@@ -54,7 +54,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py
new file mode 100644
index 00000000..57b8b9d9
--- /dev/null
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py
@@ -0,0 +1,82 @@
+from fastapi.testclient import TestClient
+
+from docs_src.query_params_str_validations.tutorial014 import app
+
+client = TestClient(app)
+
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "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": {}}},
+ },
+ "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"},
+ }
+ },
+ },
+ "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"},
+ },
+ },
+ }
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+def test_hidden_query():
+ response = client.get("/items?hidden_query=somevalue")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"hidden_query": "somevalue"}
+
+
+def test_no_hidden_query():
+ response = client.get("/items")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"hidden_query": "Not found"}
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_py310.py
new file mode 100644
index 00000000..fe54fc08
--- /dev/null
+++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_py310.py
@@ -0,0 +1,91 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "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": {}}},
+ },
+ "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"},
+ }
+ },
+ },
+ "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"},
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.query_params_str_validations.tutorial014_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+@needs_py310
+def test_hidden_query(client: TestClient):
+ response = client.get("/items?hidden_query=somevalue")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"hidden_query": "somevalue"}
+
+
+@needs_py310
+def test_no_hidden_query(client: TestClient):
+ response = client.get("/items")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"hidden_query": "Not found"}
diff --git a/tests/test_tutorial/test_request_files/test_tutorial001.py b/tests/test_tutorial/test_request_files/test_tutorial001.py
index 4eba1849..166014c7 100644
--- a/tests/test_tutorial/test_request_files/test_tutorial001.py
+++ b/tests/test_tutorial/test_request_files/test_tutorial001.py
@@ -1,5 +1,3 @@
-import os
-
from fastapi.testclient import TestClient
from docs_src.request_files.tutorial001 import app
@@ -101,7 +99,7 @@ openapi_schema = {
"loc": {
"title": "Location",
"type": "array",
- "items": {"type": "string"},
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
@@ -152,35 +150,35 @@ def test_post_body_json():
assert response.json() == file_required
-def test_post_file(tmpdir):
- path = os.path.join(tmpdir, "test.txt")
- with open(path, "wb") as file:
- file.write(b"