From 20d48345465e5b09756cc9ef62fb940c63255a13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 24 Aug 2021 12:21:05 +0200 Subject: [PATCH 001/196] =?UTF-8?q?=E2=9C=A8=20Add=20support=20for=20`read?= =?UTF-8?q?=5Fwith=5Form=5Fmode`,=20to=20support=20SQLModel=20relationship?= =?UTF-8?q?=20attributes=20(#3757)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi/routing.py | 7 +++++ tests/test_read_with_orm_mode.py | 53 ++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 tests/test_read_with_orm_mode.py diff --git a/fastapi/routing.py b/fastapi/routing.py index 0ad08234..63ad7296 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -65,6 +65,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, 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"] From dc9c570733441a83232d111ca0df3f93876a8097 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 24 Aug 2021 10:21:45 +0000 Subject: [PATCH 002/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 15a35f59..0421f754 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ✨ Add support for `read_with_orm_mode`, to support SQLModel relationship attributes. PR [#3757](https://github.com/tiangolo/fastapi/pull/3757) by [@tiangolo](https://github.com/tiangolo). * 🌐 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). From 6ac35b1c2aed4392f5227ee5458e23eca2fc52c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 24 Aug 2021 14:13:47 +0200 Subject: [PATCH 003/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 0421f754..f2c89aaa 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,7 +2,10 @@ ## Latest Changes -* ✨ Add support for `read_with_orm_mode`, to support SQLModel relationship attributes. PR [#3757](https://github.com/tiangolo/fastapi/pull/3757) by [@tiangolo](https://github.com/tiangolo). +* ✨ 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). @@ -10,12 +13,15 @@ * 🌐 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). -* ⬆ Add support for Python 3.9. PR [#2298](https://github.com/tiangolo/fastapi/pull/2298) by [@Kludex](https://github.com/Kludex). +* ⬆ 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 From 7b6e198d314e320256c2ed8b62430b2a42c31cb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 24 Aug 2021 14:16:09 +0200 Subject: [PATCH 004/196] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.68?= =?UTF-8?q?.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 3 +++ fastapi/__init__.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index f2c89aaa..921c2b99 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,9 @@ ## Latest Changes + +## 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 diff --git a/fastapi/__init__.py b/fastapi/__init__.py index 60f90b80..31636266 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.68.0" +__version__ = "0.68.1" from starlette import status as status From f49ba24b71c5c2539ba9cc8b8d1408072da5f631 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 27 Aug 2021 10:49:08 +0200 Subject: [PATCH 005/196] =?UTF-8?q?=F0=9F=94=A7=20Add=20new=20Sponsor=20Ca?= =?UTF-8?q?lmcode.io=20(#3777)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/data/sponsors.yml | 4 ++++ docs/en/data/sponsors_badge.yml | 1 + docs/en/docs/img/sponsors/calmcode.jpg | Bin 0 -> 16949 bytes 3 files changed, 5 insertions(+) create mode 100644 docs/en/docs/img/sponsors/calmcode.jpg diff --git a/docs/en/data/sponsors.yml b/docs/en/data/sponsors.yml index b5bb9a77..e0a4ee78 100644 --- a/docs/en/data/sponsors.yml +++ b/docs/en/data/sponsors.yml @@ -18,3 +18,7 @@ silver: - 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 +bronze: + - url: https://calmcode.io + title: Code. Simply. Clearly. Calmly. + img: https://fastapi.tiangolo.com/img/sponsors/calmcode.jpg diff --git a/docs/en/data/sponsors_badge.yml b/docs/en/data/sponsors_badge.yml index 066a8250..9e95a625 100644 --- a/docs/en/data/sponsors_badge.yml +++ b/docs/en/data/sponsors_badge.yml @@ -4,3 +4,4 @@ logins: - investsuite - vimsoHQ - mikeckennedy + - koaning diff --git a/docs/en/docs/img/sponsors/calmcode.jpg b/docs/en/docs/img/sponsors/calmcode.jpg new file mode 100644 index 0000000000000000000000000000000000000000..59e68dd07357878a76bd228b61fb62f201bd0236 GIT binary patch literal 16949 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 Date: Fri, 27 Aug 2021 08:49:40 +0000 Subject: [PATCH 006/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 921c2b99..1502e4de 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🔧 Add new Sponsor Calmcode.io. PR [#3777](https://github.com/tiangolo/fastapi/pull/3777) by [@tiangolo](https://github.com/tiangolo). ## 0.68.1 From c8b4e4d455cf0ca370d2814ac019ffe74e47eb12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 10 Sep 2021 17:28:22 +0300 Subject: [PATCH 007/196] =?UTF-8?q?=F0=9F=8E=A8=20Tweak=20CSS=20styles=20f?= =?UTF-8?q?or=20shell=20animations=20(#3888)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/css/custom.css | 14 ++++++++++++++ docs/en/docs/css/termynal.css | 3 ++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/css/custom.css b/docs/en/docs/css/custom.css index 997bcd3a..7d3503e4 100644 --- a/docs/en/docs/css/custom.css +++ b/docs/en/docs/css/custom.css @@ -1,3 +1,13 @@ +.termynal-comment { + color: #4a968f; + font-style: italic; + display: block; +} + +.termy [data-termynal] { + white-space: pre-wrap; +} + a.external-link::after { /* \00A0 is a non-breaking space to make the mark be on the same line as the link @@ -12,6 +22,10 @@ a.internal-link::after { content: "\00A0↪"; } +.shadow { + box-shadow: 5px 5px 10px #999; +} + /* Give space to lower icons so Gitter chat doesn't get on top of them */ .md-footer-meta { padding-bottom: 2em; diff --git a/docs/en/docs/css/termynal.css b/docs/en/docs/css/termynal.css index 0484e65d..406c0089 100644 --- a/docs/en/docs/css/termynal.css +++ b/docs/en/docs/css/termynal.css @@ -17,7 +17,8 @@ max-width: 100%; background: var(--color-bg); color: var(--color-text); - font-size: 18px; + /* font-size: 18px; */ + font-size: 15px; /* font-family: 'Fira Mono', Consolas, Menlo, Monaco, 'Courier New', Courier, monospace; */ font-family: 'Roboto Mono', 'Fira Mono', Consolas, Menlo, Monaco, 'Courier New', Courier, monospace; border-radius: 4px; From eaeafc32c49100d6b3f92acdf225761ea48bb9a7 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 10 Sep 2021 14:29:04 +0000 Subject: [PATCH 008/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 1502e4de..ea50f4ab 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🎨 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 From 3a786a7a3aef87fc1a313acf60a0782324ca3222 Mon Sep 17 00:00:00 2001 From: Maximilian Wassink Date: Mon, 13 Sep 2021 19:29:03 +0200 Subject: [PATCH 009/196] =?UTF-8?q?=F0=9F=8C=90=20Add=20German=20translati?= =?UTF-8?q?on=20for=20`docs/features.md`=20(#3699)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/de/docs/features.md | 204 +++++++++++++++++++++++++++++++++++++++ docs/de/mkdocs.yml | 1 + 2 files changed, 205 insertions(+) create mode 100644 docs/de/docs/features.md diff --git a/docs/de/docs/features.md b/docs/de/docs/features.md new file mode 100644 index 00000000..e5b38616 --- /dev/null +++ b/docs/de/docs/features.md @@ -0,0 +1,204 @@ +# 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 standartmäßig vorhanden sind. + +* Swagger UI, bietet interaktive Exploration: testen und rufen Sie ihre API direkt vom Webbrowser auf. + +![Swagger UI Interaktion](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) + +* Alternative API-Dokumentation mit ReDoc. + +![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) + +### 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: + +![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) + +* in PyCharm: + +![editor support](https://fastapi.tiangolo.com/img/pycharm-completion.png) + +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 sensible **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 brachen. + +Aber standartmäß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ögnliche 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ütz 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 eigner Starlett Quellcode funktioniert. + +`FastAPI` ist eigentlich eine Unterklasse von `Starlette`. Wenn sie also bereits Starlette kennen oder benutzen, können Sie das meiste Ihres Wissen 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. +* **GraphQL**-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/mkdocs.yml b/docs/de/mkdocs.yml index 49fad8ec..77882db0 100644 --- a/docs/de/mkdocs.yml +++ b/docs/de/mkdocs.yml @@ -54,6 +54,7 @@ nav: - tr: /tr/ - uk: /uk/ - zh: /zh/ +- features.md markdown_extensions: - toc: permalink: true From bee35f5ae1fc58e7ab125427ad4287210e99d8b3 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 13 Sep 2021 17:29:47 +0000 Subject: [PATCH 010/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index ea50f4ab..a581cf39 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🌐 Add German translation for `docs/features.md`. PR [#3699](https://github.com/tiangolo/fastapi/pull/3699) by [@mawassk](https://github.com/mawassk). * 🎨 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). From 64e7deaebc1bebb327cc146f1d65e088b282e731 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 27 Sep 2021 16:40:38 +0200 Subject: [PATCH 011/196] =?UTF-8?q?=F0=9F=93=9D=20Upgrade=20HTTPS=20guide?= =?UTF-8?q?=20with=20more=20explanations=20and=20diagrams=20(#3950)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/deployment/https.md | 192 ++++++++++-- .../en/docs/img/deployment/https/https.drawio | 277 ++++++++++++++++++ docs/en/docs/img/deployment/https/https.svg | 62 ++++ .../docs/img/deployment/https/https01.drawio | 78 +++++ docs/en/docs/img/deployment/https/https01.svg | 57 ++++ .../docs/img/deployment/https/https02.drawio | 110 +++++++ docs/en/docs/img/deployment/https/https02.svg | 57 ++++ .../docs/img/deployment/https/https03.drawio | 131 +++++++++ docs/en/docs/img/deployment/https/https03.svg | 62 ++++ .../docs/img/deployment/https/https04.drawio | 152 ++++++++++ docs/en/docs/img/deployment/https/https04.svg | 62 ++++ .../docs/img/deployment/https/https05.drawio | 166 +++++++++++ docs/en/docs/img/deployment/https/https05.svg | 62 ++++ .../docs/img/deployment/https/https06.drawio | 183 ++++++++++++ docs/en/docs/img/deployment/https/https06.svg | 62 ++++ .../docs/img/deployment/https/https07.drawio | 203 +++++++++++++ docs/en/docs/img/deployment/https/https07.svg | 62 ++++ .../docs/img/deployment/https/https08.drawio | 217 ++++++++++++++ docs/en/docs/img/deployment/https/https08.svg | 62 ++++ 19 files changed, 2232 insertions(+), 25 deletions(-) create mode 100644 docs/en/docs/img/deployment/https/https.drawio create mode 100644 docs/en/docs/img/deployment/https/https.svg create mode 100644 docs/en/docs/img/deployment/https/https01.drawio create mode 100644 docs/en/docs/img/deployment/https/https01.svg create mode 100644 docs/en/docs/img/deployment/https/https02.drawio create mode 100644 docs/en/docs/img/deployment/https/https02.svg create mode 100644 docs/en/docs/img/deployment/https/https03.drawio create mode 100644 docs/en/docs/img/deployment/https/https03.svg create mode 100644 docs/en/docs/img/deployment/https/https04.drawio create mode 100644 docs/en/docs/img/deployment/https/https04.svg create mode 100644 docs/en/docs/img/deployment/https/https05.drawio create mode 100644 docs/en/docs/img/deployment/https/https05.svg create mode 100644 docs/en/docs/img/deployment/https/https06.drawio create mode 100644 docs/en/docs/img/deployment/https/https06.svg create mode 100644 docs/en/docs/img/deployment/https/https07.drawio create mode 100644 docs/en/docs/img/deployment/https/https07.svg create mode 100644 docs/en/docs/img/deployment/https/https08.drawio create mode 100644 docs/en/docs/img/deployment/https/https08.svg diff --git a/docs/en/docs/deployment/https.md b/docs/en/docs/deployment/https.md index c735f1f4..1a3b1a0a 100644 --- a/docs/en/docs/deployment/https.md +++ b/docs/en/docs/deployment/https.md @@ -7,42 +7,184 @@ But it is way more complex than that. !!! tip If you are in a hurry or don't care, continue with the next sections for step by step instructions to set everything up with different techniques. -To learn the basics of HTTPS, from a consumer perspective, check https://howhttps.works/. +To **learn the basics of HTTPS**, from a consumer perspective, check https://howhttps.works/. -Now, from a developer's perspective, here are several things to have in mind while thinking about HTTPS: +Now, from a **developer's perspective**, here are several things to have in mind while thinking about HTTPS: -* For HTTPS, the server needs to have "certificates" generated by a third party. - * Those certificates are actually acquired from the third-party, not "generated". -* Certificates have a lifetime. - * They expire. - * And then they need to be renewed, acquired again from the third party. -* The encryption of the connection happens at the TCP level. - * That's one layer below HTTP. - * So, the certificate and encryption handling is done before HTTP. -* TCP doesn't know about "domains". Only about IP addresses. - * The information about the specific domain requested goes in the HTTP data. -* The HTTPS certificates "certify" a certain domain, but the protocol and encryption happen at the TCP level, before knowing which domain is being dealt with. -* By default, that would mean that you can only have one HTTPS certificate per IP address. +* For HTTPS, **the server** needs to **have "certificates"** generated by a **third party**. + * Those certificates are actually **acquired** from the third party, not "generated". +* Certificates have a **lifetime**. + * They **expire**. + * And then they need to be **renewed**, **acquired again** from the third party. +* The encryption of the connection happens at the **TCP level**. + * That's one layer **below HTTP**. + * So, the **certificate and encryption** handling is done **before HTTP**. +* **TCP doesn't know about "domains"**. Only about IP addresses. + * The information about the **specific domain** requested goes in the **HTTP data**. +* The **HTTPS certificates** "certify" a **certain domain**, but the protocol and encryption happen at the TCP level, **before knowing** which domain is being dealt with. +* **By default**, that would mean that you can only have **one HTTPS certificate per IP address**. * No matter how big your server is or how small each application you have on it might be. - * There is a solution to this, however. -* There's an extension to the TLS protocol (the one handling the encryption at the TCP level, before HTTP) called SNI. - * This SNI extension allows one single server (with a single IP address) to have several HTTPS certificates and serve multiple HTTPS domains/applications. - * For this to work, a single component (program) running on the server, listening on the public IP address, must have all the HTTPS certificates in the server. -* After obtaining a secure connection, the communication protocol is still HTTP. - * The contents are encrypted, even though they are being sent with the HTTP protocol. + * There is a **solution** to this, however. +* There's an **extension** to the **TLS** protocol (the one handling the encryption at the TCP level, before HTTP) called **SNI**. + * This SNI extension allows one single server (with a **single IP address**) to have **several HTTPS certificates** and serve **multiple HTTPS domains/applications**. + * For this to work, a **single** component (program) running on the server, listening on the **public IP address**, must have **all the HTTPS certificates** in the server. +* **After** obtaining a secure connection, the communication protocol is **still HTTP**. + * The contents are **encrypted**, even though they are being sent with the **HTTP protocol**. -It is a common practice to have one program/HTTP server running on the server (the machine, host, etc.) and managing all the HTTPS parts : sending the decrypted HTTP requests to the actual HTTP application running in the same server (the **FastAPI** application, in this case), take the HTTP response from the application, encrypt it using the appropriate certificate and sending it back to the client using HTTPS. This server is often called a TLS Termination Proxy. +It is a common practice to have **one program/HTTP server** running on the server (the machine, host, etc.) and **managing all the HTTPS parts**: receiving the **encrypted HTTPS requests**, sending the **decrypted HTTP requests** to the actual HTTP application running in the same server (the **FastAPI** application, in this case), take the **HTTP response** from the application, **encrypt it** using the appropriate **HTTPS certificate** and sending it back to the client using **HTTPS**. This server is often called a **TLS Termination Proxy**. + +Some of the options you could use as a TLS Termination Proxy are: + +* Traefik (that can also handle certificate renewals) +* Caddy (that can also handle certificate renewals) +* Nginx +* HAProxy ## Let's Encrypt -Before Let's Encrypt, these HTTPS certificates were sold by trusted third-parties. +Before Let's Encrypt, these **HTTPS certificates** were sold by trusted third parties. The process to acquire one of these certificates used to be cumbersome, require quite some paperwork and the certificates were quite expensive. -But then Let's Encrypt was created. +But then **Let's Encrypt** was created. -It is a project from the Linux Foundation. It provides HTTPS certificates for free. In an automated way. These certificates use all the standard cryptographic security, and are short lived (about 3 months), so the security is actually better because of their reduced lifespan. +It is a project from the Linux Foundation. It provides **HTTPS certificates for free**, in an automated way. These certificates use all the standard cryptographic security, and are short-lived (about 3 months), so the **security is actually better** because of their reduced lifespan. The domains are securely verified and the certificates are generated automatically. This also allows automating the renewal of these certificates. -The idea is to automate the acquisition and renewal of these certificates, so that you can have secure HTTPS, for free, forever. +The idea is to automate the acquisition and renewal of these certificates so that you can have **secure HTTPS, for free, forever**. + +## HTTPS for Developers + +Here's an example of how an HTTPS API could look like, step by step, paying attention mainly to the ideas important for developers. + +### Domain Name + +It would probably all start by you **acquiring** some **domain name**. Then, you would configure it in a DNS server (possibly your same cloud provider). + +You would probably get a cloud server (a virtual machine) or something similar, and it would have a fixed **public IP address**. + +In the DNS server(s) you would configure a record (an "`A record`") to point **your domain** to the public **IP address of your server**. + +You would probably do this just once, the first time, when setting everything up. + +!!! tip + This Domain Name part is way before HTTPS, but as everything depends on the domain and the IP address, it's worth mentioning it here. + +### DNS + +Now let's focus on all the actual HTTPS parts. + +First, the browser would check with the **DNS servers** what is the **IP for the domain**, in this case, `someapp.example.com`. + +The DNS servers would tell the browser to use some specific **IP address**. That would be the public IP address used by your server, that you configured in the DNS servers. + + + +### TLS Handshake Start + +The browser would then communicate with that IP address on **port 443** (the HTTPS port). + +The first part of the communication is just to establish the connection between the client and the server and to decide the cryptographic keys they will use, etc. + + + +This interaction between the client and the server to establish the TLS connection is called the **TLS handshake**. + +### TLS with SNI Extension + +**Only one process** in the server can be listening on a specific **port** in a specific **IP address**. There could be other processes listening on other ports in the same IP address, but only one for each combination of IP address and port. + +TLS (HTTPS) uses the specific port `443` by default. So that's the port we would need. + +As only one process can be listening on this port, the process that would do it would be the **TLS Termination Proxy**. + +The TLS Termination Proxy would have access to one or more **TLS certificates** (HTTPS certificates). + +Using the **SNI extension** discussed above, the TLS Termination Proxy would check which of the TLS (HTTPS) certificates available it should use for this connection, using the one that matches the domain expected by the client. + +In this case, it would use the certificate for `someapp.example.com`. + + + +The client already **trusts** the entity that generated that TLS certificate (in this case Let's Encrypt, but we'll see about that later), so it can **verify** that the certificate is valid. + +Then, using the certificate, the client and the TLS Termination Proxy **decide how to encrypt** the rest of the **TCP communication**. This completes the **TLS Handshake** part. + +After this, the client and the server have an **encrypted TCP connection**, this is what TLS provides. And then they can use that connection to start the actual **HTTP communication**. + +And that's what **HTTPS** is, it's just plain **HTTP** inside a **secure TLS connection** instead of a pure (unencrypted) TCP connection. + +!!! tip + Notice that the encryption of the communication happens at the **TCP level**, not at the HTTP level. + +### HTTPS Request + +Now that the client and server (specifically the browser and the TLS Termination Proxy) have an **encrypted TCP connection**, they can start the **HTTP communication**. + +So, the client sends an **HTTPS request**. This is just an HTTP request through an encrypted TLS connection. + + + +### Decrypt the Request + +The TLS Termination Proxy would use the encryption agreed to **decrypt the request**, and would transmit the **plain (decrypted) HTTP request** to the process running the application (for example a process with Uvicorn running the FastAPI application). + + + +### HTTP Response + +The application would process the request and send a **plain (unencrypted) HTTP response** to the TLS Termination Proxy. + + + +### HTTPS Response + +The TLS Termination Proxy would then **encrypt the response** using the cryptography agreed before (that started with the certificate for `someapp.example.com`), and send it back to the browser. + +Next, the browser would verify that the response is valid and encrypted with the right cryptographic key, etc. It would then **decrypt the response** and process it. + + + +The client (browser) will know that the response comes from the correct server because it is using the cryptography they agreed using the **HTTPS certificate** before. + +### Multiple Applications + +In the same server (or servers), there could be **multiple applications**, for example, other API programs or a database. + +Only one process can be handling the specific IP and port (the TLS Termination Proxy in our example) but the other applications/processes can be running on the server(s) too, as long as they don't try to use the same **combination of public IP and port**. + + + +That way, the TLS Termination Proxy could handle HTTPS and certificates for **multiple domains**, for multiple applications, and then transmit the requests to the right application in each case. + +### Certificate Renewal + +At some point in the future, each certificate would **expire** (about 3 months after acquiring it). + +And then, there would be another program (in some cases it's another program, in some cases it could be the same TLS Termination Proxy) that would talk to Let's Encrypt, and renew the certificate(s). + + + +The **TLS certificates** are **associated with a domain name**, not with an IP address. + +So, to renew the certificates, the renewal program needs to **prove** to the authority (Let's Encrypt) that it indeed **"owns" and controls that domain**. + +To do that, and to accommodate different application needs, there are several ways it can do it. Some popular ways are: + +* **Modify some DNS records**. + * For this, the renewal program needs to support the APIs of the DNS provider, so, depending on the DNS provider you are using, this might or might not be an option. +* **Run as a server** (at least during the certificate acquisition process) on the public IP address associated with the domain. + * As we said above, only one process can be listening on a specific IP and port. + * This is one of the reasons why it's very useful when the same TLS Termination Proxy also takes care of the certificate renewal process. + * Otherwise, you might have to stop the TLS Termination Proxy momentarily, start the renewal program to acquire the certificates, then configure them with the TLS Termination Proxy, and then restart the TLS Termination Proxy. This is not ideal, as your app(s) will not be available during the time that the TLS Termination Proxy is off. + +All this renewal process, while still serving the app, is one of the main reasons why you would want to have a **separate system to handle HTTPS** with a TLS Termination Proxy instead of just using the TLS certificates with the application server directly (e.g. Uvicorn). + +## Recap + +Having **HTTPS** is very important, and quite **critical** in most cases. Most of the effort you as a developer have to put around HTTPS is just about **understanding these concepts** and how they work. + +But once you know the basic information of **HTTPS for developers** you can easily combine and configure different tools to help you manage everything in a simple way. + +In some of the next chapters I'll show you several concrete examples of how to set up **HTTPS** for **FastAPI** applications. 🔒 diff --git a/docs/en/docs/img/deployment/https/https.drawio b/docs/en/docs/img/deployment/https/https.drawio new file mode 100644 index 00000000..31cfab96 --- /dev/null +++ b/docs/en/docs/img/deployment/https/https.drawio @@ -0,0 +1,277 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/docs/img/deployment/https/https.svg b/docs/en/docs/img/deployment/https/https.svg new file mode 100644 index 00000000..e63345eb --- /dev/null +++ b/docs/en/docs/img/deployment/https/https.svg @@ -0,0 +1,62 @@ +
Server(s)
Server(s)
https://someapp.example.com
https://someapp.example.com
DNS Servers
DNS Servers
TLS Termination Proxy
TLS Termination Proxy +
Cert Renovation Program
Cert Renovation Program
Let's Encrypt
Let's Encrypt
FastAPI app for: someapp.example.com
FastAPI app for: someapp.example.com
Another app: another.example.com
Another app: another.example.com
One more app: onemore.example.com
One more app: onemore.example.com
A Database
A Database
Plain response from: someapp.example.com
Plain response from: someapp.example.com
Port 443 (HTTPS)
Port 443 (HTTPS)
Encrypted request for: someapp.example.com
Encrypted request for: someapp.example.com
Who is: someapp.example.com
Who is: someapp.example.com
IP:
123.124.125.126
IP:...
Renew HTTPS cert for: someapp.example.com
Renew HTTPS cert for: someapp.example.com
New HTTPS cert for: someapp.example.com
New HTTPS cert for: someapp.example.com
TLS Handshake
TLS Handshake
Encrypted response from: someapp.example.com
Encrypted response from: someapp.example.com
HTTPS certificates
HTTPS certificates +
someapp.example.com
someapp.example.com +
another.example.net
another.example.net +
onemore.example.org
onemore.example.org +
IP:
123.124.125.126
IP:...
Decrypted request for: someapp.example.com
Decrypted request for: someapp.example.com
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/en/docs/img/deployment/https/https01.drawio b/docs/en/docs/img/deployment/https/https01.drawio new file mode 100644 index 00000000..9bc5340c --- /dev/null +++ b/docs/en/docs/img/deployment/https/https01.drawio @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/docs/img/deployment/https/https01.svg b/docs/en/docs/img/deployment/https/https01.svg new file mode 100644 index 00000000..4fee0adf --- /dev/null +++ b/docs/en/docs/img/deployment/https/https01.svg @@ -0,0 +1,57 @@ +
https://someapp.example.com
https://someapp.example.com
DNS Servers
DNS Servers
Who is: someapp.example.com
Who is: someapp.example.com
IP:
123.124.125.126
IP:...
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/en/docs/img/deployment/https/https02.drawio b/docs/en/docs/img/deployment/https/https02.drawio new file mode 100644 index 00000000..0f7578d3 --- /dev/null +++ b/docs/en/docs/img/deployment/https/https02.drawio @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/docs/img/deployment/https/https02.svg b/docs/en/docs/img/deployment/https/https02.svg new file mode 100644 index 00000000..1f37a709 --- /dev/null +++ b/docs/en/docs/img/deployment/https/https02.svg @@ -0,0 +1,57 @@ +
Server(s)
Server(s)
https://someapp.example.com
https://someapp.example.com
DNS Servers
DNS Servers
Port 443 (HTTPS)
Port 443 (HTTPS)
IP:
123.124.125.126
IP:...
Who is: someapp.example.com
Who is: someapp.example.com
IP:
123.124.125.126
IP:...
TLS Handshake
TLS Handshake
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/en/docs/img/deployment/https/https03.drawio b/docs/en/docs/img/deployment/https/https03.drawio new file mode 100644 index 00000000..c5766086 --- /dev/null +++ b/docs/en/docs/img/deployment/https/https03.drawio @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/docs/img/deployment/https/https03.svg b/docs/en/docs/img/deployment/https/https03.svg new file mode 100644 index 00000000..e68e1c45 --- /dev/null +++ b/docs/en/docs/img/deployment/https/https03.svg @@ -0,0 +1,62 @@ +
Server(s)
Server(s)
https://someapp.example.com
https://someapp.example.com
DNS Servers
DNS Servers
TLS Termination Proxy
TLS Termination Proxy +
Port 443 (HTTPS)
Port 443 (HTTPS)
Who is: someapp.example.com
Who is: someapp.example.com
IP:
123.124.125.126
IP:...
TLS Handshake
TLS Handshake
HTTPS certificates
HTTPS certificates +
someapp.example.com
someapp.example.com +
another.example.net
another.example.net +
onemore.example.org
onemore.example.org +
IP:
123.124.125.126
IP:...
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/en/docs/img/deployment/https/https04.drawio b/docs/en/docs/img/deployment/https/https04.drawio new file mode 100644 index 00000000..ea357a6c --- /dev/null +++ b/docs/en/docs/img/deployment/https/https04.drawio @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/docs/img/deployment/https/https04.svg b/docs/en/docs/img/deployment/https/https04.svg new file mode 100644 index 00000000..4c9b7999 --- /dev/null +++ b/docs/en/docs/img/deployment/https/https04.svg @@ -0,0 +1,62 @@ +
Server(s)
Server(s)
https://someapp.example.com
https://someapp.example.com
DNS Servers
DNS Servers
TLS Termination Proxy
TLS Termination Proxy +
Port 443 (HTTPS)
Port 443 (HTTPS)
Encrypted request for: someapp.example.com
Encrypted request for: someapp.example.com
Who is: someapp.example.com
Who is: someapp.example.com
IP:
123.124.125.126
IP:...
TLS Handshake
TLS Handshake
HTTPS certificates
HTTPS certificates +
someapp.example.com
someapp.example.com +
another.example.net
another.example.net +
onemore.example.org
onemore.example.org +
IP:
123.124.125.126
IP:...
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/en/docs/img/deployment/https/https05.drawio b/docs/en/docs/img/deployment/https/https05.drawio new file mode 100644 index 00000000..9b8b7c6f --- /dev/null +++ b/docs/en/docs/img/deployment/https/https05.drawio @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/docs/img/deployment/https/https05.svg b/docs/en/docs/img/deployment/https/https05.svg new file mode 100644 index 00000000..d11647b9 --- /dev/null +++ b/docs/en/docs/img/deployment/https/https05.svg @@ -0,0 +1,62 @@ +
Server(s)
Server(s)
https://someapp.example.com
https://someapp.example.com
DNS Servers
DNS Servers
TLS Termination Proxy
TLS Termination Proxy +
FastAPI app for: someapp.example.com
FastAPI app for: someapp.example.com
Decrypted request for: someapp.example.com
Decrypted request for: someapp.example.com
Port 443 (HTTPS)
Port 443 (HTTPS)
Encrypted request for: someapp.example.com
Encrypted request for: someapp.example.com
Who is: someapp.example.com
Who is: someapp.example.com
IP:
123.124.125.126
IP:...
TLS Handshake
TLS Handshake
HTTPS certificates
HTTPS certificates +
someapp.example.com
someapp.example.com +
another.example.net
another.example.net +
onemore.example.org
onemore.example.org +
IP:
123.124.125.126
IP:...
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/en/docs/img/deployment/https/https06.drawio b/docs/en/docs/img/deployment/https/https06.drawio new file mode 100644 index 00000000..5bb85813 --- /dev/null +++ b/docs/en/docs/img/deployment/https/https06.drawio @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/docs/img/deployment/https/https06.svg b/docs/en/docs/img/deployment/https/https06.svg new file mode 100644 index 00000000..10e03b7c --- /dev/null +++ b/docs/en/docs/img/deployment/https/https06.svg @@ -0,0 +1,62 @@ +
Server(s)
Server(s)
https://someapp.example.com
https://someapp.example.com
DNS Servers
DNS Servers
TLS Termination Proxy
TLS Termination Proxy +
FastAPI app for: someapp.example.com
FastAPI app for: someapp.example.com
Plain response from: someapp.example.com
Plain response from: someapp.example.com
Decrypted request for: someapp.example.com
Decrypted request for: someapp.example.com
Port 443 (HTTPS)
Port 443 (HTTPS)
Encrypted request for: someapp.example.com
Encrypted request for: someapp.example.com
Who is: someapp.example.com
Who is: someapp.example.com
IP:
123.124.125.126
IP:...
TLS Handshake
TLS Handshake
HTTPS certificates
HTTPS certificates +
someapp.example.com
someapp.example.com +
another.example.net
another.example.net +
onemore.example.org
onemore.example.org +
IP:
123.124.125.126
IP:...
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/en/docs/img/deployment/https/https07.drawio b/docs/en/docs/img/deployment/https/https07.drawio new file mode 100644 index 00000000..1ca994b2 --- /dev/null +++ b/docs/en/docs/img/deployment/https/https07.drawio @@ -0,0 +1,203 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/docs/img/deployment/https/https07.svg b/docs/en/docs/img/deployment/https/https07.svg new file mode 100644 index 00000000..e409d887 --- /dev/null +++ b/docs/en/docs/img/deployment/https/https07.svg @@ -0,0 +1,62 @@ +
Server(s)
Server(s)
https://someapp.example.com
https://someapp.example.com
DNS Servers
DNS Servers
TLS Termination Proxy
TLS Termination Proxy +
FastAPI app for: someapp.example.com
FastAPI app for: someapp.example.com
Plain response from: someapp.example.com
Plain response from: someapp.example.com
Decrypted request for: someapp.example.com
Decrypted request for: someapp.example.com
Port 443 (HTTPS)
Port 443 (HTTPS)
Encrypted request for: someapp.example.com
Encrypted request for: someapp.example.com
Who is: someapp.example.com
Who is: someapp.example.com
IP:
123.124.125.126
IP:...
TLS Handshake
TLS Handshake
Encrypted response from: someapp.example.com
Encrypted response from: someapp.example.com
HTTPS certificates
HTTPS certificates +
someapp.example.com
someapp.example.com +
another.example.net
another.example.net +
onemore.example.org
onemore.example.org +
IP:
123.124.125.126
IP:...
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/en/docs/img/deployment/https/https08.drawio b/docs/en/docs/img/deployment/https/https08.drawio new file mode 100644 index 00000000..8a4f4105 --- /dev/null +++ b/docs/en/docs/img/deployment/https/https08.drawio @@ -0,0 +1,217 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/docs/img/deployment/https/https08.svg b/docs/en/docs/img/deployment/https/https08.svg new file mode 100644 index 00000000..3047dd82 --- /dev/null +++ b/docs/en/docs/img/deployment/https/https08.svg @@ -0,0 +1,62 @@ +
Server(s)
Server(s)
https://someapp.example.com
https://someapp.example.com
DNS Servers
DNS Servers
TLS Termination Proxy
TLS Termination Proxy +
FastAPI app for: someapp.example.com
FastAPI app for: someapp.example.com
Another app: another.example.com
Another app: another.example.com
One more app: onemore.example.com
One more app: onemore.example.com
A Database
A Database
Plain response from: someapp.example.com
Plain response from: someapp.example.com
Decrypted request for: someapp.example.com
Decrypted request for: someapp.example.com
Port 443 (HTTPS)
Port 443 (HTTPS)
Encrypted request for: someapp.example.com
Encrypted request for: someapp.example.com
Who is: someapp.example.com
Who is: someapp.example.com
IP:
123.124.125.126
IP:...
TLS Handshake
TLS Handshake
Encrypted response from: someapp.example.com
Encrypted response from: someapp.example.com
HTTPS certificates
HTTPS certificates +
someapp.example.com
someapp.example.com +
another.example.net
another.example.net +
onemore.example.org
onemore.example.org +
IP:
123.124.125.126
IP:...
Viewer does not support full SVG 1.1
\ No newline at end of file From fe086a490396eff622a067468531c8776343217f Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 27 Sep 2021 14:41:20 +0000 Subject: [PATCH 012/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index a581cf39..f67b11f0 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 📝 Upgrade HTTPS guide with more explanations and diagrams. PR [#3950](https://github.com/tiangolo/fastapi/pull/3950) by [@tiangolo](https://github.com/tiangolo). * 🌐 Add German translation for `docs/features.md`. PR [#3699](https://github.com/tiangolo/fastapi/pull/3699) by [@mawassk](https://github.com/mawassk). * 🎨 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). From cb7e79ab8a27b8f453ded2674adb92f8738d7cdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 1 Oct 2021 22:44:19 +0200 Subject: [PATCH 013/196] =?UTF-8?q?=F0=9F=93=9D=20Re-write=20and=20extend?= =?UTF-8?q?=20Deployment=20guide:=20Concepts,=20Uvicorn,=20Gunicorn,=20Doc?= =?UTF-8?q?ker,=20Containers,=20Kubernetes=20(#3974)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/deployment/concepts.md | 311 +++++++++ docs/en/docs/deployment/deta.md | 18 + docs/en/docs/deployment/docker.md | 655 ++++++++++++++++-- docs/en/docs/deployment/index.md | 16 +- docs/en/docs/deployment/manually.md | 50 +- docs/en/docs/deployment/server-workers.md | 178 +++++ .../docs/img/deployment/concepts/image01.png | Bin 0 -> 124827 bytes .../deployment/concepts/process-ram.drawio | 106 +++ .../img/deployment/concepts/process-ram.svg | 59 ++ docs/en/mkdocs.yml | 6 +- 10 files changed, 1320 insertions(+), 79 deletions(-) create mode 100644 docs/en/docs/deployment/concepts.md create mode 100644 docs/en/docs/deployment/server-workers.md create mode 100644 docs/en/docs/img/deployment/concepts/image01.png create mode 100644 docs/en/docs/img/deployment/concepts/process-ram.drawio create mode 100644 docs/en/docs/img/deployment/concepts/process-ram.svg diff --git a/docs/en/docs/deployment/concepts.md b/docs/en/docs/deployment/concepts.md new file mode 100644 index 00000000..d22b53fe --- /dev/null +++ b/docs/en/docs/deployment/concepts.md @@ -0,0 +1,311 @@ +# Deployments Concepts + +When deploying a **FastAPI** application, or actually, any type of web API, there are several concepts that you probably care about, and using them you can find the **most appropriate** way to **deploy your application**. + +Some of the important concepts are: + +* Security - HTTPS +* Running on startup +* Restarts +* Replication (the number of processes running) +* Memory +* Previous steps before starting + +We'll see how they would affect **deployments**. + +In the end, the ultimate objective is to be able to **serve your API clients** in a way that is **secure**, to **avoid disruptions**, and to use the **compute resources** (for example remote servers/virtual machines) as efficiently as possible. 🚀 + +I'll tell you a bit more about these **concepts** here, and that would hopefully give you the **intuition** you would need to decide how to deploy your API in very different environments, possibly even in **future** ones that don't exist yet. + +By considering these concepts, you will be able to **evaluate and design** the best way to deploy **your own APIs**. + +In the next chapters, I'll give you more **concrete recipes** to deploy FastAPI applications. + +But for now, let's check these important **conceptual ideas**. These concepts also apply for any other type of web API. 💡 + +## Security - HTTPS + +In the [previous chapter about HTTPS](./https.md){.internal-link target=_blank} we learned about how HTTPS provides encryption for your API. + +We also saw that HTTPS is normally provided by a component **external** to your application server, a **TLS Termination Proxy**. + +And there has to be something in charge of **renewing the HTTPS certificates**, it could be the same component or it could be something different. + +### Example Tools for HTTPS + +Some of the tools you could use as a TLS Termination Proxy are: + +* Traefik + * Automatically handles certificates renewals ✨ +* Caddy + * Automatically handles certificates renewals ✨ +* Nginx + * With an external component like Certbot for certificate renewals +* HAProxy + * With an external component like Certbot for certificate renewals +* Kubernetes with an Ingress Controller like Nginx + * With an external component like cert-manager for certificate renewals +* Handled internally by a cloud provider as part of their services (read below 👇) + +Another option is that you could use a **cloud service** that does more of the work including setting up HTTPS. It could have some restrictions or charge you more, etc. But in that case you wouldn't have to set up a TLS Termination Proxy yourself. + +I'll show you some concrete examples in the next chapters. + +--- + +Then the next concepts to consider are all about the program running your actual API (e.g. Uvicorn). + +## Program and Process + +We will talk a lot about the running "**process**", so it's useful to have clarity about what it means, and what's the difference with the word "**program**". + +### What is a Program + +The word **program** is commonly used to describe many things: + +* The **code** that you write, the **Python files**. +* The **file** that can be **executed** by the operating system, for example `python`, `python.exe` or `uvicorn`. +* A particular program while it is **running** on the operating system, using the CPU, and storing things on memory. This is also called a **process**. + +### What is a Process + +The word **process** is normally used in a more specific way, only referring to the thing that is running in the operating system (like in the last point above): + +* A particular program while it is **running** on the operating system. + * This doesn't refer to the file, nor to the code, it refers **specifically** to the thing that is being **executed** and managed by the operating system. +* Any program, any code, **can only do things** when it is being **executed**. So, when there's a **process running**. +* The process can be **terminated** (or "killed") by you, or by the operating system. At that point, it stops running/being executed, and it can **no longer do things**. +* Each application that you have running in your computer has some process behind it, each running program, each window, etc. And there are normally many processes running **at the same time** while a computer is on. +* There can be **multiple processes** of the **same program** running at the same time. + +If you check out the "task manager" or "system monitor" (or similar tools) in your operating system, you will be able to see many of those processes running. + +And, for example, you will probably see that there are multiple processes running the same browser program (Firefox, Chrome, Edge, etc). They normally run one process per tab, plus some other extra processes. + + + +--- + +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 on 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, its own variables, its own 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 or 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. + + + +And of course, the same machine would probably have **other processes** running as well, apart from your application. + +An interesting detail is that the percentage of the **CPU used** by each process can **vary** a lot over time, but the **memory (RAM)** normally stays more or less **stable**. + +If you have an API that does a comparable amount of computations every time and you have a lot of clients, then the **CPU utilization** will probably *also be stable* (instead of constantly going up and down quickly). + +### Examples of Replication Tools and Strategies + +There can be several approaches to achieve this, and I'll tell you more about specific strategies in the next chapters, for example when talking about Docker and containers. + +The main constraint to consider is that there has to be a **single** component handling the **port** in the **public IP**. And then it has to have a way to **transmit** the communication to the replicated **processes/workers**. + +Here are some possible combinations and strategies: + +* **Gunicorn** managing **Uvicorn workers** + * Gunicorn would be the **process manager** listening on the **IP** and **port**, the replication would be by having **multiple Uvicorn worker processes** +* **Uvicorn** managing **Uvicorn workers** + * One Uvicorn **process manager** would listen on the **IP** and **port**, and it would start **multiple Uvicorn worker processes** +* **Kubernetes** and other distributed **container systems** + * Something in the **Kubernetes** layer would listen on the **IP** and **port**. The replication would be by having **multiple containers**, each with **one Uvicorn process** running +* **Cloud services** that handle this for your + * The cloud service will probably **handle replication for you**. It would possibly let you define **a process to run**, or a **container image** to use, in any case, it would most probably be **a single Uvicorn process**, and the cloud service would be in charge of replicating it. + +!!! tip + Don't worry if some of these items about **containers**, Docker, or Kubernetes don't make a lot of sense yet. + + I'll tell you more about container images, Docker, Kubernetes, etc. in a future chapter: [FastAPI in Containers - Docker](./docker.md){.internal-link target=_blank}. + +## Previous Steps Before Starting + +There are many cases where you want to perform some steps **before starting** your application. + +For example, you might want to run **database migrations**. + +But in most cases, you will want to perform these steps only **once**. + +So, you will want to have a **single process** to perform those **previous steps**, before starting the application. + +And you will have to make sure that it's a single process running those previous steps *even* if afterwards you start **multiple processes** (multiple workers) for the application itself. If those steps were run by **multiple processes**, they would **duplicate** the work by running it on **parallel**, and if the steps were something delicate like a database migration, they could cause conflicts with each other. + +Of course, there are some cases where there's no problem in running the previous steps multiple times, in that case it's a lot easier to handle. + +!!! tip + Also have in mind that depending on your setup, in some cases you **might not even need any previous steps** before starting your application. + + In that case, you wouldn't have to worry about any of this. 🤷 + +### Examples of Previous Steps Strategies + +This will **depend heavily** on the way you **deploy your system**, and it would probably be connected to the way you start programs, handling restarts, etc. + +Here are some possible ideas: + +* An "Init Container" in Kubernetes that runs before your app container +* A bash script that runs the previous steps and then starts your application + * You would still need a way to start/restart *that* bash script, detect errors, etc. + +!!! tip + I'll give you more concrete examples for doing this with containers in a future chapter: [FastAPI in Containers - Docker](./docker.md){.internal-link target=_blank}. + +## Resource Utilization + +Your server(s) is (are) a **resource**, you can consume or **utilize**, with your programs, the computation time on the CPUs, and the RAM memory available. + +How much resources do you want to be consuming/utilizing? It might be easy to think "not much", but in reality, you will probably want to consume **as much as possible without crashing**. + +If you are paying for 3 servers but you are using only a little bit of their RAM and CPU, you are probably **wasting money** 💸, and probably **wasting server electric power** 🌎, etc. + +In that case, it could be better to have only 2 servers and use a higher percentage of their resources (CPU, memory, disk, network bandwidth, etc). + +On the other hand, if you have 2 servers and you are using **100% of their CPU and RAM**, at some point one process will ask for more memory, and the server will have to use the disk as "memory" (which can be thousands of times slower), or even **crash**. Or one process might need to do some computation and would have to wait until the CPU is free again. + +In this case, it would be better to get **one extra server** and run some processes on it so that they all have **enough RAM and CPU time**. + +There's also the chance that for some reason you have a **spike** of usage of your API. Maybe it went viral, or maybe some other services or bots start using it. And you might want to have extra resources to be safe in those cases. + +You could put an **arbitrary number** to target, for example something **between 50% to 90%** of resource utilization. The point is that those are probably the main things you will want to measure and use to tweak your deployments. + +You can use simple tools like `htop` to see the CPU and RAM used in your server, or the amount used by each process. Or you can use more complex monitoring tools, maybe distributed across servers, etc. + +## Recap + +You have been reading here some of the main concepts that you would probably need to have in mind when deciding how to deploy your application: + +* Security - HTTPS +* Running on startup +* Restarts +* Replication (the number of processes running) +* Memory +* Previous steps before starting + +Understanding these ideas and how to apply them should give you the intuition necessary to take any decisions when configuring and tweaking your deployments. 🤓 + +In the next sections I'll give you more concrete examples of possible strategies you can follow. 🚀 diff --git a/docs/en/docs/deployment/deta.md b/docs/en/docs/deployment/deta.md index fd4b30a3..b0d1cb92 100644 --- a/docs/en/docs/deployment/deta.md +++ b/docs/en/docs/deployment/deta.md @@ -238,3 +238,21 @@ You can also edit them and re-play them. At some point you will probably want to store some data for your app in a way that persists through time. For that you can use Deta Base, it also has a generous **free tier**. You can also read more in the Deta Docs. + +## Deployment Concepts + +Coming back to the concepts we discussed in [Deployments Concepts](./concepts.md){.internal-link target=_blank}, here's how each of them would be handled with Deta: + +* **HTTPS**: Handled by Deta, they will give you a subdomain and handle HTTPS automatically. +* **Running on startup**: Handled by Deta, as part of their service. +* **Restarts**: Handled by Deta, as part of their service. +* **Replication**: Handled by Deta, as part of their service. +* **Memory**: Limit predefined by Deta, you could contact them to increase it. +* **Previous steps before starting**: Not directly supported, you could make it work with their Cron system or additional scripts. + +!!! note + Deta is designed to make it easy (and free) to deploy simple applications quickly. + + It can simplify a lot several use cases, but at the same time it doesn't support others, like using external databases (apart from Deta's own NoSQL database system), custom virtual machines, etc. + + You can read more details in the Deta docs to see if it's the right choice for you. diff --git a/docs/en/docs/deployment/docker.md b/docs/en/docs/deployment/docker.md index e32a2969..e25401f2 100644 --- a/docs/en/docs/deployment/docker.md +++ b/docs/en/docs/deployment/docker.md @@ -1,67 +1,144 @@ -# Deploy with Docker +# FastAPI in Containers - Docker -In this section you'll see instructions and links to guides to know how to: +When deploying FastAPI applications a common approach is to build a **Linux container image**. It's normally done using **Docker**. And then you can deploy that container image in one of different possible ways. -* Make your **FastAPI** application a Docker image/container with maximum performance. In about **5 min**. -* (Optionally) understand what you, as a developer, need to know about HTTPS. -* Set up a Docker Swarm mode cluster with automatic HTTPS, even on a simple $5 USD/month server. In about **20 min**. -* Generate and deploy a full **FastAPI** application, using your Docker Swarm cluster, with HTTPS, etc. In about **10 min**. - -You can use **Docker** for deployment. It has several advantages like security, replicability, development simplicity, etc. - -If you are using Docker, you can use the official Docker image: - -## tiangolo/uvicorn-gunicorn-fastapi - -This image has an "auto-tuning" mechanism included, so that you can just add your code and get very high performance automatically. And without making sacrifices. - -But you can still change and update all the configurations with environment variables or configuration files. +Using Linux containers has several advantages including **security**, **replicability**, **simplicity**, and others. !!! tip - To see all the configurations and options, go to the Docker image page: tiangolo/uvicorn-gunicorn-fastapi. + In a hurry and already know this stuff? Jump to the [`Dockerfile` below 👇](#build-a-docker-image-for-fastapi). -## Create a `Dockerfile` - -* Go to your project directory. -* Create a `Dockerfile` with: +
+Dockerfile Preview 👀 ```Dockerfile -FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7 +FROM python:3.9 -COPY ./app /app -``` +WORKDIR /code -### Bigger Applications +COPY ./requirements.txt /code/requirements.txt -If you followed the section about creating [Bigger Applications with Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}, your `Dockerfile` might instead look like: +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt -```Dockerfile -FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7 - -COPY ./app /app/app -``` - -### Raspberry Pi and other architectures - -If you are running Docker in a Raspberry Pi (that has an ARM processor) or any other architecture, you can create a `Dockerfile` from scratch, based on a Python base image (that is multi-architecture) and use Uvicorn alone. - -In this case, your `Dockerfile` could look like: - -```Dockerfile -FROM python:3.7 - -RUN pip install fastapi uvicorn - -EXPOSE 80 - -COPY ./app /app +COPY ./app /code/app CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] + +# If running behind a proxy like Nginx or Traefik add --proxy-headers +# CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80", "--proxy-headers"] ``` -## Create the **FastAPI** Code +
+ +## What is a Container + +Containers (mainly Linux containers) are a very **lightweight** way to package applications including all their dependencies and necessary files while keeping them isolated from other containers (other applications or components) in the same system. + +Linux containers run using the same Linux kernel of the host (machine, virtual machine, cloud server, etc). This just means that they are very lightweight (compared to full virtual machines emulating an entire operating system). + +This way, containers consume **little resources**, an amount comparable to running the processes directly (a virtual machine would consume much more). + +Containers also have their own **isolated** running processes (commonly just one process), file system, and network, simplifying deployment, security, development, etc. + +## What is a Container Image + +A **container** is run from a **container image**. + +A container image is a **static** version of all the files, environment variables, and the default command/program that should be present in a container. **Static** here means that the container **image** is not running, it's not being executed, it's only the packaged files and metadata. + +In contrast to a "**container image**" that is the stored static contents, a "**container**" normally refers to the running instance, the thing that is being **executed**. + +When the **container** is started and running (started from a **container image**) it could create or change files, environment variables, etc. Those changes will exist only in that container, but would not persist in the underlying container image (would not be saved to disk). + +A container image is comparable to the **program** file and contents, e.g. `python` and some file `main.py`. + +And the **container** itself (in contrast to the **container image**) is the actual running instance of the image, comparable to a **process**. In fact, a container is running only when it has a **process running** (and normally it's only a single process). The container stops when there's no process running in it. + +## Container Images + +Docker has been one of the main tools to create and manage **container images** and **containers**. + +And there's a public Docker Hub with pre-made **official container images** for many tools, environments, databases, and applications. + +For example, there's an official Python Image. + +And there are many other images for different things like databases, for example for: + +* PostgreSQL +* MySQL +* MongoDB +* Redis, etc. + +By using a pre-made container image it's very easy to **combine** and use different tools. For example, to try out a new database. In most cases you can use the **official images**, and just configure them with environment variables. + +That way, in many cases you can learn about containers and Docker and re-use that knowledge with many different tools and components. + +So, you would run **multiple containers** with different things, like a database, a Python application, a web server with a React frontend application, and connect them together via their internal network. + +All the container management systems (like Docker or Kubernetes) have these networking features integrated in them. + +## Containers and Processes + +A **container image** normally includes in its metadata the default program or command that should be run when the **container** is started and the parameters to be passed to that program. Very similar to what would be if it was in the command line. + +When a **container** is started, it will run that command/program (although you can override it and make it run a different command/program). + +A container is running as long as the **main process** (command or program) is running. + +A container normally has a **single process**, but it's also possible to start subprocesses from the main process, and that way have **multiple processes** in the same container. + +But it's not possible to have a running container without **at least one running process**. If the main process stops, the container stops. + +## Build a Docker Image for FastAPI + +Okay, let's build something now! 🚀 + +I'll show you how to build a **Docker image** for FastAPI **from scratch**, based on the **official Python** image. + +This is what you would want to do in **most cases**, for example: + +* Using **Kubernetes** or similar tools +* When running on a **Raspberry Pi** +* Using a cloud service that would run a container image for you, etc. + +### Package Requirements + +You would normally have the **package requirements** for your application in some file. + +It would depend mainly on the tool you use to **install** those requirements. + +The most common way to do it is to have a file `requirements.txt` with the package names and their versions, one per line. + +You would of course use the same ideas you read in [About FastAPI versions](./versions.md){.internal-link target=_blank} to set the ranges of versions. + +For example, your `requirements.txt` could look like: + +``` +fastapi>=0.68.0,<0.69.0 +pydantic>=1.8.0,<2.0.0 +uvicorn>=0.15.0,<0.16.0 +``` + +And you would normally install those package dependencies with `pip`, for example: + +
+ +```console +$ pip install -r requirements.txt +---> 100% +Successfully installed fastapi pydantic uvicorn +``` + +
+ +!!! info + There are other formats and tools to define and install package dependencies. + + I'll show you an example using Poetry later in a section below. 👇 + +### Create the **FastAPI** Code * Create an `app` directory and enter in it. +* Create an empty file `__init__.py`. * Create a `main.py` file with: ```Python @@ -82,16 +159,126 @@ def read_item(item_id: int, q: Optional[str] = None): return {"item_id": item_id, "q": q} ``` -* You should now have a directory structure like: +### Dockerfile + +Now in the same project directory create a file `Dockerfile` with: + +```{ .dockerfile .annotate } +# (1) +FROM python:3.9 + +# (2) +WORKDIR /code + +# (3) +COPY ./requirements.txt /code/requirements.txt + +# (4) +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt + +# (5) +COPY ./app /code/app + +# (6) +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] +``` + +1. Start from the official Python base image. + +2. Set the current working directory to `/code`. + + This is where we'll put the `requirements.txt` file and the `app` directory. + +3. Copy the file with the requirements to the `/code` directory. + + Copy **only** the file with the requirements first, not the rest of the code. + + As this file **doesn't change often**, Docker will detect it and use the **cache** for this step, enabling the cache for the next step too. + +4. Install the package dependencies in the requirements file. + + The `--no-cache-dir` option tells `pip` to not save the downloaded packages locally, as that is only if `pip` was going to be run again to install the same packages, but that's not the case when working with containers. + + !!! note + The `--no-cache-dir` is only related to `pip`, it has nothing to do with Docker or containers. + + The `--upgrade` option tells `pip` to upgrade the packages if they are already installed. + + Because the previous step copying the file could be detected by the **Docker cache**, this step will also **use the Docker cache** when available. + + Using the cache in this step will **save** you a lot of **time** when building the image again and again during development, instead of **downloading and installing** all the dependencies **every time**. + +5. Copy the `./app` directory inside the `/code` directory. + + As this has all the code which is what **changes most frequently** the Docker **cache** won't be used for this or any **following steps** easily. + + So, it's important to put this **near the end** of the `Dockerfile`, to optimize the container image build times. + +6. Set the **command** to run the `uvicorn` server. + + `CMD` takes a list of strings, each of this strings is what you would type in the command line separated by spaces. + + This command will be run from the **current working directory**, the same `/code` directory you set above with `WORKDIR /code`. + + Because the program will be started at `/code` and inside of it is the directory `./app` with your code, **Uvicorn** will be able to see and **import** `app` from `app.main`. + +!!! tip + Review what each line does by clicking each number bubble in the code. 👆 + +You should now have a directory structure like: ``` . ├── app +│   ├── __init__.py │ └── main.py -└── Dockerfile +├── Dockerfile +└── requirements.txt ``` -## Build the Docker image +#### Behind a TLS Termination Proxy + +If you are running your container behind a TLS Termination Proxy (load balancer) like Nginx or Traefik, add the option `--proxy-headers`, this will tell Uvicorn to trust the headers sent by that proxy telling it that the application is running behind HTTPS, etc. + +```Dockerfile +CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"] +``` + +#### Docker Cache + +There's an important trick in this `Dockerfile`, we first copy the **file with the dependencies alone**, not the rest of the code. Let me tell you why is that. + +```Dockerfile +COPY ./requirements.txt /code/requirements.txt +``` + +Docker and other tools **build** these container images **incrementally**, adding **one layer on top of the other**, starting from the top of the `Dockerfile` and adding any files created by each of the instructions of the `Dockerfile`. + +Docker and similar tools also use an **internal cache** when building the image, if a file hasn't changed since the last time building the container image, then it will **re-use the same layer** created the last time, instead of copying the file again and creating a new layer from scratch. + +Just avoiding the copy of files doesn't necessarily improve things too much, but because it used the cache for that step, it can **use the cache for the next step**. For example, it could use the cache for the instruction that installs dependencies with: + +```Dockerfile +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt +``` + +The file with the package requirements **won't change frequently**. So, by copying only that file, Docker will be able to **use the cache** for that step. + +And then, Docker will be able to **use the cache for the next step** that downloads and install those dependencies. And here's where we **save a lot of time**. ✨ ...and avoid boredom waiting. 😪😆 + +Downloading and installing the package dependencies **could take minutes**, but using the **cache** would **take seconds** at most. + +And as you would be building the container image again and again during development to check that your code changes are working, there's a lot of accumulated time this would save. + +Then, near the end of the `Dockerfile`, we copy all the code. As this is what **changes most frequently**, we put it near the end, because almost always, anything after this step will not be able to use the cache. + +```Dockerfile +COPY ./app /code/app +``` + +### Build the Docker Image + +Now that all the files are in place, let's build the container image. * Go to the project directory (in where your `Dockerfile` is, containing your `app` directory). * Build your FastAPI image: @@ -106,7 +293,12 @@ $ docker build -t myimage . -## Start the Docker container +!!! tip + Notice the `.` at the end, it's equivalent to `./`, it tells Docker the directory to use to build the container image. + + In this case, it's the same current directory (`.`). + +### Start the Docker Container * Run a container based on your image: @@ -118,8 +310,6 @@ $ docker run -d --name mycontainer -p 80:80 myimage -Now you have an optimized FastAPI server in a Docker container. Auto-tuned for your current server (and number of CPU cores). - ## Check it You should be able to check it in your Docker container's URL, for example: http://192.168.99.100/items/5?q=somequery or http://127.0.0.1/items/5?q=somequery (or equivalent, using your Docker host). @@ -146,34 +336,363 @@ You will see the alternative automatic documentation (provided by Traefik is a high performance reverse proxy / load balancer. It can do the "TLS Termination Proxy" job (apart from other features). +If your FastAPI is a single file, for example `main.py` without an `./app` directory, your file structure could look like: -It has integration with Let's Encrypt. So, it can handle all the HTTPS parts, including certificate acquisition and renewal. +``` +. +├── Dockerfile +├── main.py +└── requirements.txt +``` -It also has integrations with Docker. So, you can declare your domains in each application configurations and have it read those configurations, generate the HTTPS certificates and serve HTTPS to your application automatically, without requiring any change in its configuration. +Then you would just have to change the corresponding paths to copy the file inside the `Dockerfile`: + +```{ .dockerfile .annotate hl_lines="10 13" } +FROM python:3.9 + +WORKDIR /code + +COPY ./requirements.txt /code/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt + +# (1) +COPY ./main.py /code/ + +# (2) +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"] +``` + +1. Copy the `main.py` file to the `/code` directory directly (without any `./app` directory). + +2. Run Uvicorn and tell it to import the `app` object from `main` (instead of importing from `app.main`). + +Then adjust the Uvicorn command to use the new module `main` instead of `app.main` to import the FastAPI object `app`. + +## Deployment Concepts + +Let's talk again about some of the same [Deployment Concepts](./concepts.md){.internal-link target=_blank} in terms of containers. + +Containers are mainly a tool to simplify the process of **building and deploying** an application, but they don't enforce a particular approach to handle these **deployment concepts**, and there are several possible strategies. + +The **good news** is that with each different strategy there's a way to cover all of the deployment concepts. 🎉 + +Let's review these **deployment concepts** in terms of containers: + +* HTTPS +* Running on startup +* Restarts +* Replication (the number of processes running) +* Memory +* Previous steps before starting + +## HTTPS + +If we focus just on the **container image** for a FastAPI application (and later the running **container**), HTTPS normally would be handled **externally** by another tool. + +It could be another container, for example with Traefik, handling **HTTPS** and **automatic** acquisition of **certificates**. + +!!! tip + Traefik has integrations with Docker, Kubernetes, and others, so it's very easy to set up and configure HTTPS for your containers with it. + +Alternatively, HTTPS could be handled by a cloud provider as one of their services (while still running the application in a container). + +## Running on Startup and Restarts + +There is normally another tool in charge of **starting and running** your container. + +It could be **Docker** directly, **Docker Compose**, **Kubernetes**, a **cloud service**, etc. + +In most (or all) cases, there's a simple option to enable running the container on startup and enabling restarts on failures. For example, in Docker, it's the command line option `--restart`. + +Without using containers, making applications run on startup and with restarts can be cumbersome and difficult. But when **working with containers** in most cases that functionality is included by default. ✨ + +## Replication - Number of Processes + +If you have a cluster of machines with **Kubernetes**, Docker Swarm Mode, Nomad, or other similar complex system to manage distributed containers on multiple machines, then you will probably want to **handle replication** at the **cluster level** instead of using a **process manager** (like Gunicorn with workers) in each container. + +One of those distributed container management systems like Kubernetes normally has some integrated way of handling **replication of containers** while still supporting **load balancing** for the incoming requests. All at the **cluster level**. + +In those cases, you would probably want to build a **Docker image from scratch** as [explained above](#dockerfile), installing your dependencies, and running **a single Uvicorn process** instead of running something like Gunicorn with Uvicorn workers. + +### Load Balancer + +When using containers, you would normally have some component **listening on the main port**. It could possibly be another container that is also a **TLS Termination Proxy** to handle **HTTPS** or some similar tool. + +As this component would take the **load** of requests and distribute that among the workers in a (hopefully) **balanced** way, it is also commonly called a **Load Balancer**. + +!!! tip + The same **TLS Termination Proxy** component used for HTTPS would probably also be a **Load Balancer**. + +And when working with containers, the same system you use to start and manage them would already have internal tools to transmit the **network communication** (e.g. HTTP requests) from that **load balancer** (that could also be a **TLS Termination Proxy**) to the container(s) with your app. + +### One Load Balancer - Multiple Worker Containers + +When working with **Kubernetes** or similar distributed container management systems, using their internal networking mechanisms would allow the single **load balancer** that is listening on the main **port** to transmit communication (requests) to possibly **multiple containers** running your app. + +Each of these containers running your app would normally have **just one process** (e.g. a Uvicorn process running your FastAPI application). They would all be **identical containers**, running the same thing, but each with its own process, memory, etc. That way you would take advantage of **parallelization** in **different cores** of the CPU, or even in **different machines**. + +And the distributed container system with the **load balancer** would **distribute the requests** to each one of the containers with your app **in turns**. So, each request could be handled by one of the multiple **replicated containers** running your app. + +And normally this **load balancer** would be able to handle requests that go to *other* apps in your cluster (e.g. to a different domain, or under a different URL path prefix), and would transmit that communication to the right containers for *that other* application running in your cluster. + +### One Process per Container + +In this type of scenario, you probably would want to have **a single (Uvicorn) process per container**, as you would already be handling replication at the cluster level. + +So, in this case, you **would not** want to have a process manager like Gunicorn with Uvicorn workers, or Uvicorn using its own Uvicorn workers. You would want to have just a **single Uvicorn process** per container (but probably multiple containers). + +Having another process manager inside the container (as would be with Gunicorn or Uvicorn managing Uvicorn workers) would only add **unnecessary complexity** that you are most probably already taking care of with your cluster system. + +### Containers with Multiple Processes and Special Cases + +Of course, there are **special cases** where you could want to have **a container** with a **Gunicorn process manager** starting several **Uvicorn worker processes** inside. + +In those cases, you can use the **official Docker image** that includes **Gunicorn** as a process manager running multiple **Uvicorn worker processes**, and some default settings to adjust the number of workers based on the current CPU cores automatically. I'll tell you more about it below in [Official Docker Image with Gunicorn - Uvicorn](#official-docker-image-with-gunicorn-uvicorn). + +Here are some examples of when that could make sense: + +#### A Simple App + +You could want a process manager in the container if your application is **simple enough** that you don't need (at least not yet) to fine-tune the number of processes too much, and you can just use an automated default (with the official Docker image), and you are running it on a **single server**, not a cluster. + +#### Docker Compose + +You could be deploying to a **single server** (not a cluster) with **Docker Compose**, so you wouldn't have an easy way to manage replication of containers (with Docker Compose) while preserving the shared network and **load balancing**. + +Then you could want to have **a single container** with a **process manager** starting **several worker processes** inside. + +#### Prometheus and Other Reasons + +You could also have **other reasons** that would make it easier to have a **single container** with **multiple processes** instead of having **multiple containers** with **a single process** in each of them. + +For example (depending on your setup) you could have some tool like a Prometheus exporter in the same container that should have access to **each of the requests** that come. + +In this case, if you had **multiple containers**, by default, when Prometheus came to **read the metrics**, it would get the ones for **a single container each time** (for the container that handled that particular request), instead of getting the **accumulated metrics** for all the replicated containers. + +Then, in that case, it could be simpler to have **one container** with **multiple processes**, and a local tool (e.g. a Prometheus exporter) on the same container collecting Prometheus metrics for all the internal processes and exposing those metrics on that single container. --- -With this information and tools, continue with the next section to combine everything. +The main point is, **none** of these are **rules written in stone** that you have to blindly follow. You can use these ideas to **evaluate your own use case** and decide what is the best approach for your system, checking out how to manage the concepts of: -## Docker Swarm mode cluster with Traefik and HTTPS +* Security - HTTPS +* Running on startup +* Restarts +* Replication (the number of processes running) +* Memory +* Previous steps before starting -You can have a Docker Swarm mode cluster set up in minutes (about 20 min) with a main Traefik handling HTTPS (including certificate acquisition and renewal). +## Memory -By using Docker Swarm mode, you can start with a "cluster" of a single machine (it can even be a $5 USD / month server) and then you can grow as much as you need adding more servers. +If you run **a single process per container** you will have a more or less well defined, stable, and limited amount of memory consumed by each of of those containers (more than one if they are replicated). -To set up a Docker Swarm Mode cluster with Traefik and HTTPS handling, follow this guide: +And then you can set those same memory limits and requirements in your configurations for your container management system (for example in **Kubernetes**). That way it will be able to **replicate the containers** in the **available machines** taking into account the amount of memory needed by them, and the amount available in the machines in the cluster. -### Docker Swarm Mode and Traefik for an HTTPS cluster +If your application is **simple**, this will probably **not be a problem**, and you might not need to specify hard memory limits. But if you are **using a lot of memory** (for example with **machine learning** models), you should check how much memory you are consuming and adjust the **number of containers** that runs in **each machine** (and maybe add more machines to your cluster). -### Deploy a FastAPI application +If you run **multiple processes per container** (for example with the official Docker image) you will have to make sure that the number of processes started doesn't **consume more memory** than what is available. -The easiest way to set everything up, would be using the [**FastAPI** Project Generators](../project-generation.md){.internal-link target=_blank}. +## Previous Steps Before Starting and Containers -It is designed to be integrated with this Docker Swarm cluster with Traefik and HTTPS described above. +If you are using containers (e.g. Docker, Kubernetes), then there are two main approaches you can use. -You can generate a project in about 2 min. +### Multiple Containers -The generated project has instructions to deploy it, doing it takes another 2 min. +If you have **multiple containers**, probably each one running a **single process** (for example, in a **Kubernetes** cluster), then you would probably want to have a **separate container** doing the work of the **previous steps** in a single container, running a single process, **before** running the replicated worker containers. + +!!! info + If you are using Kubernetes, this would probably be an Init Container. + +If in your use case there's no problem in running those previous steps **multiple times in parallel** (for example if you are not running database migrations, but just checking if the database is ready yet), then you could also just put them in each container right before starting the main process. + +### Single Container + +If you have a simple setup, with a **single container** that then starts multiple **worker processes** (or also just one process), then you could run those previous steps in the same container, right before starting the process with the app. The official Docker image supports this internally. + +## Official Docker Image with Gunicorn - Uvicorn + +There is an official Docker image that includes Gunicorn running with Uvicorn workers, as detailed in a previous chapter: [Server Workers - Gunicorn with Uvicorn](./server-workers.md){.internal-link target=_blank}. + +This image would be useful mainly in the situations described above in: [Containers with Multiple Processes and Special Cases](#containers-with-multiple-processes-and-special-cases). + +* tiangolo/uvicorn-gunicorn-fastapi. + +!!! warning + There's a high chance that you **don't** need this base image or any other similar one, and would be better off by building the image from scratch as [described above in: Build a Docker Image for FastAPI](#build-a-docker-image-for-fastapi). + +This image has an **auto-tuning** mechanism included to set the **number of worker processes** based on the CPU cores available. + +It has **sensible defaults**, but you can still change and update all the configurations with **environment variables** or configuration files. + +It also supports running **previous steps before starting** with a script. + +!!! tip + To see all the configurations and options, go to the Docker image page: tiangolo/uvicorn-gunicorn-fastapi. + +### Number of Processes on the Official Docker Image + +The **number of processes** on this image is **computed automatically** from the CPU **cores** available. + +This means that it will try to **squeeze** as much **performance** from the CPU as possible. + +You can also adjust it with the configurations using **environment variables**, etc. + +But it also means that as the number of processes depends on the CPU the container is running, the **amount of memory consumed** will also depend on that. + +So, if your application consumes a lot of memory (for example with machine learning models), and your server has a lot of CPU cores **but little memory**, then your container could end up trying to use more memory than what is available, and degrading performance a lot (or even crashing). 🚨 + +### Create a `Dockerfile` + +Here's how you would create a `Dockerfile` based on this image: + +```Dockerfile +FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9 + +COPY ./requirements.txt /app/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt + +COPY ./app /app +``` + +### Bigger Applications + +If you followed the section about creating [Bigger Applications with Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}, your `Dockerfile` might instead look like: + +```Dockerfile hl_lines="7" +FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9 + +COPY ./requirements.txt /app/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt + +COPY ./app /app/app +``` + +### When to Use + +You should probably **not** use this official base image (or any other similar one) if you are using **Kubernetes** (or others) and you are already setting **replication** at the cluster level, with multiple **containers**. In those cases, you are better off **building an image from scratch** as described above: [Build a Docker Image for FastAPI](#build-a-docker-image-for-fastapi). + +This image would be useful mainly in the special cases described above in [Containers with Multiple Processes and Special Cases](#containers-with-multiple-processes-and-special-cases). For example, if your application is **simple enough** that setting a default number of processes based on the CPU works well, you don't want to bother with manually configuring the replication at the cluster level, and you are not running more than one container with your app. Or if you are deploying with **Docker Compose**, running on a single server, etc. + +## Deploy the Container Image + +After having a Container (Docker) Image there are several ways to deploy it. + +For example: + +* With **Docker Compose** in a single server +* With a **Kubernetes** cluster +* With a Docker Swarm Mode cluster +* With another tool like Nomad +* With a cloud service that takes your container image and deploys it + +## Docker Image with Poetry + +If you use Poetry to manage your project's dependencies, you could use Docker multi-stage building: + +```{ .dockerfile .annotate } +# (1) +FROM python:3.9 as requirements-stage + +# (2) +WORKDIR /tmp + +# (3) +RUN pip install poetry + +# (4) +COPY ./pyproject.toml ./poetry.lock* /tmp/ + +# (5) +RUN poetry export -f requirements.txt --output requirements.txt --without-hashes + +# (6) +FROM python:3.9 + +# (7) +WORKDIR /code + +# (8) +COPY --from=requirements-stage /tmp/requirements.txt /code/requirements.txt + +# (9) +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt + +# (10) +COPY ./app /code/app + +# (11) +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] +``` + +1. This is the first stage, it is named `requirements-stage`. + +2. Set `/tmp` as the current working directory. + + Here's where we will generate the file `requirements.txt` + +3. Install Poetry in this Docker stage. + +4. Copy the `pyproject.toml` and `poetry.lock` files to the `/tmp` directory. + + Because it uses `./poetry.lock*` (ending with a `*`), it won't crash if that file is not available yet. + +5. Generate the `requirements.txt` file. + +6. This is the final stage, anything here will be preserved in the final container image. + +7. Set the current working directory to `/code`. + +8. Copy the `requirements.txt` file to the `/code` directory. + + This file only lives in the previous Docker stage, that's why we use `--from-requirements-stage` to copy it. + +9. Install the package dependencies in the generated `requirements.txt` file. + +10. Copy the `app` directory to the `/code` directory. + +11. Run the `uvicorn` command, telling it to use the `app` object imported from `app.main`. + +!!! tip + Click the bubble numbers to see what each line does. + +A **Docker stage** is a part of a `Dockerfile` that works as a **temporary container image** that is only used to generate some files to be used later. + +The first stage will only be used to **install Poetry** and to **generate the `requirements.txt`** with your project dependencies from Poetry's `pyproject.toml` file. + +This `requirements.txt` file will be used with `pip` later in the **next stage**. + +In the final container image **only the final stage** is preserved. The previous stage(s) will be discarded. + +When using Poetry, it would make sense to use **Docker multi-stage builds** because you don't really need to have Poetry and its dependencies installed in the final container image, you **only need** to have the generated `requirements.txt` file to install your project dependencies. + +Then in the next (and final) stage you would build the image more or less in the same way as described before. + +### Behind a TLS Termination Proxy - Poetry + +Again, if you are running your container behind a TLS Termination Proxy (load balancer) like Nginx or Traefik, add the option `--proxy-headers` to the command: + +```Dockerfile +CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"] +``` + +## Recap + +Using container systems (e.g. with **Docker** and **Kubernetes**) it becomes fairly straightforward to handle all the **deployment concepts**: + +* HTTPS +* Running on startup +* Restarts +* Replication (the number of processes running) +* Memory +* Previous steps before starting + +In most cases, you probably won't want to use any base image, and instead **build a container image from scratch** one based on the official Python Docker image. + +Taking care of the **order** of instructions in the `Dockerfile` and the **Docker cache** you can **minimize build times**, to maximize your productivity (and avoid boredom). 😎 + +In certain special cases, you might want to use the official Docker image for FastAPI. 🤓 diff --git a/docs/en/docs/deployment/index.md b/docs/en/docs/deployment/index.md index d898cfef..d1941ad9 100644 --- a/docs/en/docs/deployment/index.md +++ b/docs/en/docs/deployment/index.md @@ -2,6 +2,20 @@ Deploying a **FastAPI** application is relatively easy. +## What Does Deployment Mean + +To **deploy** an application means to perform the necessary steps to make it **available to the users**. + +For a **web API**, it normally involves putting it in a **remote machine**, with a **server program** that provides good performance, stability, etc, so that your **users** can **access** the application efficiently and without interruptions or problems. + +This is in contrast to the the **development** stages, where you are constantly changing the code, breaking it and fixing it, stopping and restarting the development server, etc. + +## Deployment Strategies + There are several ways to do it depending on your specific use case and the tools that you use. -You will see more details to have in mind and some of the techniques to do it in the next sections. +You could **deploy a server** yourself using a combination of tools, you could use a **cloud service** that does part of the work for you, or other possible options. + +I will show you some of the main concepts you should probably have in mind when deploying a **FastAPI** application (although most of it applies to any other type of web application). + +You will see more details to have in mind and some of the techniques to do it in the next sections. ✨ diff --git a/docs/en/docs/deployment/manually.md b/docs/en/docs/deployment/manually.md index daa31a30..80a7df7e 100644 --- a/docs/en/docs/deployment/manually.md +++ b/docs/en/docs/deployment/manually.md @@ -1,8 +1,26 @@ -# Deploy manually +# Run a Server Manually - Uvicorn -You can deploy **FastAPI** manually as well. +The main thing you need to run a **FastAPI** application in a remote server machine is an ASGI server program like **Uvicorn**. -You just need to install an ASGI compatible server like: +There are 3 main alternatives: + +* Uvicorn: a high performance ASGI server. +* Hypercorn: an ASGI server compatible with HTTP/2 and Trio among other features. +* Daphne: the ASGI server built for Django Channels. + +## Server Machine and Server Program + +There's a small detail about names to have in mind. 💡 + +The word "**server**" is commonly used to refer to both the remote/cloud computer (the physical or virtual machine) and also the program that is running on that machine (e.g. Uvicorn). + +Just have that in mind when you read "server" in general, it could refer to one of those two things. + +When referring to the remote machine, it's common to call it **server**, but also **machine**, **VM** (virtual machine), **node**. Those all refer to some type of remote machine, normally running Linux, where you run programs. + +## Install the Server Program + +You can install an ASGI compatible server with: === "Uvicorn" @@ -39,7 +57,9 @@ You just need to install an ASGI compatible server like: ...or any other ASGI server. -And run your application the same way you have done in the tutorials, but without the `--reload` option, e.g.: +## Run the Server Program + +You can then your application the same way you have done in the tutorials, but without the `--reload` option, e.g.: === "Uvicorn" @@ -65,10 +85,24 @@ And run your application the same way you have done in the tutorials, but withou -You might want to set up some tooling to make sure it is restarted automatically if it stops. +!!! warning + Remember to remove the `--reload` option if you were using it. -You might also want to install Gunicorn and use it as a manager for Uvicorn, or use Hypercorn with multiple workers. + The `--reload` option consumes much more resources, is more unstable, etc. + + It helps a lot during **development**, but you **shouldn't** use it in **production**. -Making sure to fine-tune the number of workers, etc. +## Deployment Concepts -But if you are doing all that, you might just use the Docker image that does it automatically. +These examples run the server program (e.g Uvicorn), starting **a single process**, listening on all the IPs (`0.0.0.0`) on a predefined port (e.g. `80`). + +This is the basic idea. But you will probably want to take care of some additional things, like: + +* Security - HTTPS +* Running on startup +* Restarts +* Replication (the number of processes running) +* Memory +* Previous steps before starting + +I'll tell you more about each of these concepts, how to think about them, and some concrete examples with strategies to handle them in the next chapters. 🚀 diff --git a/docs/en/docs/deployment/server-workers.md b/docs/en/docs/deployment/server-workers.md new file mode 100644 index 00000000..2892d579 --- /dev/null +++ b/docs/en/docs/deployment/server-workers.md @@ -0,0 +1,178 @@ +# Server Workers - Gunicorn with Uvicorn + +Let's check back those deployment concepts from before: + +* Security - HTTPS +* Running on startup +* Restarts +* **Replication (the number of processes running)** +* Memory +* Previous steps before starting + +Up to this point, with all the tutorials in the docs, you have probably been running a **server program** like Uvicorn, running a **single process**. + +When deploying applications you will probably want to have some **replication of processes** to take advantage of **multiple cores** and to be able to handle more requests. + +As you saw in the previous chapter about [Deployment Concepts](./concepts.md){.internal-link target=_blank}, there are multiple strategies you can use. + +Here I'll show you how to use **Gunicorn** with **Uvicorn worker processes**. + +!!! info + If you are using containers, for example with Docker or Kubernetes, I'll tell you more about that in the next chapter: [FastAPI in Containers - Docker](./docker.md){.internal-link target=_blank}. + + In particular, when running on **Kubernetes** you will probably **not** want to use Gunicorn, and instead run **a single Uvicorn process per container**, but I'll tell you about it later in that chapter. + +## Gunicorn with Uvicorn Workers + +**Gunicorn** is mainly an application server using the **WSGI standard**. That means that Gunicorn can serve applications like Flask and Django. Gunicorn by itself is not compatible with **FastAPI**, as FastAPI uses the newest **ASGI standard**. + +But Gunicorn supports working as a **process manager** and allowing users to tell it which specific **worker process class** to use. Then Gunicorn would start one or more **worker processes** using that class. + +And **Uvicorn** has a **Gunicorn-compatible worker class**. + +Using that combination, Gunicorn would act as a **process manager**, listening on the **port** and the **IP**. And it would **transmit** the communication to the worker processes running the **Uvicorn class**. + +And then the Gunicorn-compatible **Uvicorn worker** class would be in charge of converting the data sent by Gunicorn to the ASGI standard for FastAPI to use it. + +## Install Gunicorn and Uvicorn + +
+ +```console +$ pip install uvicorn[standard] gunicorn + +---> 100% +``` + +
+ +That will install both Uvicorn with the `standard` extra packages (to get high performance) and Gunicorn. + +## Run Gunicorn with Uvicorn Workers + +Then you can run Gunicorn with: + +
+ +```console +$ gunicorn main:app --workers 4 --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:80 + +[19499] [INFO] Starting gunicorn 20.1.0 +[19499] [INFO] Listening at: http://0.0.0.0:80 (19499) +[19499] [INFO] Using worker: uvicorn.workers.UvicornWorker +[19511] [INFO] Booting worker with pid: 19511 +[19513] [INFO] Booting worker with pid: 19513 +[19514] [INFO] Booting worker with pid: 19514 +[19515] [INFO] Booting worker with pid: 19515 +[19511] [INFO] Started server process [19511] +[19511] [INFO] Waiting for application startup. +[19511] [INFO] Application startup complete. +[19513] [INFO] Started server process [19513] +[19513] [INFO] Waiting for application startup. +[19513] [INFO] Application startup complete. +[19514] [INFO] Started server process [19514] +[19514] [INFO] Waiting for application startup. +[19514] [INFO] Application startup complete. +[19515] [INFO] Started server process [19515] +[19515] [INFO] Waiting for application startup. +[19515] [INFO] Application startup complete. +``` + +
+ +Let's see what each of those options mean: + +* `main:app`: This is the same syntax used by Uvicorn, `main` means the Python module named "`main`", so, a file `main.py`. And `app` is the name of the variable that is the **FastAPI** application. + * You can imagine that `main:app` is equivalent to a Python `import` statement like: + + ```Python + from main import app + ``` + + * So, the colon in `main:app` would be equivalent to the Python `import` part in `from main import app`. +* `--workers`: The number of worker processes to use, each will run a Uvicorn worker, in this case 4 workers. +* `--worker-class`: The Gunicorn-compatible worker class to use in the worker processes. + * Here we pass the class that Gunicorn can import and use with: + + ```Python + import uvicorn.workers.UvicornWorker + ``` + +* `--bind`: This tells Gunicorn the IP and the port to listen to, using a colon (`:`) to separate the IP and the port. + * If you were running Uvicorn directly, instead of `--bind 0.0.0.0:80` (the Gunicorn option) you would use `--host 0.0.0.0` and `--port 80`. + +In the output you can see that it shows the **PID** (process ID) of each process (it's just a number). + +You can see that: + +* The Gunicorn **process manager** starts with PID `19499` (in your case it will be a different number). +* Then it starts `Listening at: http://0.0.0.0:80`. +* Then it detects that it has to use the worker class at `uvicorn.workers.UvicornWorker`. +* And then it starts **4 workers**, each with its own PID: `19511`, `19513`, `19514`, and `19515`. + +Gunicorn would also take care of managing **dead processes** and **restarting** new ones if needed to keep the number of workers. So that helps in part with the **restart** concept from the list above. + +Nevertheless, you would probably also want to have something outside making sure to **restart Gunicorn** if necessary, and also to **run it on startup**, etc. + +## Uvicorn with Workers + +Uvicorn also has an option to start and run several **worker processes**. + +Nevertheless, as of now, Uvicorn's capabilities for handling worker processes are more limited than Gunicorn's. So, if you want to have a process manager at this level (at the Python level), then it might be better to try with Gunicorn as the process manager. + +In any case, you would run it like this: + +
+ +```console +$ uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4 +INFO: Uvicorn running on http://0.0.0.0:8080 (Press CTRL+C to quit) +INFO: Started parent process [27365] +INFO: Started server process [27368] +INFO: Waiting for application startup. +INFO: Application startup complete. +INFO: Started server process [27369] +INFO: Waiting for application startup. +INFO: Application startup complete. +INFO: Started server process [27370] +INFO: Waiting for application startup. +INFO: Application startup complete. +INFO: Started server process [27367] +INFO: Waiting for application startup. +INFO: Application startup complete. +``` + +
+ +The only new option here is `--workers` telling Uvicorn to start 4 worker processes. + +You can also see that it shows the **PID** of each process, `27365` for the parent process (this is the **process manager**) and one for each worker process: `27368`, `27369`, `27370`, and `27367`. + +## Deployment Concepts + +Here you saw how to use **Gunicorn** (or Uvicorn) managing **Uvicorn worker processes** to **parallelize** the execution of the application, take advantage of **multiple cores** in the CPU, and be able to serve **more requests**. + +From the list of deployment concepts from above, using workers would mainly help with the **replication** part, and a little bit with the **restarts**, but you still need to take care of the others: + +* **Security - HTTPS** +* **Running on startup** +* ***Restarts*** +* Replication (the number of processes running) +* **Memory** +* **Previous steps before starting** + +## Containers and Docker + +In the next chapter about [FastAPI in Containers - Docker](./docker.md){.internal-link target=_blank} I'll tell some strategies you could use to handle the other **deployment concepts**. + +I'll also show you the **official Docker image** that includes **Gunicorn with Uvicorn workers** and some default configurations that can be useful for simple cases. + +There I'll also show you how to **build your own image from scratch** to run a single Uvicorn process (without Gunicorn). It is a simple process and is probably what you would want to do when using a distributed container management system like **Kubernetes**. + +## Recap + +You can use **Gunicorn** (or also Uvicorn) as a process manager with Uvicorn workers to take advantage of **multi-core CPUs**, to run **multiple processes in parallel**. + +You could use these tools and ideas if you are setting up **your own deployment system** while taking care of the other deployment concepts yourself. + +Check out the next chapter to learn about **FastAPI** with containers (e.g. Docker and Kubernetes). You will see that those tools have simple ways to solve the other **deployment concepts** as well. ✨ diff --git a/docs/en/docs/img/deployment/concepts/image01.png b/docs/en/docs/img/deployment/concepts/image01.png new file mode 100644 index 0000000000000000000000000000000000000000..fdce75e983509b17ff6fdb0a1bef0f2806791163 GIT binary patch literal 124827 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 s!MNjY0r`9Ne+uG#|5C + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/en/docs/img/deployment/concepts/process-ram.svg b/docs/en/docs/img/deployment/concepts/process-ram.svg new file mode 100644 index 00000000..cd086c36 --- /dev/null +++ b/docs/en/docs/img/deployment/concepts/process-ram.svg @@ -0,0 +1,59 @@ +
Server
Server
RAM
RAM +
CPU
CPU +
Process Manager
Process Manager
Worker Process
Worker Process
Worker Process
Worker Process
Another Process
Another Process
1 GB
1 GB
1 GB
1 GB
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/en/mkdocs.yml b/docs/en/mkdocs.yml index a11ac687..b3246878 100644 --- a/docs/en/mkdocs.yml +++ b/docs/en/mkdocs.yml @@ -147,9 +147,11 @@ nav: - deployment/index.md - deployment/versions.md - deployment/https.md - - deployment/deta.md - - deployment/docker.md - deployment/manually.md + - deployment/concepts.md + - deployment/deta.md + - deployment/server-workers.md + - deployment/docker.md - project-generation.md - alternatives.md - history-design-future.md From 1b6350ad9e67cd700ecec2d360bd06b946c3231c Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 1 Oct 2021 20:44:53 +0000 Subject: [PATCH 014/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index f67b11f0..7206c6a6 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 📝 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). * 🌐 Add German translation for `docs/features.md`. PR [#3699](https://github.com/tiangolo/fastapi/pull/3699) by [@mawassk](https://github.com/mawassk). * 🎨 Tweak CSS styles for shell animations. PR [#3888](https://github.com/tiangolo/fastapi/pull/3888) by [@tiangolo](https://github.com/tiangolo). From d29aa3d436849c7abb7c5fd6988ef4531b08f9e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 2 Oct 2021 12:16:55 +0200 Subject: [PATCH 015/196] =?UTF-8?q?=E2=9C=A8=20Add=20Deepset=20Sponsorship?= =?UTF-8?q?=20(#3976)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + docs/en/data/sponsors.yml | 3 + docs/en/data/sponsors_badge.yml | 1 + .../en/docs/img/sponsors/haystack-fastapi.svg | 192 ++++++++++++++++++ 4 files changed, 197 insertions(+) create mode 100644 docs/en/docs/img/sponsors/haystack-fastapi.svg diff --git a/README.md b/README.md index bdc8c3b0..233e8ed5 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ The key features are: + diff --git a/docs/en/data/sponsors.yml b/docs/en/data/sponsors.yml index e0a4ee78..4949e6c5 100644 --- a/docs/en/data/sponsors.yml +++ b/docs/en/data/sponsors.yml @@ -18,6 +18,9 @@ silver: - 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 bronze: - url: https://calmcode.io title: Code. Simply. Clearly. Calmly. diff --git a/docs/en/data/sponsors_badge.yml b/docs/en/data/sponsors_badge.yml index 9e95a625..0496c627 100644 --- a/docs/en/data/sponsors_badge.yml +++ b/docs/en/data/sponsors_badge.yml @@ -5,3 +5,4 @@ logins: - vimsoHQ - mikeckennedy - koaning + - deepset-ai diff --git a/docs/en/docs/img/sponsors/haystack-fastapi.svg b/docs/en/docs/img/sponsors/haystack-fastapi.svg new file mode 100644 index 00000000..6303ba61 --- /dev/null +++ b/docs/en/docs/img/sponsors/haystack-fastapi.svg @@ -0,0 +1,192 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 1db0fc29535f91fd026d3e439b9663a4ca6a44b1 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 2 Oct 2021 10:17:32 +0000 Subject: [PATCH 016/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 7206c6a6..062f295f 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ✨ Add Deepset Sponsorship. PR [#3976](https://github.com/tiangolo/fastapi/pull/3976) 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). * 🌐 Add German translation for `docs/features.md`. PR [#3699](https://github.com/tiangolo/fastapi/pull/3699) by [@mawassk](https://github.com/mawassk). From 4b968c4e3907066a994e125de1fa90768e9c9188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 3 Oct 2021 20:00:28 +0200 Subject: [PATCH 017/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20GraphQL=20docs,?= =?UTF-8?q?=20recommend=20Strawberry=20(#3981)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +- docs/de/docs/features.md | 1 - docs/de/docs/index.md | 2 -- docs/en/docs/advanced/graphql.md | 62 +++++++++++++++++++------------- docs/en/docs/alternatives.md | 3 +- docs/en/docs/features.md | 1 - docs/en/docs/index.md | 3 +- docs_src/graphql/tutorial001.py | 26 ++++++++++---- 8 files changed, 59 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 233e8ed5..5bdd9e6d 100644 --- a/README.md +++ b/README.md @@ -418,9 +418,9 @@ For a more complete example including more features, see the Dependency Injection** system. * Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth. * More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic). +* **GraphQL** integration with Strawberry and other libraries. * Many extra features (thanks to Starlette) as: * **WebSockets** - * **GraphQL** * extremely easy tests based on `requests` and `pytest` * **CORS** * **Cookie Sessions** @@ -447,7 +447,6 @@ Used by Starlette: * 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: diff --git a/docs/de/docs/features.md b/docs/de/docs/features.md index e5b38616..f56257e7 100644 --- a/docs/de/docs/features.md +++ b/docs/de/docs/features.md @@ -167,7 +167,6 @@ Mit **FastAPI** bekommen Sie viele von **Starlette**'s Funktionen (da FastAPI nu * Stark beeindruckende Performanz. Es ist eines der schnellsten Python frameworks, auf Augenhöhe mit **NodeJS** und **Go**. * **WebSocket**-Unterstützung. -* **GraphQL**-Unterstützung. * Hintergrundaufgaben im selben Prozess. * Ereignisse für das Starten und Herunterfahren. * Testclient basierend auf `requests`. diff --git a/docs/de/docs/index.md b/docs/de/docs/index.md index 95fb7ae2..d09ce70a 100644 --- a/docs/de/docs/index.md +++ b/docs/de/docs/index.md @@ -425,7 +425,6 @@ For a more complete example including more features, see the 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: 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/alternatives.md b/docs/en/docs/alternatives.md index 8b1fa778..d2792eb0 100644 --- a/docs/en/docs/alternatives.md +++ b/docs/en/docs/alternatives.md @@ -367,7 +367,6 @@ It has: * Seriously impressive performance. * WebSocket support. -* GraphQL support. * In-process background tasks. * Startup and shutdown events. * Test client built on requests. @@ -375,7 +374,7 @@ It has: * Session and Cookie support. * 100% test coverage. * 100% type annotated codebase. -* Zero hard dependencies. +* Few hard dependencies. Starlette is currently the fastest Python framework tested. Only surpassed by Uvicorn, which is not a framework, but a server. diff --git a/docs/en/docs/features.md b/docs/en/docs/features.md index c92795d4..36f80783 100644 --- a/docs/en/docs/features.md +++ b/docs/en/docs/features.md @@ -164,7 +164,6 @@ With **FastAPI** you get all of **Starlette**'s features (as FastAPI is just Sta * Seriously impressive performance. It is one of the fastest Python frameworks available, on par with **NodeJS** and **Go**. * **WebSocket** support. -* **GraphQL** support. * In-process background tasks. * Startup and shutdown events. * Test client built on `requests`. diff --git a/docs/en/docs/index.md b/docs/en/docs/index.md index 33aadb8e..ef593153 100644 --- a/docs/en/docs/index.md +++ b/docs/en/docs/index.md @@ -419,9 +419,9 @@ For a more complete example including more features, see the Dependency Injection** system. * Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth. * More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic). +* **GraphQL** integration with Strawberry and other libraries. * Many extra features (thanks to Starlette) as: * **WebSockets** - * **GraphQL** * extremely easy tests based on `requests` and `pytest` * **CORS** * **Cookie Sessions** @@ -448,7 +448,6 @@ Used by Starlette: * 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: 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) From 54c02d402a47cccb337652f033c6ad2e852e768d Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 3 Oct 2021 18:01:11 +0000 Subject: [PATCH 018/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 062f295f..cf503d50 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 📝 Update GraphQL docs, recommend Strawberry. PR [#3981](https://github.com/tiangolo/fastapi/pull/3981) by [@tiangolo](https://github.com/tiangolo). * ✨ Add Deepset Sponsorship. PR [#3976](https://github.com/tiangolo/fastapi/pull/3976) 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). From 099c4786554be070eadf47edf80bf4071bccdb47 Mon Sep 17 00:00:00 2001 From: Andy Challis Date: Mon, 4 Oct 2021 05:13:47 +1100 Subject: [PATCH 019/196] =?UTF-8?q?=F0=9F=92=9A=20Fix=20badges=20in=20READ?= =?UTF-8?q?ME=20and=20main=20page=20(#3979)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Anthony Lukach Co-authored-by: Sebastián Ramírez --- .github/workflows/test.yml | 2 ++ README.md | 8 ++++---- docs/en/docs/index.md | 8 ++++---- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1867cbb0..02507a9d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,6 +2,8 @@ name: Test on: push: + branches: + - master pull_request: types: [opened, synchronize] diff --git a/README.md b/README.md index 5bdd9e6d..3055e670 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,8 @@ FastAPI framework, high performance, easy to learn, fast to code, ready for production

- - Test + + Test Coverage @@ -316,7 +316,7 @@ And now, go to FastAPI framework, high performance, easy to learn, fast to code, ready for production

- - Test + + Test Coverage @@ -317,7 +317,7 @@ And now, go to Date: Sun, 3 Oct 2021 18:14:25 +0000 Subject: [PATCH 020/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index cf503d50..bff7aa48 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 💚 Fix badges in README and main page. PR [#3979](https://github.com/tiangolo/fastapi/pull/3979) by [@ghandic](https://github.com/ghandic). * 📝 Update GraphQL docs, recommend Strawberry. PR [#3981](https://github.com/tiangolo/fastapi/pull/3981) by [@tiangolo](https://github.com/tiangolo). * ✨ Add Deepset Sponsorship. PR [#3976](https://github.com/tiangolo/fastapi/pull/3976) 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). From 2680f369d0f5ec69645e1cdf42e2b9f197f8edf5 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Tue, 5 Oct 2021 10:06:35 +0200 Subject: [PATCH 021/196] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Bump=20Uvicorn=20m?= =?UTF-8?q?ax=20range=20to=200.15.0=20(#3345)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index eddbb92a..f48d034b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -78,7 +78,7 @@ dev = [ "passlib[bcrypt] >=1.7.2,<2.0.0", "autoflake >=1.3.1,<2.0.0", "flake8 >=3.8.3,<4.0.0", - "uvicorn[standard] >=0.12.0,<0.14.0", + "uvicorn[standard] >=0.12.0,<0.16.0", "graphene >=2.1.8,<3.0.0" ] all = [ @@ -92,7 +92,7 @@ all = [ "ujson >=4.0.1,<5.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", + "uvicorn[standard] >=0.12.0,<0.16.0", "async_exit_stack >=1.0.1,<2.0.0", "async_generator >=1.10,<2.0.0" ] From 189ac3e280a3b5698ac40d2f412818106e3f05cf Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 5 Oct 2021 08:07:20 +0000 Subject: [PATCH 022/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index bff7aa48..fc8e32c9 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ⬆️ Bump Uvicorn max range to 0.15.0. PR [#3345](https://github.com/tiangolo/fastapi/pull/3345) by [@Kludex](https://github.com/Kludex). * 💚 Fix badges in README and main page. PR [#3979](https://github.com/tiangolo/fastapi/pull/3979) by [@ghandic](https://github.com/ghandic). * 📝 Update GraphQL docs, recommend Strawberry. PR [#3981](https://github.com/tiangolo/fastapi/pull/3981) by [@tiangolo](https://github.com/tiangolo). * ✨ Add Deepset Sponsorship. PR [#3976](https://github.com/tiangolo/fastapi/pull/3976) by [@tiangolo](https://github.com/tiangolo). From 8c102814fd9ac0f0ff329273f65a743849cb2267 Mon Sep 17 00:00:00 2001 From: ArcLight_Slavik Date: Tue, 5 Oct 2021 11:40:08 +0300 Subject: [PATCH 023/196] =?UTF-8?q?=E2=AC=86=20Upgrade=20internal=20testin?= =?UTF-8?q?g=20dependencies:=20mypy=20to=20version=200.910,=20add=20newly?= =?UTF-8?q?=20needed=20type=20packages=20(#3350)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- mypy.ini | 25 ------------------------- pyproject.toml | 36 ++++++++++++++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 27 deletions(-) delete mode 100644 mypy.ini 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 f48d034b..7c986dbd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ test = [ "pytest >=6.2.4,<7.0.0", "pytest-cov >=2.12.0,<3.0.0", "pytest-asyncio >=0.14.0,<0.15.0", - "mypy ==0.812", + "mypy ==0.910", "flake8 >=3.8.3,<4.0.0", "black ==20.8b1", "isort >=5.0.6,<6.0.0", @@ -63,7 +63,12 @@ test = [ "async_generator >=1.10,<2.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,<2.0.0", + + # types + "types-ujson ==0.1.1", + "types-orjson ==3.6.0", + "types-dataclasses ==0.1.7; python_version<'3.7'", ] doc = [ "mkdocs >=1.1.2,<2.0.0", @@ -101,6 +106,33 @@ all = [ 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", From 655db2af1f4175b7ef0dd7004d27325d980b90e4 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 5 Oct 2021 08:40:44 +0000 Subject: [PATCH 024/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index fc8e32c9..ea32949b 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ⬆ 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). * ⬆️ Bump Uvicorn max range to 0.15.0. PR [#3345](https://github.com/tiangolo/fastapi/pull/3345) by [@Kludex](https://github.com/Kludex). * 💚 Fix badges in README and main page. PR [#3979](https://github.com/tiangolo/fastapi/pull/3979) by [@ghandic](https://github.com/ghandic). * 📝 Update GraphQL docs, recommend Strawberry. PR [#3981](https://github.com/tiangolo/fastapi/pull/3981) by [@tiangolo](https://github.com/tiangolo). From cf5e67590a6585355e3169072732cd73a3de4e28 Mon Sep 17 00:00:00 2001 From: Taneli Hukkinen <3275109+hukkin@users.noreply.github.com> Date: Tue, 5 Oct 2021 10:46:42 +0200 Subject: [PATCH 025/196] =?UTF-8?q?=E2=AC=86=20Upgrade=20required=20Python?= =?UTF-8?q?=20version=20to=20>=3D=203.6.1,=20needed=20by=20typing.Deque,?= =?UTF-8?q?=20used=20by=20Pydantic=20(#2733)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Taneli Hukkinen Co-authored-by: Sebastián Ramírez --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7c986dbd..b2be66fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,7 @@ requires = [ "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/" From 5607c198c47da11a7998f0ca2d3aad2c0bcb7411 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 5 Oct 2021 08:47:25 +0000 Subject: [PATCH 026/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index ea32949b..68c03d0e 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ⬆ 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). * ⬆ 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). * ⬆️ Bump Uvicorn max range to 0.15.0. PR [#3345](https://github.com/tiangolo/fastapi/pull/3345) by [@Kludex](https://github.com/Kludex). * 💚 Fix badges in README and main page. PR [#3979](https://github.com/tiangolo/fastapi/pull/3979) by [@ghandic](https://github.com/ghandic). From 8a02a471240b5f7991f05c2ff6bb0f49caa7d198 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20La=C3=ADns?= Date: Tue, 5 Oct 2021 09:58:00 +0100 Subject: [PATCH 027/196] =?UTF-8?q?=E2=9E=96=20Do=20not=20require=20backpo?= =?UTF-8?q?rts=20in=20Python=20>=3D=203.7=20(#1880)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- pyproject.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b2be66fa..30ebe55c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,11 +59,11 @@ test = [ "databases[sqlite] >=0.3.2,<0.4.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", "python-multipart >=0.0.5,<0.0.6", "aiofiles >=0.5.0,<0.6.0", "flask >=1.1.2,<2.0.0", + "async_exit_stack >=1.0.1,<2.0.0; python_version < '3.7'", + "async_generator >=1.10,<2.0.0; python_version < '3.7'", # types "types-ujson ==0.1.1", @@ -98,8 +98,8 @@ all = [ "orjson >=3.2.1,<4.0.0", "email_validator >=1.1.1,<2.0.0", "uvicorn[standard] >=0.12.0,<0.16.0", - "async_exit_stack >=1.0.1,<2.0.0", - "async_generator >=1.10,<2.0.0" + "async_exit_stack >=1.0.1,<2.0.0; python_version < '3.7'", + "async_generator >=1.10,<2.0.0; python_version < '3.7'", ] [tool.isort] From 69784e514161827f1702444ffd9b50585d79d6eb Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 5 Oct 2021 08:58:41 +0000 Subject: [PATCH 028/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 68c03d0e..b9fd722a 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ➖ 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). * ⬆ 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). * ⬆️ Bump Uvicorn max range to 0.15.0. PR [#3345](https://github.com/tiangolo/fastapi/pull/3345) by [@Kludex](https://github.com/Kludex). From ba8c78d87fe3901ed74e57199d4f282ea2ceb5c2 Mon Sep 17 00:00:00 2001 From: SnkSynthesis <63564282+SnkSynthesis@users.noreply.github.com> Date: Tue, 5 Oct 2021 02:17:14 -0700 Subject: [PATCH 029/196] =?UTF-8?q?=E2=AC=86Increase=20supported=20version?= =?UTF-8?q?=20of=20aiofiles=20to=20suppress=20warnings=20(#2899)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 30ebe55c..49af6366 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ test = [ "orjson >=3.2.1,<4.0.0", "ujson >=4.0.1,<5.0.0", "python-multipart >=0.0.5,<0.0.6", - "aiofiles >=0.5.0,<0.6.0", + "aiofiles >=0.5.0,<0.8.0", "flask >=1.1.2,<2.0.0", "async_exit_stack >=1.0.1,<2.0.0; python_version < '3.7'", "async_generator >=1.10,<2.0.0; python_version < '3.7'", @@ -88,7 +88,7 @@ dev = [ ] all = [ "requests >=2.24.0,<3.0.0", - "aiofiles >=0.5.0,<0.6.0", + "aiofiles >=0.5.0,<0.8.0", "jinja2 >=2.11.2,<3.0.0", "python-multipart >=0.0.5,<0.0.6", "itsdangerous >=1.1.0,<2.0.0", From f6a3bc2902ecec1cd66b36b2f2de146055d8e26b Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 5 Oct 2021 09:17:56 +0000 Subject: [PATCH 030/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index b9fd722a..c7e29cd5 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ⬆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). * ⬆ 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). From 24749aef7185b2d98f22650a71e73d90f29c7f56 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 5 Oct 2021 11:54:36 +0200 Subject: [PATCH 031/196] =?UTF-8?q?=F0=9F=91=A5=20Update=20FastAPI=20Peopl?= =?UTF-8?q?e=20(#3986)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions --- docs/en/data/people.yml | 228 ++++++++++++++++++++++------------------ 1 file changed, 124 insertions(+), 104 deletions(-) diff --git a/docs/en/data/people.yml b/docs/en/data/people.yml index 1e4ee30c..8f1710d3 100644 --- a/docs/en/data/people.yml +++ b/docs/en/data/people.yml @@ -1,12 +1,12 @@ maintainers: - login: tiangolo - answers: 1229 - prs: 250 - avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=05f95ca7fdead36edd9c86be46b4ef6c3c71f876&v=4 + answers: 1230 + prs: 260 + avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=5cad72c846b7aba2e960546af490edc7375dafc4&v=4 url: https://github.com/tiangolo experts: - login: Kludex - count: 278 + count: 299 avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=cf8455cb899806b774a3a71073f88583adec99f6&v=4 url: https://github.com/Kludex - login: dmontagu @@ -14,11 +14,11 @@ experts: avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=58ed2a45798a4339700e2f62b2e12e6e54bf0396&v=4 url: https://github.com/dmontagu - login: ycd - count: 217 + count: 219 avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=826f228edf0bab0d19ad1d5c4ba4df1047ccffef&v=4 url: https://github.com/ycd - login: Mause - count: 202 + count: 207 avatarUrl: https://avatars.githubusercontent.com/u/1405026?v=4 url: https://github.com/Mause - login: euri10 @@ -30,7 +30,7 @@ experts: avatarUrl: https://avatars.githubusercontent.com/u/331403?v=4 url: https://github.com/phy25 - login: ArcLightSlavik - count: 66 + count: 68 avatarUrl: https://avatars.githubusercontent.com/u/31127044?u=81a84af39c89b898b0fbc5a04e8834f60f23e55a&v=4 url: https://github.com/ArcLightSlavik - login: falkben @@ -42,7 +42,7 @@ experts: avatarUrl: https://avatars.githubusercontent.com/u/516999?u=437c0c5038558c67e887ccd863c1ba0f846c03da&v=4 url: https://github.com/sm-Fifteen - login: raphaelauv - count: 47 + count: 48 avatarUrl: https://avatars.githubusercontent.com/u/10202690?u=e6f86f5c0c3026a15d6b51792fa3e532b12f1371&v=4 url: https://github.com/raphaelauv - login: includeamin @@ -69,18 +69,26 @@ experts: count: 29 avatarUrl: https://avatars.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4 url: https://github.com/wshayes +- login: frankie567 + count: 29 + avatarUrl: https://avatars.githubusercontent.com/u/1144727?u=85c025e3fcc7bd79a5665c63ee87cdf8aae13374&v=4 + url: https://github.com/frankie567 - 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: chbndrhnns + count: 24 + avatarUrl: https://avatars.githubusercontent.com/u/7534547?v=4 + url: https://github.com/chbndrhnns - login: SirTelemak count: 24 avatarUrl: https://avatars.githubusercontent.com/u/9435877?u=719327b7d2c4c62212456d771bfa7c6b8dbb9eac&v=4 url: https://github.com/SirTelemak -- login: chbndrhnns - count: 23 - avatarUrl: https://avatars.githubusercontent.com/u/7534547?v=4 - url: https://github.com/chbndrhnns +- login: STeveShary + count: 24 + avatarUrl: https://avatars.githubusercontent.com/u/5167622?u=de8f597c81d6336fcebc37b32dfd61a3f877160c&v=4 + url: https://github.com/STeveShary - login: acnebs count: 22 avatarUrl: https://avatars.githubusercontent.com/u/9054108?u=bfd127b3e6200f4d00afd714f0fc95c2512df19b&v=4 @@ -89,10 +97,6 @@ experts: 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=85c025e3fcc7bd79a5665c63ee87cdf8aae13374&v=4 - url: https://github.com/frankie567 - login: chris-allnutt count: 21 avatarUrl: https://avatars.githubusercontent.com/u/565544?v=4 @@ -101,10 +105,18 @@ experts: count: 19 avatarUrl: https://avatars.githubusercontent.com/u/24581770?v=4 url: https://github.com/retnikt +- login: ghandic + count: 18 + avatarUrl: https://avatars.githubusercontent.com/u/23500353?u=e2e1d736f924d9be81e8bfc565b6d8836ba99773&v=4 + url: https://github.com/ghandic - login: Hultner count: 18 avatarUrl: https://avatars.githubusercontent.com/u/2669034?u=115e53df959309898ad8dc9443fbb35fee71df07&v=4 url: https://github.com/Hultner +- login: panla + count: 18 + avatarUrl: https://avatars.githubusercontent.com/u/41326348?u=ba2fda6b30110411ecbf406d187907e2b420ac19&v=4 + url: https://github.com/panla - login: jorgerpo count: 17 avatarUrl: https://avatars.githubusercontent.com/u/12537771?u=7444d20019198e34911082780cc7ad73f2b97cb3&v=4 @@ -113,42 +125,38 @@ experts: count: 17 avatarUrl: https://avatars.githubusercontent.com/u/28262306?u=66ee21316275ef356081c2efc4ed7a4572e690dc&v=4 url: https://github.com/nkhitrov +- login: adriangb + count: 17 + avatarUrl: https://avatars.githubusercontent.com/u/1755071?u=81f0262df34e1460ca546fbd0c211169c2478532&v=4 + url: https://github.com/adriangb - login: waynerv count: 16 avatarUrl: https://avatars.githubusercontent.com/u/39515546?u=ec35139777597cdbbbddda29bf8b9d4396b429a9&v=4 url: https://github.com/waynerv -- login: adriangb - count: 14 - avatarUrl: https://avatars.githubusercontent.com/u/1755071?u=81f0262df34e1460ca546fbd0c211169c2478532&v=4 - url: https://github.com/adriangb -- login: panla - count: 14 - avatarUrl: https://avatars.githubusercontent.com/u/41326348?u=ba2fda6b30110411ecbf406d187907e2b420ac19&v=4 - url: https://github.com/panla - login: haizaar count: 13 avatarUrl: https://avatars.githubusercontent.com/u/58201?u=4f1f9843d69433ca0d380d95146cfe119e5fdac4&v=4 url: https://github.com/haizaar -- login: STeveShary - count: 13 - avatarUrl: https://avatars.githubusercontent.com/u/5167622?u=de8f597c81d6336fcebc37b32dfd61a3f877160c&v=4 - url: https://github.com/STeveShary - login: acidjunk - count: 11 + count: 13 avatarUrl: https://avatars.githubusercontent.com/u/685002?u=b5094ab4527fc84b006c0ac9ff54367bdebb2267&v=4 url: https://github.com/acidjunk +- login: David-Lor + count: 12 + avatarUrl: https://avatars.githubusercontent.com/u/17401854?u=474680c02b94cba810cb9032fb7eb787d9cc9d22&v=4 + url: https://github.com/David-Lor - login: zamiramir count: 11 avatarUrl: https://avatars.githubusercontent.com/u/40475662?u=e58ef61034e8d0d6a312cc956fb09b9c3332b449&v=4 url: https://github.com/zamiramir -- login: David-Lor - count: 11 - avatarUrl: https://avatars.githubusercontent.com/u/17401854?u=474680c02b94cba810cb9032fb7eb787d9cc9d22&v=4 - url: https://github.com/David-Lor - login: juntatalor count: 11 avatarUrl: https://avatars.githubusercontent.com/u/8134632?v=4 url: https://github.com/juntatalor +- login: dstlny + count: 11 + avatarUrl: https://avatars.githubusercontent.com/u/41964673?u=9f2174f9d61c15c6e3a4c9e3aeee66f711ce311f&v=4 + url: https://github.com/dstlny - login: valentin994 count: 11 avatarUrl: https://avatars.githubusercontent.com/u/42819267?u=fdeeaa9242a59b243f8603496b00994f6951d5a2&v=4 @@ -166,42 +174,42 @@ experts: avatarUrl: https://avatars.githubusercontent.com/u/47581948?v=4 url: https://github.com/hellocoldworld last_month_active: -- login: Mause - count: 10 - avatarUrl: https://avatars.githubusercontent.com/u/1405026?v=4 - url: https://github.com/Mause - login: Kludex - count: 9 + count: 13 avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=cf8455cb899806b774a3a71073f88583adec99f6&v=4 url: https://github.com/Kludex -- login: panla - count: 7 - avatarUrl: https://avatars.githubusercontent.com/u/41326348?u=ba2fda6b30110411ecbf406d187907e2b420ac19&v=4 - url: https://github.com/panla -- login: adriangb - count: 6 - avatarUrl: https://avatars.githubusercontent.com/u/1755071?u=81f0262df34e1460ca546fbd0c211169c2478532&v=4 - url: https://github.com/adriangb -- login: Dustyposa - count: 6 - avatarUrl: https://avatars.githubusercontent.com/u/27180793?u=5cf2877f50b3eb2bc55086089a78a36f07042889&v=4 - url: https://github.com/Dustyposa +- login: ghandic + count: 12 + avatarUrl: https://avatars.githubusercontent.com/u/23500353?u=e2e1d736f924d9be81e8bfc565b6d8836ba99773&v=4 + url: https://github.com/ghandic - login: STeveShary - count: 5 + count: 8 avatarUrl: https://avatars.githubusercontent.com/u/5167622?u=de8f597c81d6336fcebc37b32dfd61a3f877160c&v=4 url: https://github.com/STeveShary -- login: Cosmicoppai +- login: Honda-a count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/63765823?u=b97c98ddd6eaf06cd5a0d312db36f97996ac5b23&v=4 - url: https://github.com/Cosmicoppai -- login: AlexanderPodorov + avatarUrl: https://avatars.githubusercontent.com/u/22759187?u=f45bd5fb17b4dca331529b8e9e5eab6122b84b8b&v=4 + url: https://github.com/Honda-a +- login: frankie567 count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/54511144?v=4 - url: https://github.com/AlexanderPodorov -- login: SamuelHeath + avatarUrl: https://avatars.githubusercontent.com/u/1144727?u=85c025e3fcc7bd79a5665c63ee87cdf8aae13374&v=4 + url: https://github.com/frankie567 +- login: panla count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/12291364?v=4 - url: https://github.com/SamuelHeath + avatarUrl: https://avatars.githubusercontent.com/u/41326348?u=ba2fda6b30110411ecbf406d187907e2b420ac19&v=4 + url: https://github.com/panla +- login: dstlny + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/41964673?u=9f2174f9d61c15c6e3a4c9e3aeee66f711ce311f&v=4 + url: https://github.com/dstlny +- login: trevorwang + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/121966?v=4 + url: https://github.com/trevorwang +- login: klaa97 + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/39653693?v=4 + url: https://github.com/klaa97 top_contributors: - login: waynerv count: 25 @@ -224,19 +232,19 @@ top_contributors: avatarUrl: https://avatars.githubusercontent.com/u/11489395?u=4adb6986bf3debfc2b8216ae701f2bd47d73da7d&v=4 url: https://github.com/mariacamilagl - login: jaystone776 - count: 9 + count: 11 avatarUrl: https://avatars.githubusercontent.com/u/11191137?u=299205a95e9b6817a43144a48b643346a5aac5cc&v=4 url: https://github.com/jaystone776 +- login: Serrones + count: 8 + avatarUrl: https://avatars.githubusercontent.com/u/22691749?u=4795b880e13ca33a73e52fc0ef7dc9c60c8fce47&v=4 + url: https://github.com/Serrones - 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: Kludex - count: 6 + count: 7 avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=cf8455cb899806b774a3a71073f88583adec99f6&v=4 url: https://github.com/Kludex - login: hard-coders @@ -255,6 +263,10 @@ top_contributors: count: 5 avatarUrl: https://avatars.githubusercontent.com/u/1175560?v=4 url: https://github.com/Attsun1031 +- login: Smlep + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/16785985?v=4 + url: https://github.com/Smlep - login: jekirl count: 4 avatarUrl: https://avatars.githubusercontent.com/u/2546697?u=a027452387d85bd4a14834e19d716c99255fb3b7&v=4 @@ -267,15 +279,15 @@ top_contributors: count: 4 avatarUrl: https://avatars.githubusercontent.com/u/39375566?u=260ad6b1a4b34c07dbfa728da5e586f16f6d1824&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: 85 + count: 90 avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=cf8455cb899806b774a3a71073f88583adec99f6&v=4 url: https://github.com/Kludex +- login: waynerv + 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 @@ -284,18 +296,18 @@ top_reviewers: count: 46 avatarUrl: https://avatars.githubusercontent.com/u/41147016?u=55010621aece725aa702270b54fed829b6a1fe60&v=4 url: https://github.com/tokusumi -- login: waynerv - count: 43 - avatarUrl: https://avatars.githubusercontent.com/u/39515546?u=ec35139777597cdbbbddda29bf8b9d4396b429a9&v=4 - url: https://github.com/waynerv - login: ycd - count: 41 + count: 43 avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=826f228edf0bab0d19ad1d5c4ba4df1047ccffef&v=4 url: https://github.com/ycd - login: AdrianDeAnda - count: 28 + count: 32 avatarUrl: https://avatars.githubusercontent.com/u/1024932?u=bb7f8a0d6c9de4e9d0320a9f271210206e202250&v=4 url: https://github.com/AdrianDeAnda +- login: ArcLightSlavik + count: 27 + avatarUrl: https://avatars.githubusercontent.com/u/31127044?u=81a84af39c89b898b0fbc5a04e8834f60f23e55a&v=4 + url: https://github.com/ArcLightSlavik - login: dmontagu count: 23 avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=58ed2a45798a4339700e2f62b2e12e6e54bf0396&v=4 @@ -304,12 +316,8 @@ top_reviewers: count: 21 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: 18 + count: 19 avatarUrl: https://avatars.githubusercontent.com/u/3127847?u=b0a652331da17efeb85cd6e3a4969182e5004804&v=4 url: https://github.com/cassiobotaro - login: yanever @@ -320,6 +328,10 @@ top_reviewers: count: 16 avatarUrl: https://avatars.githubusercontent.com/u/52768429?u=6a3aa15277406520ad37f6236e89466ed44bc5b8&v=4 url: https://github.com/SwftAlpc +- login: hard-coders + count: 16 + avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=f2d3d2038c55d86d7f9348f4e6c5e30191e4ee8b&v=4 + url: https://github.com/hard-coders - login: pedabraham count: 15 avatarUrl: https://avatars.githubusercontent.com/u/16860088?u=abf922a7b920bf8fdb7867d8b43e091f1e796178&v=4 @@ -328,10 +340,18 @@ top_reviewers: count: 15 avatarUrl: https://avatars.githubusercontent.com/u/63476957?u=6c86e59b48e0394d4db230f37fc9ad4d7e2c27c7&v=4 url: https://github.com/delhi09 +- login: Smlep + count: 15 + avatarUrl: https://avatars.githubusercontent.com/u/16785985?v=4 + url: https://github.com/Smlep - login: lsglucas - count: 13 + count: 14 avatarUrl: https://avatars.githubusercontent.com/u/61513630?u=320e43fe4dc7bc6efc64e9b8f325f8075634fd20&v=4 url: https://github.com/lsglucas +- login: rjNemo + count: 13 + 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 @@ -340,14 +360,6 @@ top_reviewers: count: 12 avatarUrl: https://avatars.githubusercontent.com/u/6478810?u=af15d724875cec682ed8088a86d36b2798f981c0&v=4 url: https://github.com/sh0nk -- login: rjNemo - count: 12 - avatarUrl: https://avatars.githubusercontent.com/u/56785022?u=d5c3a02567c8649e146fcfc51b6060ccaf8adef8&v=4 - url: https://github.com/rjNemo -- login: hard-coders - count: 12 - avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=f2d3d2038c55d86d7f9348f4e6c5e30191e4ee8b&v=4 - url: https://github.com/hard-coders - login: mariacamilagl count: 10 avatarUrl: https://avatars.githubusercontent.com/u/11489395?u=4adb6986bf3debfc2b8216ae701f2bd47d73da7d&v=4 @@ -360,10 +372,6 @@ top_reviewers: count: 10 avatarUrl: https://avatars.githubusercontent.com/u/7887703?v=4 url: https://github.com/maoyibo -- login: Smlep - count: 9 - avatarUrl: https://avatars.githubusercontent.com/u/16785985?v=4 - url: https://github.com/Smlep - login: PandaHun count: 9 avatarUrl: https://avatars.githubusercontent.com/u/13096845?u=646eba44db720e37d0dbe8e98e77ab534ea78a20&v=4 @@ -400,10 +408,22 @@ top_reviewers: count: 7 avatarUrl: https://avatars.githubusercontent.com/u/1405026?v=4 url: https://github.com/Mause +- login: solomein-sv + count: 7 + avatarUrl: https://avatars.githubusercontent.com/u/46193920?u=46acfb4aeefb1d7b9fdc5a8cbd9eb8744683c47a&v=4 + url: https://github.com/solomein-sv +- login: ComicShrimp + count: 7 + avatarUrl: https://avatars.githubusercontent.com/u/43503750?u=b3e4d9a14d9a65d429ce62c566aef73178b7111d&v=4 + url: https://github.com/ComicShrimp - login: jovicon count: 6 avatarUrl: https://avatars.githubusercontent.com/u/21287303?u=b049eac3e51a4c0473c2efe66b4d28a7d8f2b572&v=4 url: https://github.com/jovicon +- login: BilalAlpaslan + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/47563997?u=63ed66e304fe8d765762c70587d61d9196e5c82d&v=4 + url: https://github.com/BilalAlpaslan - login: nimctl count: 5 avatarUrl: https://avatars.githubusercontent.com/u/49960770?u=e39b11d47188744ee07b2a1c7ce1a1bdf3c80760&v=4 @@ -416,18 +436,10 @@ top_reviewers: count: 5 avatarUrl: https://avatars.githubusercontent.com/u/63564282?u=0078826509dbecb2fdb543f4e881c9cd06157893&v=4 url: https://github.com/SnkSynthesis -- login: solomein-sv - count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/46193920?u=46acfb4aeefb1d7b9fdc5a8cbd9eb8744683c47a&v=4 - url: https://github.com/solomein-sv - login: anthonycepeda count: 5 avatarUrl: https://avatars.githubusercontent.com/u/72019805?u=892f700c79f9732211bd5221bf16eec32356a732&v=4 url: https://github.com/anthonycepeda -- login: ComicShrimp - count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/43503750?u=b3e4d9a14d9a65d429ce62c566aef73178b7111d&v=4 - url: https://github.com/ComicShrimp - login: oandersonmagalhaes count: 5 avatarUrl: https://avatars.githubusercontent.com/u/83456692?v=4 @@ -440,6 +452,10 @@ top_reviewers: count: 4 avatarUrl: https://avatars.githubusercontent.com/u/23391143?u=56ab6bff50be950fa8cae5cf736f2ae66e319ff3&v=4 url: https://github.com/rkbeatss +- login: izaguerreiro + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/2241504?v=4 + url: https://github.com/izaguerreiro - login: aviramha count: 4 avatarUrl: https://avatars.githubusercontent.com/u/41201924?u=6883cc4fc13a7b2e60d4deddd4be06f9c5287880&v=4 @@ -452,6 +468,10 @@ top_reviewers: count: 4 avatarUrl: https://avatars.githubusercontent.com/u/31370133?v=4 url: https://github.com/Zxilly +- login: dukkee + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/36825394?u=ccfd86e6a4f2d093dad6f7544cc875af67fa2df8&v=4 + url: https://github.com/dukkee - login: Bluenix2 count: 4 avatarUrl: https://avatars.githubusercontent.com/u/38372706?u=c9d28aff15958d6ebf1971148bfb3154ff943c4f&v=4 From c86a4e1dd3a83f82eafb67fd2b41b8ec8a6b93d4 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 5 Oct 2021 09:55:12 +0000 Subject: [PATCH 032/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index c7e29cd5..04a4a8c7 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 👥 Update FastAPI People. PR [#3986](https://github.com/tiangolo/fastapi/pull/3986) by [@github-actions[bot]](https://github.com/apps/github-actions). * ⬆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). From 96ca425b1f7c4918d8cd2b79342b883c2fb97a5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 5 Oct 2021 12:07:40 +0200 Subject: [PATCH 033/196] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Increase=20depende?= =?UTF-8?q?ncy=20ranges=20for=20tests=20and=20docs:=20pytest-cov,=20pytest?= =?UTF-8?q?-asyncio,=20black,=20httpx,=20sqlalchemy,=20databases,=20mkdocs?= =?UTF-8?q?-markdownextradata-plugin=20(#3987)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 49af6366..06802aba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,22 +45,23 @@ Documentation = "https://fastapi.tiangolo.com/" [tool.flit.metadata.requires-extra] test = [ "pytest >=6.2.4,<7.0.0", - "pytest-cov >=2.12.0,<3.0.0", - "pytest-asyncio >=0.14.0,<0.15.0", + "pytest-cov >=2.12.0,<4.0.0", + "pytest-asyncio >=0.14.0,<0.16.0", "mypy ==0.910", "flake8 >=3.8.3,<4.0.0", - "black ==20.8b1", + "black ==21.9b0", "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", "python-multipart >=0.0.5,<0.0.6", "aiofiles >=0.5.0,<0.8.0", + # TODO: try to upgrade after upgrading Starlette "flask >=1.1.2,<2.0.0", "async_exit_stack >=1.0.1,<2.0.0; python_version < '3.7'", "async_generator >=1.10,<2.0.0; python_version < '3.7'", @@ -74,7 +75,7 @@ doc = [ "mkdocs >=1.1.2,<2.0.0", "mkdocs-material >=7.1.9,<8.0.0", "mdx-include >=1.4.1,<2.0.0", - "mkdocs-markdownextradata-plugin >=0.1.7,<0.2.0", + "mkdocs-markdownextradata-plugin >=0.1.7,<0.3.0", "typer-cli >=0.0.12,<0.0.13", "pyyaml >=5.3.1,<6.0.0" ] @@ -84,15 +85,19 @@ dev = [ "autoflake >=1.3.1,<2.0.0", "flake8 >=3.8.3,<4.0.0", "uvicorn[standard] >=0.12.0,<0.16.0", + # TODO: remove in the next major version "graphene >=2.1.8,<3.0.0" ] all = [ "requests >=2.24.0,<3.0.0", "aiofiles >=0.5.0,<0.8.0", + # TODO: try to upgrade after upgrading Starlette "jinja2 >=2.11.2,<3.0.0", "python-multipart >=0.0.5,<0.0.6", + # TODO: try to upgrade after upgrading Starlette "itsdangerous >=1.1.0,<2.0.0", "pyyaml >=5.3.1,<6.0.0", + # TODO: remove in the next major version "graphene >=2.1.8,<3.0.0", "ujson >=4.0.1,<5.0.0", "orjson >=3.2.1,<4.0.0", From 9229ba8078897d5ad1210d42b0cf4b672dc06457 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 5 Oct 2021 10:08:26 +0000 Subject: [PATCH 034/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 04a4a8c7..dc623d9b 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ⬆️ 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). * ⬆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). From ae22bca9fea481eb0bd20e08dcd5f89d9e158894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 5 Oct 2021 12:17:31 +0200 Subject: [PATCH 035/196] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Upgrade=20developm?= =?UTF-8?q?ent=20`autoflake`,=20supporting=20multi-line=20imports=20(#3988?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/contributing.md | 14 -------------- pyproject.toml | 2 +- scripts/format-imports.sh | 6 ------ 3 files changed, 1 insertion(+), 21 deletions(-) delete mode 100755 scripts/format-imports.sh diff --git a/docs/en/docs/contributing.md b/docs/en/docs/contributing.md index cd87faa4..9ef6e22c 100644 --- a/docs/en/docs/contributing.md +++ b/docs/en/docs/contributing.md @@ -163,20 +163,6 @@ It will also auto-sort all your imports. For it to sort them correctly, you need to have FastAPI installed locally in your environment, with the command in the section above using `--symlink` (or `--pth-file` on Windows). -### Format imports - -There is another script that formats all the imports and makes sure you don't have unused imports: - -

- -As it runs one command after the other and modifies and reverts many files, it takes a bit longer to run, so it might be easier to use `scripts/format.sh` frequently and `scripts/format-imports.sh` only before committing. - ## Docs First, make sure you set up your environment as described above, that will install all the requirements. diff --git a/pyproject.toml b/pyproject.toml index 06802aba..5b6b272a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,7 +82,7 @@ doc = [ dev = [ "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.16.0", # TODO: remove in the next major version 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 From 458a7fbf15034d848ab0f099a965e4c622fb41db Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 5 Oct 2021 10:18:13 +0000 Subject: [PATCH 036/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index dc623d9b..005d024b 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ⬆️ 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). * ⬆Increase supported version of aiofiles to suppress warnings. PR [#2899](https://github.com/tiangolo/fastapi/pull/2899) by [@SnkSynthesis](https://github.com/SnkSynthesis). From 557fe61d9298d213f26357439c59bf3002b78979 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 5 Oct 2021 12:24:02 +0200 Subject: [PATCH 037/196] =?UTF-8?q?=E2=9C=A8=20Update=20GitHub=20Action:?= =?UTF-8?q?=20notify-translations,=20to=20avoid=20a=20race=20conditon=20(#?= =?UTF-8?q?3989)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../actions/notify-translations/app/main.py | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/.github/actions/notify-translations/app/main.py b/.github/actions/notify-translations/app/main.py index 79850513..7d6c1a4d 100644 --- a/.github/actions/notify-translations/app/main.py +++ b/.github/actions/notify-translations/app/main.py @@ -1,5 +1,7 @@ import logging +import time from pathlib import Path +import random from typing import Dict, Optional import yaml @@ -45,8 +47,11 @@ if __name__ == "__main__": github_event = PartialGitHubEvent.parse_raw(contents) translations_map: Dict[str, int] = yaml.safe_load(translations_path.read_text()) logging.debug(f"Using translations map: {translations_map}") + sleep_time = random.random() * 10 # random number between 0 and 10 seconds pr = repo.get_pull(github_event.pull_request.number) - logging.debug(f"Processing PR: {pr.number}") + logging.debug( + f"Processing PR: {pr.number}, with anti-race condition sleep time: {sleep_time}" + ) if pr.state == "open": logging.debug(f"PR is open: {pr.number}") label_strs = set([label.name for label in pr.get_labels()]) @@ -67,19 +72,31 @@ if __name__ == "__main__": for lang in langs: if lang in translations_map: num = translations_map[lang] - logging.info(f"Found a translation issue for language: {lang} in issue: {num}") + logging.info( + f"Found a translation issue for language: {lang} in issue: {num}" + ) issue = repo.get_issue(num) message = f"Good news everyone! 😉 There's a new translation PR to be reviewed: #{pr.number} 🎉" already_notified = False - logging.info(f"Checking current comments in issue: {num} to see if already notified about this PR: {pr.number}") + time.sleep(sleep_time) + logging.info( + f"Sleeping for {sleep_time} seconds to avoid race conditions and multiple comments" + ) + logging.info( + f"Checking current comments in issue: {num} to see if already notified about this PR: {pr.number}" + ) for comment in issue.get_comments(): if message in comment.body: already_notified = True if not already_notified: - logging.info(f"Writing comment in issue: {num} about PR: {pr.number}") + logging.info( + f"Writing comment in issue: {num} about PR: {pr.number}" + ) issue.create_comment(message) else: - logging.info(f"Issue: {num} was already notified of PR: {pr.number}") + logging.info( + f"Issue: {num} was already notified of PR: {pr.number}" + ) else: logging.info( f"Changing labels in a closed PR doesn't trigger comments, PR: {pr.number}" From 39ec5b33d9d4109333d9429352db792464b159a6 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 5 Oct 2021 10:24:47 +0000 Subject: [PATCH 038/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 005d024b..1e260585 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ✨ Update GitHub Action: notify-translations, to avoid a race conditon. 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). From 6de87487888b762b2c4f83b67db97b8c8d6b0584 Mon Sep 17 00:00:00 2001 From: Lucas <61513630+lsglucas@users.noreply.github.com> Date: Tue, 5 Oct 2021 07:40:05 -0300 Subject: [PATCH 039/196] =?UTF-8?q?=F0=9F=8C=90=20Add=20Portuguese=20trans?= =?UTF-8?q?lation=20for=20`docs/deployment/https.md`=20(#3754)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/pt/docs/deployment/https.md | 48 ++++++++++++++++++++++++++++++++ docs/pt/mkdocs.yml | 2 +- 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 docs/pt/docs/deployment/https.md 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/mkdocs.yml b/docs/pt/mkdocs.yml index ec0684d5..63c524d8 100644 --- a/docs/pt/mkdocs.yml +++ b/docs/pt/mkdocs.yml @@ -67,6 +67,7 @@ nav: - Implantação: - deployment/index.md - deployment/versions.md + - deployment/https.md - alternatives.md - history-design-future.md - external-links.md @@ -140,4 +141,3 @@ extra_css: extra_javascript: - https://fastapi.tiangolo.com/js/termynal.js - https://fastapi.tiangolo.com/js/custom.js - From 4e2595f9283f9151c22dc6a4104b59b668ea6357 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 5 Oct 2021 10:40:42 +0000 Subject: [PATCH 040/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 1e260585..a2bace25 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🌐 Add Portuguese translation for `docs/deployment/https.md`. PR [#3754](https://github.com/tiangolo/fastapi/pull/3754) by [@lsglucas](https://github.com/lsglucas). * ✨ Update GitHub Action: notify-translations, to avoid a race conditon. 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). From e0e32245883616f944794abaddeace967a9edd13 Mon Sep 17 00:00:00 2001 From: jaystone776 Date: Tue, 5 Oct 2021 19:03:59 +0800 Subject: [PATCH 041/196] =?UTF-8?q?=F0=9F=8C=90=20Delete=20old=20Chinese?= =?UTF-8?q?=20translation=20for=20`docs/deployment.md`=20(#3840)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- docs/zh/docs/deployment.md | 392 ------------------------------------- docs/zh/mkdocs.yml | 1 - 2 files changed, 393 deletions(-) delete mode 100644 docs/zh/docs/deployment.md 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** 镜像 - -
- -```console -$ docker build -t myimage . - ----> 100% -``` - -
- -### 启动 Docker 容器 - -* 运行基于你的镜像容器: - -
- -```console -$ docker run -d --name mycontainer -p 80:80 myimage -``` - -
- -现在你在 Docker 容器中有了一个根据当前服务器(和CPU核心的数量)自动优化好的 FastAPI 服务器。 - -### 检查一下 - -你应该能够在 Docker 容器的 URL 中检查它。例如:http://192.168.99.100/items/5?q=somequery 或者 http://127.0.0.1/items/5?q=somequery (或者类似的,使用Docker主机)。 - -得到类似的输出: - -```JSON -{"item_id": 5, "q": "somequery"} -``` - -### 交互式 API 文档 - -现在可以访问 http://192.168.99.100/docs 或者 http://127.0.0.1/docs (或者类似的,使用Docker主机)。 - -你会看到一个交互式的 API 文档 (由 Swagger UI 提供): - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) - -### 可选的 API 文档 - -你也可以访问 http://192.168.99.100/redoc 或者 http://127.0.0.1/redoc (或者类似的,使用Docker主机)。 - -你将看到一个可选的自动化文档(由 ReDoc 提供) - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) - -## HTTPS - -### 关于 HTTPS - -我们当然可以假设 HTTPS 只是某种「启用」或「不启用」的东西。 - -但是事实比这要复杂的多。 - -!!! tip - 如果你着急或者不关心这部分内容,请继续按照下一章节的步骤进行配置。 - -要从用户的角度学习 HTTPS 的基础,请移步 https://howhttps.works/。 - -从开发人员的角度来看,在考虑 HTTPS 时有以下几点需要注意: - -* 对 HTTPS 来说,服务端需要有第三方生成的「证书」。 - * 实际上这些证书是从第三方获取的,而非「生成」的。 -* 证书有生命周期。 - * 证书会过期。 - * 证书过期之后需要更新,重新从第三方获取。 -* 连接的加密发生在 TCP 层。 - * TCP 层在 HTTP 之下一层。 - * 因此,证书和加密处理在 HTTP 之前完成。 -* TCP 不知道「域名」,只知道 IP 地址。 - * 指定域名的请求信息在 HTTP 的数据中。 -* HTTPS 证书「认证」某个特定域名,但是协议和加密在知道要处理哪个域名之前就已经在 TCP 层发生了。 -* 默认情况下,一个 IP 地址仅有一个 HTTPS 证书。 - * 无论你的服务器大小,都是如此。 - * 但是对此有解决办法。 -* TSL 协议(在 TCP 层处理加密的协议,发生在 HTTP 之前)有一个扩展,叫 SNI。 - * SNI 扩展允许一个服务器(一个 IP 地址)有多个 HTTPS 证书,为多个 HTTPS 域名/应用 提供服务。 - * 要使其工作,服务器运行的单一组件(程序)监听公网 IP 地址,所有 HTTPS 证书必须都在该服务器上。 -* 在获得一个安全连接之后,通讯协议仍然是 HTTP。 - * HTTP 内容是加密的,即使这些内容使用 HTTP 协议传输。 - -常见的做法是在服务器(机器,主机等等)上运行一个程序或 HTTP 服务来管理所有的 HTTPS 部分:将解密后的 HTTP 请求发送给在同一服务器运行的真实 HTTP 应用(在这里是 **FastAPI** 应用),从应用获得 HTTP 响应,使用适当的证书加密响应然后使用 HTTPS 将其发回客户端。这个服务器常被称作 TLS 终止代理。 - - -### Let's Encrypt - -在 Let's Encrypt 出现之前,这些 HTTPS 证书由受信任的第三方出售。 - -获取这些证书的过程曾经非常繁琐,需要大量的文书工作,而且证书的价格也相当昂贵。 - -但是紧接着 Let's Encrypt 被创造了。 - -这是一个来自 Linux 基金会的项目。它以自动化的方式免费提供 HTTPS 证书。这些证书使用所有的标准加密措施,且证书生命周期很短(大约 3 个月),正是由于它们生命周期的减短,所以实际上安全性更高。 - -对域名进行安全验证并自动生成证书。同时也允许自动更新这些证书。 - -其想法是自动获取和更新这些证书,这样就可以一直免费获得安全的 HTTPS。 - -### Traefik - -Traefik 是一个高性能的反向代理/负载均衡器。它能够完成「TLS 终止代理」的工作(其他特性除外)。 - -Traefik 集成了 Let's Encrypt,所以能够处理全部 HTTPS 的部分,包括证书获取与更新。 - -Traefik 也集成了 Docker,所以你也可以在每个应用的配置中声明你的域名并可以让它读取这些配置,生成 HTTPS 证书并自动将 HTTPS 提供给你的应用程序,而你不需要对其配置进行任何更改。 - ---- - -有了这些信息和工具,就可以进入下一节把所有内容结合到一起。 - -## 通过 Traefik 和 HTTPS 搭建 Docker Swarm mode 集群 - -通过一个主要的 Traefik 来处理 HTTPS (包括证书获取和更新),大约 20 分钟就可以搭建好一个 Docker Swarm mode 集群。 - -借助 Docker Swarm mode,你可以从单个机器的集群开始(甚至可以是 $5 /月的服务器),然后你可以根据需要添加更多的服务器来进行扩展。 - -要使用 Traefik 和 HTTPS 处理来构建 Docker Swarm Mode 集群,请遵循以下指南: - -### Docker Swarm Mode 和 Traefik 用于 HTTPS 集群 - -### 部署一个 FastAPI 应用 - -部署的最简单方式就是使用 [**FastAPI** 项目生成器](project-generation.md){.internal-link target=_blank}。 - -它被设计成与上述带有 Traefik 和 HTTPS 的 Docker Swarm 集群整合到一起。 - -你可以在大概两分钟内生成一个项目。 - -生成的项目有部署说明,需要再花两分钟部署项目。 - -## 或者,不用 Docker 部署 **FastAPI** - -你也可以不用 Docker 直接部署 **FastAPI**。 - -只需要安装一个兼容 ASGI 的服务器: - -* Uvicorn,一个轻量快速的 ASGI 服务器,基于 uvloop 和 httptools 构建。 - -
- -```console -$ pip install uvicorn[standard] - ----> 100% -``` - -
- -* Hypercorn,一个也兼容 HTTP/2 的 ASGI 服务器。 - -
- -```console -$ pip install hypercorn - ----> 100% -``` - -
- -...或者任何其他的 ASGI 服务器。 - -然后使用教程中同样的方式来运行你的应用,但是不要加 `--reload` 选项,比如: - -
- -```console -$ uvicorn main:app --host 0.0.0.0 --port 80 - -INFO: Uvicorn running on http://0.0.0.0:80 (Press CTRL+C to quit) -``` - -
- -或者使用 Hypercorn: - -
- -```console -$ hypercorn main:app --bind 0.0.0.0:80 - -Running on 0.0.0.0:8080 over http (CTRL + C to quit) -``` - -
- -也许你想编写一些工具来确保它停止时会自动重启。 - -或者安装 Gunicorn将其作为 Uvicorn 的管理器,或者使用多职程(worker)的 Hypercorn。 - -或者保证精确调整职程的数量等等。 - -但是如果你正做这些,你可能只需要使用 Docker 镜像就能够自动做到这些了。 diff --git a/docs/zh/mkdocs.yml b/docs/zh/mkdocs.yml index d73afaa9..71391993 100644 --- a/docs/zh/mkdocs.yml +++ b/docs/zh/mkdocs.yml @@ -101,7 +101,6 @@ nav: - advanced/additional-status-codes.md - advanced/response-directly.md - advanced/custom-response.md -- deployment.md - contributing.md - help-fastapi.md - benchmarks.md From 8c36572d23893a39732290a1ad74df60cb16fd61 Mon Sep 17 00:00:00 2001 From: Felipe Silva <66804965+FelipeSilva93@users.noreply.github.com> Date: Tue, 5 Oct 2021 09:19:03 -0300 Subject: [PATCH 042/196] =?UTF-8?q?=F0=9F=8C=90=20Add=20Portuguese=20trans?= =?UTF-8?q?lation=20for=20`docs/tutorial/path-params.md`=20(#3664)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mário Victor Ribeiro Silva Co-authored-by: Izabela Guerreiro Co-authored-by: Marcelo Trylesinski Co-authored-by: Lucas <61513630+lsglucas@users.noreply.github.com> Co-authored-by: Felipe Co-authored-by: Sebastián Ramírez --- docs/pt/docs/tutorial/path-params.md | 246 +++++++++++++++++++++++++++ docs/pt/mkdocs.yml | 1 + 2 files changed, 247 insertions(+) create mode 100644 docs/pt/docs/tutorial/path-params.md diff --git a/docs/pt/docs/tutorial/path-params.md b/docs/pt/docs/tutorial/path-params.md new file mode 100644 index 00000000..20913a56 --- /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 documtaçã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/mkdocs.yml b/docs/pt/mkdocs.yml index 63c524d8..567d21cc 100644 --- a/docs/pt/mkdocs.yml +++ b/docs/pt/mkdocs.yml @@ -59,6 +59,7 @@ nav: - Tutorial - Guia de Usuário: - tutorial/index.md - tutorial/first-steps.md + - tutorial/path-params.md - tutorial/body-fields.md - Segurança: - tutorial/security/index.md From d8b0a9dc6e08d55623d4f84a0ed268e5b3ad05c9 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 5 Oct 2021 12:19:45 +0000 Subject: [PATCH 043/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index a2bace25..849303bb 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🌐 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). * ✨ Update GitHub Action: notify-translations, to avoid a race conditon. 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). From 00ac07f65c4dd8fa9fa3a78ed6b81d9aa190e521 Mon Sep 17 00:00:00 2001 From: Ruidy Date: Tue, 5 Oct 2021 14:27:24 +0200 Subject: [PATCH 044/196] =?UTF-8?q?=F0=9F=8C=90=20Add=20French=20translati?= =?UTF-8?q?on=20for=20`deployment/docker.md`=20(#3694)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sam Courtemanche Co-authored-by: Sebastián Ramírez --- docs/fr/docs/deployment/docker.md | 182 ++++++++++++++++++++++++++++++ docs/fr/mkdocs.yml | 2 + 2 files changed, 184 insertions(+) create mode 100644 docs/fr/docs/deployment/docker.md 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 : + +
+ +```console +$ docker build -t myimage . + +---> 100% +``` + +
+ +## Démarrer le conteneur Docker + +* Exécutez un conteneur basé sur votre image : + +
+ +```console +$ docker run -d --name mycontainer -p 80:80 myimage +``` + +
+ +Vous disposez maintenant d'un serveur FastAPI optimisé dans un conteneur Docker. Configuré automatiquement pour votre +serveur actuel (et le nombre de cœurs du CPU). + +## Vérifier + +Vous devriez pouvoir accéder à votre application via l'URL de votre conteneur Docker, par exemple : http://192.168.99.100/items/5?q=somequery ou http://127.0.0.1/items/5?q=somequery (ou équivalent, en utilisant votre hôte Docker). + +Vous verrez quelque chose comme : + +```JSON +{"item_id": 5, "q": "somequery"} +``` + +## Documentation interactive de l'API + +Vous pouvez maintenant visiter http://192.168.99.100/docs ou http://127.0.0.1/docs (ou équivalent, en utilisant votre hôte Docker). + +Vous verrez la documentation interactive automatique de l'API (fournie par Swagger UI) : + +![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) + +## Documentation de l'API alternative + +Et vous pouvez également aller sur http://192.168.99.100/redoc ou http://127.0.0.1/redoc (ou équivalent, en utilisant votre hôte Docker). + +Vous verrez la documentation automatique alternative (fournie par ReDoc) : + +![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) + +## Traefik + +Traefik est un reverse proxy/load balancer +haute performance. Il peut faire office de "Proxy de terminaison TLS" (entre autres fonctionnalités). + +Il est intégré à Let's Encrypt. Ainsi, il peut gérer toutes les parties HTTPS, y compris l'acquisition et le renouvellement des certificats. + +Il est également intégré à Docker. Ainsi, vous pouvez déclarer vos domaines dans les configurations de chaque application et faire en sorte qu'elles lisent ces configurations, génèrent les certificats HTTPS et servent via HTTPS à votre application automatiquement, sans nécessiter aucune modification de leurs configurations. + +--- + +Avec ces informations et ces outils, passez à la section suivante pour tout combiner. + +## Cluster en mode Docker Swarm avec Traefik et HTTPS + +Vous pouvez avoir un cluster en mode Docker Swarm configuré en quelques minutes (environ 20 min) avec un processus Traefik principal gérant HTTPS (y compris l'acquisition et le renouvellement des certificats). + +En utilisant le mode Docker Swarm, vous pouvez commencer par un "cluster" d'une seule machine (il peut même s'agir +d'un serveur à 5 USD/mois) et ensuite vous pouvez vous développer autant que vous le souhaitez en ajoutant d'autres serveurs. + +Pour configurer un cluster en mode Docker Swarm avec Traefik et la gestion de HTTPS, suivez ce guide : + +### Docker Swarm Mode et Traefik pour un cluster HTTPS + +### Déployer une application FastAPI + +La façon la plus simple de tout mettre en place, serait d'utiliser les [**Générateurs de projet FastAPI**](../project-generation.md){.internal-link target=_blank}. + +Le génerateur de projet adéquat est conçu pour être intégré à ce cluster Docker Swarm avec Traefik et HTTPS décrit ci-dessus. + +Vous pouvez générer un projet en 2 min environ. + +Le projet généré a des instructions pour le déployer et le faire prend 2 min de plus. diff --git a/docs/fr/mkdocs.yml b/docs/fr/mkdocs.yml index 4891a29c..c1fb0f23 100644 --- a/docs/fr/mkdocs.yml +++ b/docs/fr/mkdocs.yml @@ -60,6 +60,8 @@ nav: - Tutoriel - Guide utilisateur: - tutorial/background-tasks.md - async.md +- Déploiement: + - deployment/docker.md - project-generation.md - alternatives.md - external-links.md From 68c43eb12677edf8b2863216f710d6f1d44031f7 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 5 Oct 2021 12:28:05 +0000 Subject: [PATCH 045/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 849303bb..9564600a 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🌐 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). * ✨ Update GitHub Action: notify-translations, to avoid a race conditon. PR [#3989](https://github.com/tiangolo/fastapi/pull/3989) by [@tiangolo](https://github.com/tiangolo). From 0eb27ab4d033ef409aa359cfba8c8552e05b9eb6 Mon Sep 17 00:00:00 2001 From: Sam Courtemanche Date: Tue, 5 Oct 2021 14:30:41 +0200 Subject: [PATCH 046/196] =?UTF-8?q?=F0=9F=8C=90=20Add=20French=20translati?= =?UTF-8?q?on=20for=20`docs/tutorial/body.md`=20(#3671)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- docs/fr/docs/tutorial/body.md | 165 ++++++++++++++++++++++++++++++++++ docs/fr/mkdocs.yml | 1 + 2 files changed, 166 insertions(+) create mode 100644 docs/fr/docs/tutorial/body.md diff --git a/docs/fr/docs/tutorial/body.md b/docs/fr/docs/tutorial/body.md new file mode 100644 index 00000000..c0953f49 --- /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 PyCharm. + + 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/mkdocs.yml b/docs/fr/mkdocs.yml index c1fb0f23..018c2bd3 100644 --- a/docs/fr/mkdocs.yml +++ b/docs/fr/mkdocs.yml @@ -58,6 +58,7 @@ nav: - fastapi-people.md - python-types.md - Tutoriel - Guide utilisateur: + - tutorial/body.md - tutorial/background-tasks.md - async.md - Déploiement: From bc99d2b7b82764eeb0c74da4b89f7d0c8e2590b1 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 5 Oct 2021 12:31:22 +0000 Subject: [PATCH 047/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 9564600a..553fe0be 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🌐 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). From 5fbf597cd5599fd0f578476129e1f861b8223f49 Mon Sep 17 00:00:00 2001 From: Yagiz Degirmenci <62724709+ycd@users.noreply.github.com> Date: Tue, 5 Oct 2021 15:46:11 +0300 Subject: [PATCH 048/196] =?UTF-8?q?=F0=9F=8C=90=20Add=20Turkish=20translat?= =?UTF-8?q?ion=20for=20`docs/index.md`=20(#1908)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- docs/tr/docs/index.md | 310 ++++++++++++++++++++++-------------------- 1 file changed, 159 insertions(+), 151 deletions(-) diff --git a/docs/tr/docs/index.md b/docs/tr/docs/index.md index 282e2b60..88660f7e 100644 --- a/docs/tr/docs/index.md +++ b/docs/tr/docs/index.md @@ -6,7 +6,7 @@ FastAPI

- 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,27 +22,27 @@ --- -**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. ## Sponsors @@ -61,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._"

Kabir Khan - Microsoft (ref)
--- -"_We adopted the **FastAPI** library to spawn a **REST** server that can be queried to obtain **predictions**. [for Ludwig]_" + +"_**FastAPI**'ı **tahminlerimiz**'i sorgulanabilir hale getirmek için **REST** mimarisı ile beraber server üzerinde kullanmaya başladık._" +
Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - Uber (ref)
--- -"_**Netflix** is pleased to announce the open-source release of our **crisis management** orchestration framework: **Dispatch**! [built with **FastAPI**]_" + +"_**Netflix** **kriz yönetiminde** orkestrasyon yapabilmek için geliştirdiği yeni framework'ü **Dispatch**'in, açık kaynak versiyonunu paylaşmaktan gurur duyuyor. [**FastAPI** ile yapıldı.]_"
Kevin Glisson, Marc Vilanova, Forest Monsen - Netflix (ref)
--- -"_I’m over the moon excited about **FastAPI**. It’s so fun!_" + +"_**FastAPI** için ayın üzerindeymişcesine heyecanlıyım. Çok eğlenceli!_" +
Brian Okken - Python Bytes podcast host (ref)
--- -"_Honestly, what you've built looks super solid and polished. In many ways, it's what I wanted **Hug** to be - it's really inspiring to see someone build that._" +"_Dürüst olmak gerekirse, geliştirdiğin şey bir çok açıdan çok sağlam ve parlak gözüküyor. Açıkcası benim **Hug**'ı tasarlarken yapmaya çalıştığım şey buydu - bunu birisinin başardığını görmek gerçekten çok ilham verici._" -
Timothy Crosley - Hug creator (ref)
+
Timothy Crosley - Hug'ın Yaratıcısı (ref)
--- -"_If you're looking to learn one **modern framework** for building REST APIs, check out **FastAPI** [...] It's fast, easy to use and easy to learn [...]_" +"_Eğer REST API geliştirmek için **modern bir framework** öğrenme arayışında isen, **FastAPI**'a bir göz at [...] Hızlı, kullanımı ve öğrenmesi kolay. [...]_" -"_We've switched over to **FastAPI** for our **APIs** [...] I think you'll like it [...]_" +"_Biz **API** servislerimizi **FastAPI**'a geçirdik [...] Sizin de beğeneceğinizi düşünüyoruz. [...]_" -
Ines Montani - Matthew Honnibal - Explosion AI founders - spaCy creators (ref) - (ref)
+ + +
Ines Montani - Matthew Honnibal - Explosion AI kurucuları - spaCy yaratıcıları (ref) - (ref)
--- -## **Typer**, the FastAPI of CLIs +## **Typer**, komut satırı uygulamalarının FastAPI'ı -If you are building a CLI app to be used in the terminal instead of a web API, check out **Typer**. +Eğer API yerine komut satırı uygulaması geliştiriyor isen **Typer**'a bir göz at. -**Typer** is FastAPI's little sibling. And it's intended to be the **FastAPI of CLIs**. ⌨️ 🚀 +**Typer** kısaca FastAPI'ın küçük kız kardeşi. Komut satırı uygulamalarının **FastAPI'ı** olması hedeflendi. ⌨️ 🚀 -## Requirements +## Gereksinimler Python 3.6+ -FastAPI stands on the shoulders of giants: +FastAPI iki devin omuzları üstünde duruyor: -* Starlette for the web parts. -* Pydantic for the data parts. +* Web tarafı için Starlette. +* Data tarafı için Pydantic. -## Installation +## Yükleme
@@ -130,7 +138,7 @@ $ pip install fastapi
-You will also need an ASGI server, for production such as Uvicorn or Hypercorn. +Uygulamanı kullanılabilir hale getirmek için Uvicorn ya da Hypercorn gibi bir ASGI serverına ihtiyacın olacak.
@@ -142,11 +150,11 @@ $ pip install uvicorn[standard]
-## Example +## Örnek -### Create it +### Şimdi dene -* Create a file `main.py` with: +* `main.py` adında bir dosya oluştur : ```Python from typing import Optional @@ -167,9 +175,9 @@ def read_item(item_id: int, q: Optional[str] = None): ```
-Or use async def... +Ya da async def... -If your code uses `async` / `await`, use `async def`: +Eğer kodunda `async` / `await` var ise, `async def` kullan: ```Python hl_lines="9 14" from typing import Optional @@ -189,15 +197,15 @@ async def read_item(item_id: int, q: Optional[str] = None): return {"item_id": item_id, "q": q} ``` -**Note**: +**Not**: -If you don't know, check the _"In a hurry?"_ section about `async` and `await` in the docs. +Eğer ne olduğunu bilmiyor isen _"Acelen mi var?"_ kısmını oku `async` ve `await`.
-### Run it +### Çalıştır -Run the server with: +Serverı aşağıdaki komut ile çalıştır:
@@ -214,54 +222,54 @@ INFO: Application startup complete.
-About the command uvicorn main:app --reload... +Çalıştırdığımız uvicorn main:app --reload hakkında... -The command `uvicorn main:app` refers to: +`uvicorn main:app` şunları ifade ediyor: -* `main`: the file `main.py` (the Python "module"). -* `app`: the object created inside of `main.py` with the line `app = FastAPI()`. -* `--reload`: make the server restart after code changes. Only do this for development. +* `main`: dosya olan `main.py` (yani Python "modülü"). +* `app`: ise `main.py` dosyasının içerisinde oluşturduğumuz `app = FastAPI()` 'a denk geliyor. +* `--reload`: ise kodda herhangi bir değişiklik yaptığımızda serverın yapılan değişiklerileri algılayıp, değişiklikleri siz herhangi bir şey yapmadan uygulamasını sağlıyor.
-### Check it +### Dokümantasyonu kontrol et -Open your browser at http://127.0.0.1:8000/items/5?q=somequery. +Browserını aç ve şu linke git http://127.0.0.1:8000/items/5?q=somequery. -You will see the JSON response as: +Bir JSON yanıtı göreceksin: ```JSON {"item_id": 5, "q": "somequery"} ``` -You already created an API that: +Az önce oluşturduğun API: -* Receives HTTP requests in the _paths_ `/` and `/items/{item_id}`. -* Both _paths_ take `GET` operations (also known as HTTP _methods_). -* The _path_ `/items/{item_id}` has a _path parameter_ `item_id` that should be an `int`. -* The _path_ `/items/{item_id}` has an optional `str` _query parameter_ `q`. +* `/` ve `/items/{item_id}` adreslerine HTTP talebi alabilir hale geldi. +* İki _adresde_ `GET` operasyonlarını (HTTP _metodları_ olarakta bilinen) yapabilir hale geldi. +* `/items/{item_id}` _adresi_ ayrıca bir `item_id` _adres parametresine_ sahip ve bu bir `int` olmak zorunda. +* `/items/{item_id}` _adresi_ opsiyonel bir `str` _sorgu paramtersine_ sahip bu da `q`. -### Interactive API docs +### İnteraktif API dokümantasyonu -Now go to http://127.0.0.1:8000/docs. +Şimdi http://127.0.0.1:8000/docs adresine git. -You will see the automatic interactive API documentation (provided by Swagger UI): +Senin için otomatik oluşturulmuş(Swagger UI tarafından sağlanan) interaktif bir API dokümanı göreceksin: ![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) -### Alternative API docs +### Alternatif API dokümantasyonu -And now, go to http://127.0.0.1:8000/redoc. +Şimdi http://127.0.0.1:8000/redoc adresine git. -You will see the alternative automatic documentation (provided by ReDoc): +Senin için alternatif olarak (ReDoc tarafından sağlanan) bir API dokümantasyonu daha göreceksin: ![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) -## Example upgrade +## Örnek bir değişiklik -Now modify the file `main.py` to receive a body from a `PUT` request. +Şimdi `main.py` dosyasını değiştirelim ve body ile `PUT` talebi alabilir hale getirelim. -Declare the body using standard Python types, thanks to Pydantic. +Şimdi Pydantic sayesinde, Python'un standart tiplerini kullanarak bir body tanımlayacağız. ```Python hl_lines="4 9 10 11 12 25 26 27" from typing import Optional @@ -293,175 +301,175 @@ def update_item(item_id: int, item: Item): return {"item_name": item.name, "item_id": item_id} ``` -The server should reload automatically (because you added `--reload` to the `uvicorn` command above). +Server otomatik olarak yeniden başlamalı (çünkü yukarıda `uvicorn`'u çalıştırırken `--reload` parametresini kullandık.). -### Interactive API docs upgrade +### İnteraktif API dokümantasyonu'nda değiştirme yapmak -Now go to http://127.0.0.1:8000/docs. +Şimdi http://127.0.0.1:8000/docs bağlantısına tekrar git. -* The interactive API documentation will be automatically updated, including the new body: +* İnteraktif API dokümantasyonu, yeni body ile beraber çoktan yenilenmiş olması lazım: ![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) -* Click on the button "Try it out", it allows you to fill the parameters and directly interact with the API: +* "Try it out"a tıkla, bu senin API parametleri üzerinde deneme yapabilmene izin veriyor: ![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) -* Then click on the "Execute" button, the user interface will communicate with your API, send the parameters, get the results and show them on the screen: +* Şimdi "Execute" butonuna tıkla, kullanıcı arayüzü otomatik olarak API'ın ile bağlantı kurarak ona bu parametreleri gönderecek ve sonucu karşına getirecek. ![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) -### Alternative API docs upgrade +### Alternatif API dokümantasyonunda değiştirmek -And now, go to http://127.0.0.1:8000/redoc. +Şimdi ise http://127.0.0.1:8000/redoc adresine git. -* The alternative documentation will also reflect the new query parameter and body: +* Alternatif dokümantasyonda koddaki değişimler ile beraber kendini yeni query ve body ile güncelledi. ![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) -### Recap +### Özet -In summary, you declare **once** the types of parameters, body, etc. as function parameters. +Özetleyecek olursak, URL, sorgu veya request body'deki parametrelerini fonksiyon parametresi olarak kullanıyorsun. Bu parametrelerin veri tiplerini bir kere belirtmen yeterli. -You do that with standard modern Python types. +Type-hinting işlemini Python dilindeki standart veri tipleri ile yapabilirsin -You don't have to learn a new syntax, the methods or classes of a specific library, etc. +Yeni bir syntax'e alışmana gerek yok, metodlar ve classlar zaten spesifik kütüphanelere ait. -Just standard **Python 3.6+**. +Sadece standart **Python 3.6+**. -For example, for an `int`: +Örnek olarak, `int` tanımlamak için: ```Python item_id: int ``` -or for a more complex `Item` model: +ya da daha kompleks `Item` tipi: ```Python item: Item ``` -...and with that single declaration you get: +...sadece kısa bir parametre tipi belirtmekle beraber, sahip olacakların: -* Editor support, including: - * Completion. - * Type checks. -* Validation of data: - * Automatic and clear errors when the data is invalid. - * Validation even for deeply nested JSON objects. -* Conversion of input data: coming from the network to Python data and types. Reading from: +* Editör desteği dahil olmak üzere: + * Otomatik tamamlama. + * Tip sorguları. +* Datanın tipe uyumunun sorgulanması: + * Eğer data geçersiz ise, otomatik olarak hataları ayıklar. + * Çok derin JSON objelerinde bile veri tipi sorgusu yapar. +* Gelen verinin dönüşümünü aşağıdaki veri tiplerini kullanarak gerçekleştirebiliyor. * JSON. - * Path parameters. - * Query parameters. + * Path parametreleri. + * Query parametreleri. * Cookies. * Headers. * Forms. * Files. -* Conversion of output data: converting from Python data and types to network data (as JSON): - * Convert Python types (`str`, `int`, `float`, `bool`, `list`, etc). - * `datetime` objects. - * `UUID` objects. - * Database models. - * ...and many more. -* Automatic interactive API documentation, including 2 alternative user interfaces: +* Giden verinin dönüşümünü aşağıdaki veri tiplerini kullanarak gerçekleştirebiliyor (JSON olarak): + * Python tiplerinin (`str`, `int`, `float`, `bool`, `list`, vs) çevirisi. + * `datetime` objesi. + * `UUID` objesi. + * Veritabanı modelleri. + * ve daha fazlası... +* 2 alternatif kullanıcı arayüzü dahil olmak üzere, otomatik interaktif API dokümanu: * Swagger UI. * ReDoc. --- -Coming back to the previous code example, **FastAPI** will: +Az önceki kod örneğine geri dönelim, **FastAPI**'ın yapacaklarına bir bakış atalım: -* Validate that there is an `item_id` in the path for `GET` and `PUT` requests. -* Validate that the `item_id` is of type `int` for `GET` and `PUT` requests. - * If it is not, the client will see a useful, clear error. -* Check if there is an optional query parameter named `q` (as in `http://127.0.0.1:8000/items/foo?q=somequery`) for `GET` requests. - * As the `q` parameter is declared with `= None`, it is optional. - * Without the `None` it would be required (as is the body in the case with `PUT`). -* For `PUT` requests to `/items/{item_id}`, Read the body as JSON: - * Check that it has a required attribute `name` that should be a `str`. - * Check that it has a required attribute `price` that has to be a `float`. - * Check that it has an optional attribute `is_offer`, that should be a `bool`, if present. - * All this would also work for deeply nested JSON objects. -* Convert from and to JSON automatically. -* Document everything with OpenAPI, that can be used by: - * Interactive documentation systems. - * Automatic client code generation systems, for many languages. -* Provide 2 interactive documentation web interfaces directly. +* `item_id`'nin `GET` ve `PUT` talepleri içinde olup olmadığının doğruluğunu kontol edecek. +* `item_id`'nin tipinin `int` olduğunu `GET` ve `PUT` talepleri içinde olup olmadığının doğruluğunu kontol edecek. + * Eğer `GET` ve `PUT` içinde yok ise ve `int` değil ise, sebebini belirten bir hata mesajı gösterecek +* Opsiyonel bir `q` parametresinin `GET` talebi için (`http://127.0.0.1:8000/items/foo?q=somequery` içinde) olup olmadığını kontrol edecek + * `q` parametresini `= None` ile oluşturduğumuz için, opsiyonel bir parametre olacak. + * Eğer `None` olmasa zorunlu bir parametre olacak idi (bu yüzden body'de `PUT` parametresi var). +* `PUT` talebi için `/items/{item_id}`'nin body'sini, JSON olarak okuyor: + * `name` adında bir parametetre olup olmadığını ve var ise onun `str` olup olmadığını kontol ediyor. + * `price` adında bir parametetre olup olmadığını ve var ise onun `float` olup olmadığını kontol ediyor. + * `is_offer` adında bir parametetre olup olmadığını ve var ise onun `bool` olup olmadığını kontol ediyor. + * Bunların hepsini en derin JSON modellerinde bile yapacaktır. +* Bütün veri tiplerini otomatik olarak JSON'a çeviriyor veya tam tersi. +* Her şeyi dokümanlayıp, çeşitli yerlerde: + * İnteraktif dokümantasyon sistemleri. + * Otomatik alıcı kodu üretim sistemlerinde ve çeşitli dillerde. +* İki ayrı web arayüzüyle direkt olarak interaktif bir dokümantasyon sunuyor. --- -We just scratched the surface, but you already get the idea of how it all works. +Henüz yüzeysel bir bakış attık, fakat sen çoktan çalışma mantığını anladın. -Try changing the line with: +Şimdi aşağıdaki satırı değiştirmeyi dene: ```Python return {"item_name": item.name, "item_id": item_id} ``` -...from: +...bundan: ```Python ... "item_name": item.name ... ``` -...to: +...buna: ```Python ... "item_price": item.price ... ``` -...and see how your editor will auto-complete the attributes and know their types: +...şimdi editör desteğinin nasıl veri tiplerini bildiğini ve otomatik tamamladığını gör: ![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) -For a more complete example including more features, see the Tutorial - User Guide. +Daha fazla örnek ve özellik için Tutorial - User Guide sayfasını git. -**Spoiler alert**: the tutorial - user guide includes: +**Spoiler**: Öğretici - Kullanıcı rehberi şunları içeriyor: -* Declaration of **parameters** from other different places as: **headers**, **cookies**, **form fields** and **files**. -* How to set **validation constraints** as `maximum_length` or `regex`. -* A very powerful and easy to use **Dependency Injection** system. -* Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth. -* More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic). -* Many extra features (thanks to Starlette) as: +* **Parameterlerini** nasıl **headers**, **cookies**, **form fields** ve **files** olarak deklare edebileceğini. +* `maximum_length` ya da `regex` gibi şeylerle nasıl **doğrulama** yapabileceğini. +* Çok güçlü ve kullanımı kolay **Zorunluluk Entegrasyonu** oluşturmayı. +* Güvenlik ve kimlik doğrulama, **JWT tokenleri**'yle beraber **OAuth2** desteği, ve **HTTP Basic** doğrulaması. +* İleri seviye fakat ona göre oldukça basit olan **derince oluşturulmuş JSON modelleri** (Pydantic sayesinde). +* Diğer ekstra özellikler (Starlette sayesinde): * **WebSockets** * **GraphQL** - * extremely easy tests based on `requests` and `pytest` + * `requests` ve `pytest` sayesinde aşırı kolay testler. * **CORS** * **Cookie Sessions** - * ...and more. + * ...ve daha fazlası. -## Performance +## Performans -Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as one of the fastest Python frameworks available, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*) +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 yavaş ki FastAPI bunların üzerine kurulu. -To understand more about it, see the section Benchmarks. +Daha fazla bilgi için, bu bölüme bir göz at Benchmarks. -## Optional Dependencies +## Opsiyonel gereksinimler -Used by Pydantic: +Pydantic tarafında kullanılan: -* 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. +* aiofiles - `FileResponse` ya da `StaticFiles` 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. From c6461ad7dc9d37646a74ff0154cd202922b5e91e Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 5 Oct 2021 12:46:49 +0000 Subject: [PATCH 049/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 553fe0be..0b1cf1ab 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🌐 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). From 507e9baf9eb4d5164bea4eb4173c3cabeefc3149 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20Emre=20Arma=C4=9Fan?= Date: Tue, 5 Oct 2021 15:50:38 +0300 Subject: [PATCH 050/196] =?UTF-8?q?=F0=9F=8C=90=20Add=20Turkish=20translat?= =?UTF-8?q?ion=20for=20`docs/benchmarks.md`=20(#2729)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- docs/tr/docs/benchmarks.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 docs/tr/docs/benchmarks.md 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. From 85f0d2b924389faaf5b0a533d407738481803a51 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 5 Oct 2021 12:51:14 +0000 Subject: [PATCH 051/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 0b1cf1ab..435d54c2 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🌐 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). From 63744b2e8a14e07faa64e6442b0ea6d3298101a6 Mon Sep 17 00:00:00 2001 From: Yagiz Degirmenci <62724709+ycd@users.noreply.github.com> Date: Tue, 5 Oct 2021 16:01:50 +0300 Subject: [PATCH 052/196] =?UTF-8?q?=F0=9F=8C=90=20Add=20Turkish=20translat?= =?UTF-8?q?ion=20for=20`docs/features.md`=20(#1950)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- docs/tr/docs/features.md | 209 +++++++++++++++++++++++++++++++++++++++ docs/tr/mkdocs.yml | 1 + 2 files changed, 210 insertions(+) create mode 100644 docs/tr/docs/features.md diff --git a/docs/tr/docs/features.md b/docs/tr/docs/features.md new file mode 100644 index 00000000..c06c27c1 --- /dev/null +++ b/docs/tr/docs/features.md @@ -0,0 +1,209 @@ +# Özelikler + +## FastAPI özellikleri + +**FastAPI** sana bunları sağlıyor + +### Açık standartları temel alır + +* API oluşturma işlemlerinde OpenAPI buna path operasyonları parametreleri, body talebi, güvenlik gibi şeyler dahil olmak üzere deklare bunların deklare edilmesi. +* Otomatik olarak data modelinin JSON Schema ile beraber dokümante edilmesi (OpenAPI'n kendisi zaten JSON Schema'ya dayanıyor). +* Titiz bir çalışmanın sonucunda yukarıdaki standartlara uygun bir framework oluşturduk. Standartları pastanın üzerine sonradan eklenmiş bir çilek olarak görmedik. +* Ayrıca bu bir çok dilde kullanılabilecek **client code generator** kullanımına da izin veriyor. + +### Otomatik dokümantasyon + + +OpenAPI standartlarına dayalı olan bir framework olarak, geliştiricilerin birden çok seçeneği var, varsayılan olarak gelen 2 farklı interaktif API dokümantasyonu ve web kullanıcı arayüzü var. + + +* Swagger UI interaktif olarak API'ınızı tarayıcı üzerinden çağırıp test edebilmenize olanak sağlıyor. + +![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) + +* ReDoc ile beraber alternatif API dokümantasyonu. + +![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) + +### Sadece modern Python + +Tamamiyle standartlar **Python 3.6**'nın type hintlerine dayanıyor (Pydantic'in sayesinde). Yeni bir syntax öğrenmene gerek yok. Sadece modern Python. + + +Eğer Python type hintlerini bilmiyorsan veya bir hatırlatmaya ihtiyacın var ise(FastAPI kullanmasan bile) şu iki dakikalık küçük bilgilendirici içeriğe bir göz at: [Python Types](python-types.md){.internal-link target=_blank}. + +Standart Python'u typelarını belirterek yazıyorsun: + +```Python +from typing import List, Dict +from datetime import date + +from pydantic import BaseModel + +# Değişkeni str olarak belirt +# ve o fonksiyon için harika bir editör desteği al +def main(user_id: str): + return user_id + + +# Pydantic modeli +class User(BaseModel): + id: int + name: str + joined: date +``` + +Sonrasında bu şekilde kullanabilirsin + +```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` şu anlama geliyor: + + Key-Value çiftini direkt olarak `second_user_data` dictionarysine kaydet , yaptığın şey buna eşit olacak: `User(id=4, name="Mary", joined="2018-11-30")` + +### Editor desteği + +Bütün framework kullanılması kolay ve sezgileri güçlü olması için tasarlandı, verilen bütün kararlar geliştiricilere en iyi geliştirme deneyimini yaşatmak üzere, bir çok editör üzerinde test edildi. + +Son yapılan Python geliştiricileri anketinde, açık ara en çok kullanılan özellik "oto-tamamlama" idi.. + +Bütün **FastAPI** frameworkü oto-tamamlama açısından geliştiriciyi tatmin etmek üzerine tasarlandı. Otomatik tamamlama her yerde çalışıyor. + +Dokümantasyona tekrardan çok nadir olarak geleceksin. + +Editörün sana nasıl yardım ettiğine bir bak: + +* Visual Studio Code ile: + +![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) + +* PyCharm ile: + +![editor support](https://fastapi.tiangolo.com/img/pycharm-completion.png) + + +Daha önceden düşünüp en imkansız diyebileceğin durumlarda bile otomatik tamamlama alacaksın, örnek olarak `price` JSON body içerisinde (nested bir JSON body de olabilirdi.) direkt olarak istekten geliyor, bu durumda bile oto-tammalama sağlıyor. + +Artık key isimlerini yanlış yazma, dokümantasyona dönüp deliler gibi yukarı aşağı sayfada gezmek ve en sonunda `username` mi yoksa `user_name` mi kullandım gibi sorular yok. + +### Kısa + +Her şey için mantıklı bir **varsayılanı** var. Parametrelerini opsiyonel olarak tanımlayıp API'nı istediğin gibi modifiye edebilirsin. + +Hepsi varsayılan olarak **çalışıyor**. + + +### Doğrulama + +* Neredeyse bütün (ya da hepsi?) Python **data typeları** için doğrulama, kapsadıkları: + * JSON objeleri (`dict`). + * JSON array (`list`) item type'ı belirtirken. + * String (`str`) parametresi, minimum ve maksimum uzunluk gibi sınırlandırmalar yaparken. + * Numaralar (`int`, `float`) maksimum ve minimum gibi sınırlandırmalar yaparken. + +* Bunlar gibi en egzotik typelarla bile doğrulama yapabiliyorsunuz.: + * URL. + * Email. + * UUID. + * ...ve diğerleri. + +Bütün doğrulama olayları çok güçlü bir kütüphane sayesinde yapılıyor, **Pydantic**. + +### Güvenlik ve kimlik doğrulama + +Güvenlik ve doğrulama database ve data modellerinden taviz vermeden entegre edilebilir durumda. + +Bütün güvenlik şemaları OpenAPI'da tanımlanmış durumda, kapsadıkları: + +* HTTP Basic. +* **OAuth2** (ve **JWT tokenleriyle** beraber). Bu öğretici içeriğe göz atabilirsin [OAuth2 with JWT](tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. +* API anahtarları: + * Headerlar. + * Query parametreleri. + * Cookies, vs. + +Bütün güvenlik özellikleri Starlette'den geliyor (**session cookies'de** dahil olmak üzere). + +Bütün hepsi tekrardan kullanılabilir aletler ve bileşenler olarak, kolayca sistemlerinize, data depolarınıza, ilişkisel ve NoSQL databaselerinize entegre edebileceğiniz şekilde yapıldı. + +### Dependency injection + +FastAPI'ın inanılmaz derecede kullanımı kolay, fakat inanılmaz derecede güçlü Dependency Injection sistemi var. + +* Dependencylerin bile dependencies'i olabiliyor, FastAPI bunun için **graph of "dependency"** yaratıyor. +* Hepsi **otomatik olarak** FastAPI tarafından hallediliyor. +* Bütün zorunlulukların gelen datalara bağlı olarak farklı gereksinimleri olabiliyor, ilave path operasyonlarının kısıtlamaları ve otomatik dokümantasyonu da ayrıca yapılıyor . +* Path operasyonu parametreleri içerisinde belirtilen gereksinimler için bile **Otomatik doğrulama** yapılabiliyor. +* Kompleks kimlik doğrulama sistemleri için destek, **database bağlantıları**, vs. +* **Taviz yok** hiçbir şeyden taviz vermeden, database frontend vs. Bütün hepsinin kolayca entegre edilebiliyor. + +### Sınırsız "plug-inler" + +Başka bir deyişle, plug-inlere ihtiyacımız yok, import edip direkt olarak kullanmaya başlayabiliriz. + +Bütün entegrasyonlar kullanımı kolay olmak üzere (zorunluluklar ile beraber) tasarlandı, sen bir "plug-in" yaratıp 2 satır kod ile, *path operasyonlarında* kullandığımız syntax ve aynı yapı ile koduna entregre edebilirsin. + + +### Test edildi + +* 100% test coverage. +* 100% typeları belirtilmiş codebase. +* FastAPI ile yapılan bir çok proje insanlar tarafından kullanılıyor. + +## Starlette özellikleri + +**FastAPI**, Starlette ile tamamiyle uyumlu ve üzerine kurulu. Yani FastAPI üzerine ekleme yapacağınız herhangi bir Starlette kodu da çalışacaktır. + +`FastAPI` aslında `Starlette`'nin bir sub-class'ı. Eğer Starlette'nin nasıl kullanılacağını biliyor isen, çoğu işlevini aynı şekilde yapıyor. + +**FastAPI** ile beraber **Starlette**'nin bütün özelliklerine de sahip olacaksınız (FastAPI aslında Starlette'nin steroid basmış hali): + +* Gerçekten etkileyici bir performansa sahip.Python'un ise en hızlı frameworklerinden bir tanesi, **NodeJS** ve **Go** ile ise eşdeğer performansa sahip.. +* **WebSocket** desteği. +* **GraphQL** desteği. +* Kullanım halinde arka plan işlevleri. +* Başlatma ve kapatma eventleri(startup and shutdown). +* Test sunucusu `requests` üzerine kurulu. +* **CORS**, GZip, Static dosyalar, Streaming responseları. +* **Session and Cookie** desteği. +* 100% test kapsayıcılığı. +* 100% typeları belirtilmiş codebase. + +## Pydantic özellikleri + +**FastAPI** ile Pydantic tamamiyle uyumlu ve üzerine kurulu. Yani FastAPI üzerine ekleme yapacağınız herhangi bir Pydantic kodu da çalışacaktır. + +Bunlara Pydantic üzerine kurulu ORM databaseler ve , ODM kütüphaneler de dahil olmak üzere. + +Bu ayrıca şu anlama da geliyor, bir çok durumda requestten gelen objeyi **direkt olarak database**'e her şeyi otomatik olarak doğrulanmış bir biçimde aktarabilirisin. + +Aynı şekilde, databaseden gelen objeyi de **direkt olarak isteğe** de tamamiyle doğrulanmış bir biçimde gönderebilirsiniz. + +**FastAPI** ile beraber **Pydantic**'in bütün özelliklerine sahip olacaksınız (FastAPI data kontrolünü Pydantic'in üzerine kurduğu için): + +* **Kafa karıştırmaz**: + * Farklı bir syntax öğrenmenize gerek kalmaz, + * Eğer Python typelarını nasıl kullanacağını biliyorsan Pydantic kullanmayı da biliyorsundur. +* Kullandığın geliştirme araçları ile iyi çalışır **IDE/linter/brain**: + * Pydantic'in veri yapıları aslında sadece senin tanımladığın classlar; Bu yüzden doğrulanmış dataların ile otomatik tamamlama, linting ve mypy'ı kullanarak sorunsuz bir şekilde çalışabilirsin +* **Hızlı**: + * Benchmarklarda, Pydantic'in diğer bütün test edilmiş bütün kütüphanelerden daha hızlı. +* **En kompleks** yapıları bile doğrula: + * Hiyerarşik Pydantic modellerinin kullanımı ile beraber, Python `typing`’s `List` and `Dict`, vs gibi şeyleri doğrula. + * Doğrulayıcılar en kompleks data şemalarının bile temiz ve kolay bir şekilde tanımlanmasına izin veriyor, ve hepsi JSON şeması olarak dokümante ediliyor + * Pydantic, JSON objen ne kadar derin (nested) olursa olsun doğrulamasını ve gösterimini yapıyor +* **Genişletilebilir**: + * Pydantic özelleştirilmiş data tiplerinin tanımlanmasının yapılmasına izin veriyor ayrıca validator decoratorü ile senin doğrulamaları genişletip, kendi doğrulayıcılarını yazmana izin veriyor. +* 100% test kapsayıcılığı. + diff --git a/docs/tr/mkdocs.yml b/docs/tr/mkdocs.yml index 5e429fde..d4ff63d0 100644 --- a/docs/tr/mkdocs.yml +++ b/docs/tr/mkdocs.yml @@ -54,6 +54,7 @@ nav: - tr: /tr/ - uk: /uk/ - zh: /zh/ +- features.md markdown_extensions: - toc: permalink: true From 19fd336cde290606e37cfbc059ca15360f2de9bc Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 5 Oct 2021 13:02:28 +0000 Subject: [PATCH 053/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 435d54c2..16a807dd 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🌐 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). From eb515b1af75d9d1bf7c00a0ca4c994ba346b6978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 5 Oct 2021 15:31:39 +0200 Subject: [PATCH 054/196] =?UTF-8?q?=F0=9F=93=9D=20Re-structure=20release?= =?UTF-8?q?=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 16a807dd..2cf6f9ea 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,22 @@ ## Latest Changes + +### 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). @@ -9,21 +25,17 @@ * 🌐 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). -* ✨ Update GitHub Action: notify-translations, to avoid a race conditon. PR [#3989](https://github.com/tiangolo/fastapi/pull/3989) by [@tiangolo](https://github.com/tiangolo). +* 🌐 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). -* ⬆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). -* ⬆ 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). -* ⬆️ Bump Uvicorn max range to 0.15.0. PR [#3345](https://github.com/tiangolo/fastapi/pull/3345) by [@Kludex](https://github.com/Kludex). * 💚 Fix badges in README and main page. PR [#3979](https://github.com/tiangolo/fastapi/pull/3979) by [@ghandic](https://github.com/ghandic). -* 📝 Update GraphQL docs, recommend Strawberry. PR [#3981](https://github.com/tiangolo/fastapi/pull/3981) by [@tiangolo](https://github.com/tiangolo). +* ⬆ 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). -* 📝 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). -* 🌐 Add German translation for `docs/features.md`. PR [#3699](https://github.com/tiangolo/fastapi/pull/3699) by [@mawassk](https://github.com/mawassk). * 🎨 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). From 378fa4ef755a0f814cd61daf571466fde3af9b05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 5 Oct 2021 15:32:18 +0200 Subject: [PATCH 055/196] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.68?= =?UTF-8?q?.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 7 +++++++ fastapi/__init__.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 2cf6f9ea..56bb42bd 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,13 @@ ## Latest Changes +## 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 diff --git a/fastapi/__init__.py b/fastapi/__init__.py index 31636266..ba2b9abf 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.68.1" +__version__ = "0.68.2" from starlette import status as status From 2210c84efd16668e9561fd448f80abe6249b27da Mon Sep 17 00:00:00 2001 From: bilal alpaslan <47563997+BilalAlpaslan@users.noreply.github.com> Date: Tue, 5 Oct 2021 18:47:51 +0300 Subject: [PATCH 056/196] =?UTF-8?q?=F0=9F=8C=90=20Add=20Turkish=20translat?= =?UTF-8?q?ion=20for=20`docs/fastapi-people.md`=20(#3848)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- docs/tr/docs/fastapi-people.md | 178 +++++++++++++++++++++++++++++++++ docs/tr/mkdocs.yml | 1 + 2 files changed, 179 insertions(+) create mode 100644 docs/tr/docs/fastapi-people.md 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 %} +
+{% for user in people.maintainers %} + +
@{{ user.login }}
Answers: {{ user.answers }}
Pull Requests: {{ user.prs }}
+{% endfor %} + +
+{% endif %} + +Ben **FastAPI** 'nin yazarı ve geliştiricisiyim. Bununla ilgili daha fazla bilgiyi şurada okuyabilirsiniz: + [FastAPI yardım - yardım al - Yazar ile iletişime geç](help-fastapi.md#connect-with-the-author){.internal-link target=_blank}. + +... Burada size harika FastAPI topluluğunu göstermek istiyorum. + +--- + +**FastAPI** topluluğundan destek alıyor. Ve katkıda bulunanları vurgulamak istiyorum. + +İşte o mükemmel insanlar: + +* [GitHubdaki sorunları (issues) çözmelerinde diğerlerine yardım et](help-fastapi.md#help-others-with-issues-in-github){.internal-link target=_blank}. +* [Pull Requests oluşturun](help-fastapi.md#create-a-pull-request){.internal-link target=_blank}. +* Pull Requests 'leri gözden geçirin, [özelliklede çevirileri](contributing.md#translations){.internal-link target=_blank}. + +Onlara bir alkış. 👏 🙇 + +## Geçen ayın en aktif kullanıcıları + +Bunlar geçen ay boyunca [GitHub' da başkalarına sorunlarında (issues) en çok yardımcı olan ](help-fastapi.md#help-others-with-issues-in-github){.internal-link target=_blank} kullanıcılar ☕ + +{% if people %} +
+{% for user in people.last_month_active %} + +
@{{ user.login }}
Issues replied: {{ user.count }}
+{% endfor %} + +
+{% endif %} + +## Uzmanlar + +İşte **FastAPI Uzmanları**. 🤓 + +Bunlar *tüm zamanlar boyunca* [GitHub' da başkalarına sorunlarında (issues) en çok yardımcı olan](help-fastapi.md#help-others-with-issues-in-github){.internal-link target=_blank} kullanıcılar. + +Başkalarına yardım ederek uzman olduklarını kanıtladılar. ✨ + +{% if people %} +
+{% for user in people.experts %} + +
@{{ user.login }}
Issues replied: {{ user.count }}
+{% endfor %} + +
+{% endif %} + +## En fazla katkıda bulunanlar + +işte **En fazla katkıda bulunanlar**. 👷 + +Bu kullanıcılar en çok [Pull Requests oluşturan](help-fastapi.md#create-a-pull-request){.internal-link target=_blank} ve onu kaynak koduna *birleştirenler*. + +Kaynak koduna, belgelere, çevirilere vb. katkıda bulundular. 📦 + +{% if people %} +
+{% for user in people.top_contributors %} + +
@{{ user.login }}
Pull Requests: {{ user.count }}
+{% endfor %} + +
+{% endif %} + +Çok fazla katkıda bulunan var (binden fazla), hepsini şurda görebilirsin: FastAPI GitHub Katkıda Bulunanlar. 👷 + +## En fazla inceleme yapanlar + +İşte **En fazla inceleme yapanlar**. 🕵️ + +### Çeviri için İncelemeler + +Yalnızca birkaç dil konuşabiliyorum (ve çok da iyi değilim 😅). Bu yüzden döküman çevirilerini [**onaylama yetkisi**](contributing.md#translations){.internal-link target=_blank} siz inceleyenlere aittir. Sizler olmadan diğer birkaç dilde dokümantasyon olmazdı. + +--- + +**En fazla inceleme yapanlar** 🕵️ kodun, belgelerin ve özellikle **çevirilerin** kalitesini sağlamak için diğerlerinden daha fazla pull requests incelemiştir. + +{% if people %} +
+{% for user in people.top_reviewers %} + +
@{{ user.login }}
Reviews: {{ user.count }}
+{% endfor %} + +
+{% endif %} + +## Sponsorlar + +işte **Sponsorlarımız**. 😎 + +**FastAPI** ve diğer projelerde çalışmamı destekliyorlar, özellikle de GitHub Sponsorları. + +### Altın Sponsorlar + +{% if sponsors %} +{% for sponsor in sponsors.gold -%} + +{% endfor %} +{% endif %} + +### Gümüş Sponsorlar + +{% if sponsors %} +{% for sponsor in sponsors.silver -%} + +{% endfor %} +{% endif %} + +### Bronz Sponsorlar + +{% if sponsors %} +{% for sponsor in sponsors.bronze -%} + +{% endfor %} +{% endif %} + +### Bireysel Sponsorlar + +{% if people %} +{% if people.sponsors_50 %} + +
+{% for user in people.sponsors_50 %} + + +{% endfor %} + +
+ +{% endif %} +{% endif %} + +{% if people %} +
+{% for user in people.sponsors %} + + +{% endfor %} + +
+{% endif %} + +## Veriler hakkında - Teknik detaylar + +Bu sayfanın temel amacı, topluluğun başkalarına yardım etme çabasını vurgulamaktır. + +Özellikle normalde daha az görünür olan ve çoğu durumda daha zahmetli olan, diğerlerine sorunlar konusunda yardımcı olmak ve pull requests'leri gözden geçirmek gibi çabalar dahil. + +Veriler ayda bir hesaplanır, işte kaynak kodu okuyabilirsin :source code here. + +Burada sponsorların katkılarını da tekrardan vurgulamak isterim. + +Ayrıca algoritmayı, bölümleri, eşikleri vb. güncelleme hakkımı da saklı tutarım (her ihtimale karşı 🤷). diff --git a/docs/tr/mkdocs.yml b/docs/tr/mkdocs.yml index d4ff63d0..f2936667 100644 --- a/docs/tr/mkdocs.yml +++ b/docs/tr/mkdocs.yml @@ -55,6 +55,7 @@ nav: - uk: /uk/ - zh: /zh/ - features.md +- fastapi-people.md markdown_extensions: - toc: permalink: true From 67d5ba1efd0e4f588c6c411cac6299fc4b364ce2 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 5 Oct 2021 15:53:54 +0000 Subject: [PATCH 057/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 56bb42bd..030b04b7 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🌐 Add Turkish translation for `docs/fastapi-people.md`. PR [#3848](https://github.com/tiangolo/fastapi/pull/3848) by [@BilalAlpaslan](https://github.com/BilalAlpaslan). ## 0.68.2 This release has **no breaking changes**. 🎉 From cc0d0f3899d94d3e964fd8a1bc8aced83c79e8ef Mon Sep 17 00:00:00 2001 From: Madat Bay <46858323+madatbay@users.noreply.github.com> Date: Tue, 5 Oct 2021 20:07:25 +0400 Subject: [PATCH 058/196] =?UTF-8?q?=F0=9F=8C=90=20Initialize=20Azerbaijani?= =?UTF-8?q?=20translations=20(#3941)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- docs/az/mkdocs.yml | 128 +++++++++++++++++++++++++++++++++++ docs/az/overrides/.gitignore | 0 docs/de/mkdocs.yml | 3 + docs/en/mkdocs.yml | 6 +- docs/es/mkdocs.yml | 3 + docs/fr/mkdocs.yml | 3 + docs/id/mkdocs.yml | 3 + docs/it/mkdocs.yml | 3 + docs/ja/mkdocs.yml | 3 + docs/ko/mkdocs.yml | 3 + docs/pl/mkdocs.yml | 3 + docs/pt/mkdocs.yml | 3 + docs/ru/mkdocs.yml | 3 + docs/sq/mkdocs.yml | 3 + docs/tr/mkdocs.yml | 3 + docs/uk/mkdocs.yml | 3 + docs/zh/mkdocs.yml | 3 + 17 files changed, 173 insertions(+), 3 deletions(-) create mode 100644 docs/az/mkdocs.yml create mode 100644 docs/az/overrides/.gitignore diff --git a/docs/az/mkdocs.yml b/docs/az/mkdocs.yml new file mode 100644 index 00000000..edeb6c2e --- /dev/null +++ b/docs/az/mkdocs.yml @@ -0,0 +1,128 @@ +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-outline + name: Switch to light mode + - scheme: slate + primary: teal + accent: amber + toggle: + icon: material/lightbulb + 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: '' +google_analytics: +- UA-133183413-1 +- auto +plugins: +- search +- markdownextradata: + data: data +nav: +- FastAPI: index.md +- Languages: + - en: / + - az: /az/ + - de: /de/ + - es: /es/ + - fr: /fr/ + - id: /id/ + - it: /it/ + - ja: /ja/ + - ko: /ko/ + - 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 +extra: + 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: /fr/ + name: fr - français + - link: /id/ + name: id + - link: /it/ + name: it - italiano + - link: /ja/ + name: ja - 日本語 + - link: /ko/ + name: ko - 한국어 + - 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/mkdocs.yml b/docs/de/mkdocs.yml index 77882db0..bcd521c8 100644 --- a/docs/de/mkdocs.yml +++ b/docs/de/mkdocs.yml @@ -40,6 +40,7 @@ nav: - FastAPI: index.md - Languages: - en: / + - az: /az/ - de: /de/ - es: /es/ - fr: /fr/ @@ -90,6 +91,8 @@ extra: alternate: - link: / name: en - English + - link: /az/ + name: az - link: /de/ name: de - link: /es/ diff --git a/docs/en/mkdocs.yml b/docs/en/mkdocs.yml index b3246878..28a688a9 100644 --- a/docs/en/mkdocs.yml +++ b/docs/en/mkdocs.yml @@ -40,6 +40,7 @@ nav: - FastAPI: index.md - Languages: - en: / + - az: /az/ - de: /de/ - es: /es/ - fr: /fr/ @@ -165,9 +166,6 @@ markdown_extensions: permalink: true - markdown.extensions.codehilite: guess_lang: false -# Uncomment these 2 lines during development to more easily add highlights -# - pymdownx.highlight: -# linenums: true - mdx_include: base_path: docs - admonition @@ -198,6 +196,8 @@ extra: alternate: - link: / name: en - English + - link: /az/ + name: az - link: /de/ name: de - link: /es/ diff --git a/docs/es/mkdocs.yml b/docs/es/mkdocs.yml index 3c0c65c8..35127ac2 100644 --- a/docs/es/mkdocs.yml +++ b/docs/es/mkdocs.yml @@ -40,6 +40,7 @@ nav: - FastAPI: index.md - Languages: - en: / + - az: /az/ - de: /de/ - es: /es/ - fr: /fr/ @@ -99,6 +100,8 @@ extra: alternate: - link: / name: en - English + - link: /az/ + name: az - link: /de/ name: de - link: /es/ diff --git a/docs/fr/mkdocs.yml b/docs/fr/mkdocs.yml index 018c2bd3..4cd0750c 100644 --- a/docs/fr/mkdocs.yml +++ b/docs/fr/mkdocs.yml @@ -40,6 +40,7 @@ nav: - FastAPI: index.md - Languages: - en: / + - az: /az/ - de: /de/ - es: /es/ - fr: /fr/ @@ -101,6 +102,8 @@ extra: alternate: - link: / name: en - English + - link: /az/ + name: az - link: /de/ name: de - link: /es/ diff --git a/docs/id/mkdocs.yml b/docs/id/mkdocs.yml index 88028533..31488ee3 100644 --- a/docs/id/mkdocs.yml +++ b/docs/id/mkdocs.yml @@ -40,6 +40,7 @@ nav: - FastAPI: index.md - Languages: - en: / + - az: /az/ - de: /de/ - es: /es/ - fr: /fr/ @@ -89,6 +90,8 @@ extra: alternate: - link: / name: en - English + - link: /az/ + name: az - link: /de/ name: de - link: /es/ diff --git a/docs/it/mkdocs.yml b/docs/it/mkdocs.yml index 477f9c55..10e6198f 100644 --- a/docs/it/mkdocs.yml +++ b/docs/it/mkdocs.yml @@ -40,6 +40,7 @@ nav: - FastAPI: index.md - Languages: - en: / + - az: /az/ - de: /de/ - es: /es/ - fr: /fr/ @@ -89,6 +90,8 @@ extra: alternate: - link: / name: en - English + - link: /az/ + name: az - link: /de/ name: de - link: /es/ diff --git a/docs/ja/mkdocs.yml b/docs/ja/mkdocs.yml index 2617fec9..37f6548d 100644 --- a/docs/ja/mkdocs.yml +++ b/docs/ja/mkdocs.yml @@ -40,6 +40,7 @@ nav: - FastAPI: index.md - Languages: - en: / + - az: /az/ - de: /de/ - es: /es/ - fr: /fr/ @@ -129,6 +130,8 @@ extra: alternate: - link: / name: en - English + - link: /az/ + name: az - link: /de/ name: de - link: /es/ diff --git a/docs/ko/mkdocs.yml b/docs/ko/mkdocs.yml index 6ee22afb..bb66c898 100644 --- a/docs/ko/mkdocs.yml +++ b/docs/ko/mkdocs.yml @@ -40,6 +40,7 @@ nav: - FastAPI: index.md - Languages: - en: / + - az: /az/ - de: /de/ - es: /es/ - fr: /fr/ @@ -95,6 +96,8 @@ extra: alternate: - link: / name: en - English + - link: /az/ + name: az - link: /de/ name: de - link: /es/ diff --git a/docs/pl/mkdocs.yml b/docs/pl/mkdocs.yml index 37995778..0775048e 100644 --- a/docs/pl/mkdocs.yml +++ b/docs/pl/mkdocs.yml @@ -40,6 +40,7 @@ nav: - FastAPI: index.md - Languages: - en: / + - az: /az/ - de: /de/ - es: /es/ - fr: /fr/ @@ -89,6 +90,8 @@ extra: alternate: - link: / name: en - English + - link: /az/ + name: az - link: /de/ name: de - link: /es/ diff --git a/docs/pt/mkdocs.yml b/docs/pt/mkdocs.yml index 567d21cc..3e5fece5 100644 --- a/docs/pt/mkdocs.yml +++ b/docs/pt/mkdocs.yml @@ -40,6 +40,7 @@ nav: - FastAPI: index.md - Languages: - en: / + - az: /az/ - de: /de/ - es: /es/ - fr: /fr/ @@ -108,6 +109,8 @@ extra: alternate: - link: / name: en - English + - link: /az/ + name: az - link: /de/ name: de - link: /es/ diff --git a/docs/ru/mkdocs.yml b/docs/ru/mkdocs.yml index 359cce8e..eee98a9b 100644 --- a/docs/ru/mkdocs.yml +++ b/docs/ru/mkdocs.yml @@ -40,6 +40,7 @@ nav: - FastAPI: index.md - Languages: - en: / + - az: /az/ - de: /de/ - es: /es/ - fr: /fr/ @@ -89,6 +90,8 @@ extra: alternate: - link: / name: en - English + - link: /az/ + name: az - link: /de/ name: de - link: /es/ diff --git a/docs/sq/mkdocs.yml b/docs/sq/mkdocs.yml index 53019fea..1d2adbb7 100644 --- a/docs/sq/mkdocs.yml +++ b/docs/sq/mkdocs.yml @@ -40,6 +40,7 @@ nav: - FastAPI: index.md - Languages: - en: / + - az: /az/ - de: /de/ - es: /es/ - fr: /fr/ @@ -89,6 +90,8 @@ extra: alternate: - link: / name: en - English + - link: /az/ + name: az - link: /de/ name: de - link: /es/ diff --git a/docs/tr/mkdocs.yml b/docs/tr/mkdocs.yml index f2936667..d78947fa 100644 --- a/docs/tr/mkdocs.yml +++ b/docs/tr/mkdocs.yml @@ -40,6 +40,7 @@ nav: - FastAPI: index.md - Languages: - en: / + - az: /az/ - de: /de/ - es: /es/ - fr: /fr/ @@ -91,6 +92,8 @@ extra: alternate: - link: / name: en - English + - link: /az/ + name: az - link: /de/ name: de - link: /es/ diff --git a/docs/uk/mkdocs.yml b/docs/uk/mkdocs.yml index 4d8636bd..41aec880 100644 --- a/docs/uk/mkdocs.yml +++ b/docs/uk/mkdocs.yml @@ -40,6 +40,7 @@ nav: - FastAPI: index.md - Languages: - en: / + - az: /az/ - de: /de/ - es: /es/ - fr: /fr/ @@ -89,6 +90,8 @@ extra: alternate: - link: / name: en - English + - link: /az/ + name: az - link: /de/ name: de - link: /es/ diff --git a/docs/zh/mkdocs.yml b/docs/zh/mkdocs.yml index 71391993..3f27884e 100644 --- a/docs/zh/mkdocs.yml +++ b/docs/zh/mkdocs.yml @@ -40,6 +40,7 @@ nav: - FastAPI: index.md - Languages: - en: / + - az: /az/ - de: /de/ - es: /es/ - fr: /fr/ @@ -139,6 +140,8 @@ extra: alternate: - link: / name: en - English + - link: /az/ + name: az - link: /de/ name: de - link: /es/ From ac0f594305a059a30b8c0c29c48ee79df95d5e4e Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 5 Oct 2021 16:08:13 +0000 Subject: [PATCH 059/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 030b04b7..1fc165d5 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🌐 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). ## 0.68.2 From cce8d9e32cb7efcd6fcdb361f1e407d7ec2cdb76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 5 Oct 2021 18:09:45 +0200 Subject: [PATCH 060/196] =?UTF-8?q?=F0=9F=94=A7=20Add=20GitHub=20Action=20?= =?UTF-8?q?notify-translations=20config=20for=20Azerbaijani=20(#3995)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/actions/notify-translations/app/translations.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/notify-translations/app/translations.yml b/.github/actions/notify-translations/app/translations.yml index 3f53adb6..decd6349 100644 --- a/.github/actions/notify-translations/app/translations.yml +++ b/.github/actions/notify-translations/app/translations.yml @@ -12,3 +12,4 @@ sq: 2041 pl: 3169 de: 3716 id: 3717 +az: 3994 From 292b8c8ce96b04dba358ae3d53a5eea2a9c04127 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 5 Oct 2021 16:10:28 +0000 Subject: [PATCH 061/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 1fc165d5..b9c9b9e9 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🔧 Add GitHub Action notify-translations config for Azerbaijani. PR [#3995](https://github.com/tiangolo/fastapi/pull/3995) by [@tiangolo](https://github.com/tiangolo). * 🌐 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). ## 0.68.2 From eb1d68c789d320dc3edcb2d8d1e462e9244df874 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Wed, 6 Oct 2021 17:08:57 +0200 Subject: [PATCH 062/196] =?UTF-8?q?=F0=9F=94=A7=20Lint=20only=20in=20Pytho?= =?UTF-8?q?n=203.7=20and=20above=20(#4006)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test.yml | 3 +++ scripts/test.sh | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 02507a9d..7ab0f123 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,6 +32,9 @@ jobs: - name: Install Dependencies if: steps.cache.outputs.cache-hit != 'true' run: flit install --symlink + - name: Lint + if: ${{ matrix.python-version != '3.6' }} + run: bash scripts/lint.sh - name: Test run: bash scripts/test.sh - name: Upload coverage diff --git a/scripts/test.sh b/scripts/test.sh index 74ed7a1f..d445ca17 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -3,7 +3,6 @@ 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 From 4d26fa5c546f68ddb67bd3e6c194c644323aadaa Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 6 Oct 2021 15:09:36 +0000 Subject: [PATCH 063/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index b9c9b9e9..86805922 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🔧 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). * 🌐 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). From 11d0a08acda2f4bbc21ca1c5df92b34d8758c842 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 6 Oct 2021 16:32:11 +0100 Subject: [PATCH 064/196] =?UTF-8?q?=E2=9C=A8=20Add=20support=20for=20Trio?= =?UTF-8?q?=20via=20AnyIO=20(#3372)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- README.md | 1 - docs/en/docs/advanced/async-tests.md | 20 ++------ docs/en/docs/advanced/extending-openapi.md | 15 ------ docs/en/docs/advanced/templates.md | 12 ----- docs/en/docs/index.md | 1 - .../dependencies/dependencies-with-yield.md | 9 ---- docs/en/docs/tutorial/sql-databases.md | 11 ----- docs/en/docs/tutorial/static-files.md | 14 ------ docs_src/async_tests/test_main.py | 2 +- fastapi/concurrency.py | 45 ++++++------------ fastapi/dependencies/utils.py | 46 ++++++------------- pyproject.toml | 16 +++---- tests/test_fakeasync.py | 12 ----- .../test_async_tests/test_main.py | 2 +- .../test_websockets/test_tutorial002.py | 10 +++- 15 files changed, 49 insertions(+), 167 deletions(-) delete mode 100644 tests/test_fakeasync.py diff --git a/README.md b/README.md index 3055e670..d376fb74 100644 --- a/README.md +++ b/README.md @@ -442,7 +442,6 @@ Used by Pydantic: Used by 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. 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: - -
- -```console -$ pip install pytest-asyncio - ----> 100% -``` - -
+If we want to call asynchronous functions in our tests, our test functions have to be asynchronous. Anyio provides a neat plugin for this, that allows us to specify that some test functions are to be called asynchronously. ## HTTPX @@ -66,7 +54,7 @@ $ pytest ## In Detail -The marker `@pytest.mark.asyncio` tells pytest that this test function should be called asynchronously: +The marker `@pytest.mark.anyio` tells pytest that this test function should be called asynchronously: ```Python hl_lines="7" {!../../../docs_src/async_tests/test_main.py!} @@ -97,4 +85,4 @@ that we used to make our requests with the `TestClient`. As the testing function is now asynchronous, you can now also call (and `await`) other `async` functions apart from sending requests to your FastAPI application in your tests, exactly as you would call them anywhere else in your code. !!! tip - If you encounter a `RuntimeError: Task attached to a different loop` when integrating asynchronous function calls in your tests (e.g. when using MongoDB's MotorClient) check out this issue in the pytest-asyncio repository. + If you encounter a `RuntimeError: Task attached to a different loop` when integrating asynchronous function calls in your tests (e.g. when using MongoDB's MotorClient) Remember to instantiate objects that need an event loop only within async functions, e.g. an `'@app.on_event("startup")` callback. diff --git a/docs/en/docs/advanced/extending-openapi.md b/docs/en/docs/advanced/extending-openapi.md index 9179126d..d8d280ba 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`: - - -
- -```console -$ pip install aiofiles - ----> 100% -``` - -
- ### Serve the static files * Import `StaticFiles`. diff --git a/docs/en/docs/advanced/templates.md b/docs/en/docs/advanced/templates.md index a8e2575c..45e6a20f 100644 --- a/docs/en/docs/advanced/templates.md +++ b/docs/en/docs/advanced/templates.md @@ -20,18 +20,6 @@ $ pip install jinja2 -If you need to also serve static files (as in this example), install `aiofiles`: - -
- -```console -$ pip install aiofiles - ----> 100% -``` - -
- ## Using `Jinja2Templates` * Import `Jinja2Templates`. diff --git a/docs/en/docs/index.md b/docs/en/docs/index.md index 998564bb..cc6982b7 100644 --- a/docs/en/docs/index.md +++ b/docs/en/docs/index.md @@ -443,7 +443,6 @@ Used by Pydantic: Used by 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. diff --git a/docs/en/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/en/docs/tutorial/dependencies/dependencies-with-yield.md index 3388a082..82553afa 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: diff --git a/docs/en/docs/tutorial/sql-databases.md b/docs/en/docs/tutorial/sql-databases.md index c623fad2..e8ebb29c 100644 --- a/docs/en/docs/tutorial/sql-databases.md +++ b/docs/en/docs/tutorial/sql-databases.md @@ -441,17 +441,6 @@ You can find an example of Alembic in a FastAPI project in the templates from [P ### Create a dependency -!!! info - For this to work, you need to use **Python 3.7** or above, or in **Python 3.6**, install the "backports": - - ```console - $ pip install async-exit-stack async-generator - ``` - - This installs async-exit-stack and async-generator. - - You can also use the alternative method with a "middleware" explained at the end. - Now use the `SessionLocal` class we created in the `sql_app/databases.py` file to create a dependency. We need to have an independent database session/connection (`SessionLocal`) per request, use the same session through all the request and then close it after the request is finished. diff --git a/docs/en/docs/tutorial/static-files.md b/docs/en/docs/tutorial/static-files.md index c103bd94..7a0c36af 100644 --- a/docs/en/docs/tutorial/static-files.md +++ b/docs/en/docs/tutorial/static-files.md @@ -2,20 +2,6 @@ You can serve static files automatically from a directory using `StaticFiles`. -## Install `aiofiles` - -First you need to install `aiofiles`: - -
- -```console -$ pip install aiofiles - ----> 100% -``` - -
- ## Use `StaticFiles` * Import `StaticFiles`. 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/fastapi/concurrency.py b/fastapi/concurrency.py index d1fdfe5f..04382c69 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,41 +7,21 @@ 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: diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index 95049d40..35ba44aa 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -1,4 +1,3 @@ -import asyncio import dataclasses import inspect from contextlib import contextmanager @@ -6,6 +5,7 @@ from copy import deepcopy from typing import ( Any, Callable, + Coroutine, Dict, List, Mapping, @@ -17,10 +17,10 @@ from typing import ( cast, ) +import anyio from fastapi import params from fastapi.concurrency import ( AsyncExitStack, - _fake_asynccontextmanager, asynccontextmanager, contextmanager_in_threadpool, ) @@ -266,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, @@ -289,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): @@ -452,14 +438,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) @@ -539,10 +517,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 ) @@ -697,9 +672,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) diff --git a/pyproject.toml b/pyproject.toml index 5b6b272a..ddce5a39 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,8 +33,10 @@ classifiers = [ "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.15.0", + "pydantic >=1.6.2,!=1.7,!=1.7.1,!=1.7.2,!=1.7.3,!=1.8,!=1.8.1,<2.0.0", + # TODO: remove contextlib2 as a direct dependency after upgrading Starlette + "contextlib2 >= 21.6.0; python_version < '3.7'", ] description-file = "README.md" requires-python = ">=3.6.1" @@ -46,7 +48,6 @@ Documentation = "https://fastapi.tiangolo.com/" test = [ "pytest >=6.2.4,<7.0.0", "pytest-cov >=2.12.0,<4.0.0", - "pytest-asyncio >=0.14.0,<0.16.0", "mypy ==0.910", "flake8 >=3.8.3,<4.0.0", "black ==21.9b0", @@ -60,11 +61,9 @@ test = [ "orjson >=3.2.1,<4.0.0", "ujson >=4.0.1,<5.0.0", "python-multipart >=0.0.5,<0.0.6", - "aiofiles >=0.5.0,<0.8.0", # TODO: try to upgrade after upgrading Starlette "flask >=1.1.2,<2.0.0", - "async_exit_stack >=1.0.1,<2.0.0; python_version < '3.7'", - "async_generator >=1.10,<2.0.0; python_version < '3.7'", + "anyio[trio] >=3.2.1,<4.0.0", # types "types-ujson ==0.1.1", @@ -90,7 +89,6 @@ dev = [ ] all = [ "requests >=2.24.0,<3.0.0", - "aiofiles >=0.5.0,<0.8.0", # TODO: try to upgrade after upgrading Starlette "jinja2 >=2.11.2,<3.0.0", "python-multipart >=0.0.5,<0.0.6", @@ -103,8 +101,6 @@ all = [ "orjson >=3.2.1,<4.0.0", "email_validator >=1.1.1,<2.0.0", "uvicorn[standard] >=0.12.0,<0.16.0", - "async_exit_stack >=1.0.1,<2.0.0; python_version < '3.7'", - "async_generator >=1.10,<2.0.0; python_version < '3.7'", ] [tool.isort] @@ -148,6 +144,8 @@ junit_family = "xunit2" filterwarnings = [ "error", 'ignore:"@coroutine" decorator is deprecated since Python 3\.8, use "async def" instead:DeprecationWarning', + # TODO: needed by AnyIO in Python 3.9, try to remove after an AnyIO upgrade + 'ignore:The loop argument is deprecated since Python 3\.8, and scheduled for removal in Python 3\.10:DeprecationWarning', # TODO: if these ignores are needed, enable them, otherwise remove them # 'ignore:The explicit passing of coroutine objects to asyncio\.wait\(\) is deprecated since Python 3\.8:DeprecationWarning', # 'ignore:Exception ignored in. Date: Wed, 6 Oct 2021 15:32:53 +0000 Subject: [PATCH 065/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 86805922..c6fc827a 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ✨ Add support for Trio via AnyIO. PR [#3372](https://github.com/tiangolo/fastapi/pull/3372) by [@graingert](https://github.com/graingert). * 🔧 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). * 🌐 Initialize Azerbaijani translations. PR [#3941](https://github.com/tiangolo/fastapi/pull/3941) by [@madatbay](https://github.com/madatbay). From a8bde38f7c4498edafce088bb9b525b07db889ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Wed, 6 Oct 2021 17:42:55 +0200 Subject: [PATCH 066/196] =?UTF-8?q?=E2=9E=96=20Remove=20`graphene`=20as=20?= =?UTF-8?q?an=20optional=20dependency=20(#4007)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ddce5a39..73a02010 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,8 +84,6 @@ dev = [ "autoflake >=1.4.0,<2.0.0", "flake8 >=3.8.3,<4.0.0", "uvicorn[standard] >=0.12.0,<0.16.0", - # TODO: remove in the next major version - "graphene >=2.1.8,<3.0.0" ] all = [ "requests >=2.24.0,<3.0.0", @@ -95,8 +93,6 @@ all = [ # TODO: try to upgrade after upgrading Starlette "itsdangerous >=1.1.0,<2.0.0", "pyyaml >=5.3.1,<6.0.0", - # TODO: remove in the next major version - "graphene >=2.1.8,<3.0.0", "ujson >=4.0.1,<5.0.0", "orjson >=3.2.1,<4.0.0", "email_validator >=1.1.1,<2.0.0", From c15f0423188c4768a60dc934f86e233732c000e4 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 6 Oct 2021 15:43:37 +0000 Subject: [PATCH 067/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index c6fc827a..0849a07d 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ➖ Remove `graphene` as an optional dependency. PR [#4007](https://github.com/tiangolo/fastapi/pull/4007) by [@tiangolo](https://github.com/tiangolo). * ✨ Add support for Trio via AnyIO. PR [#3372](https://github.com/tiangolo/fastapi/pull/3372) by [@graingert](https://github.com/graingert). * 🔧 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). From d9fa2311b358cdbc545f5c60f98d0119ec8f0c92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 7 Oct 2021 12:36:09 +0200 Subject: [PATCH 068/196] =?UTF-8?q?=F0=9F=93=9D=20Add=20docs=20for=20using?= =?UTF-8?q?=20Trio=20with=20Hypercorn=20(#4014)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/async.md | 10 +++++++- docs/en/docs/deployment/manually.md | 37 +++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/async.md b/docs/en/docs/async.md index 7fadee87..8194650f 100644 --- a/docs/en/docs/async.md +++ b/docs/en/docs/async.md @@ -324,7 +324,15 @@ So, about the egg and the chicken, how do you call the first `async` function? If you are working with **FastAPI** you don't have to worry about that, because that "first" function will be your *path operation function*, and FastAPI will know how to do the right thing. -But if you want to use `async` / `await` without FastAPI, check the official Python docs. +But if you want to use `async` / `await` without FastAPI, you can do it as well. + +### Write your own async code + +Starlette (and **FastAPI**) are based on AnyIO, which makes it compatible with both Python's standard library asyncio and Trio. + +In particular, you can directly use AnyIO for your advanced concurrency use cases that require more advanced patterns in your own code. + +And even if you were not using FastAPI, you could also write your own async applications with AnyIO to be highly compatible and get its benefits (e.g. *structured concurrency*). ### Other forms of asynchronous code diff --git a/docs/en/docs/deployment/manually.md b/docs/en/docs/deployment/manually.md index 80a7df7e..6a3619b6 100644 --- a/docs/en/docs/deployment/manually.md +++ b/docs/en/docs/deployment/manually.md @@ -92,6 +92,43 @@ You can then your application the same way you have done in the tutorials, but w It helps a lot during **development**, but you **shouldn't** use it in **production**. +## Hypercorn with Trio + +Starlette and **FastAPI** are based on AnyIO, which makes them compatible with both Python's standard library asyncio and Trio. + +Nevertheless, Uvicorn is currently only compatible with asyncio, and it normally uses `uvloop`, the high-performance drop-in replacement for `asyncio`. + +But if you want to directly use **Trio**, then you can use **Hypercorn** as it supports it. ✨ + +### Install Hypercorn with Trio + +First you need to install Hypercorn with Trio support: + +
+ +```console +$ pip install "hypercorn[trio]" +---> 100% +``` + +
+ +### Run with Trio + +Then you can pass the command line option `--worker-class` with the value `trio`: + +
+ +```console +$ hypercorn main:app --worker-class trio +``` + +
+ +And that will start Hypercorn with your app using Trio as the backend. + +Now you can use Trio internally in your app. Or even better, you can use AnyIO, to keep your code compatible with both Trio and asyncio. 🎉 + ## Deployment Concepts These examples run the server program (e.g Uvicorn), starting **a single process**, listening on all the IPs (`0.0.0.0`) on a predefined port (e.g. `80`). From a6ef62ce7b6d4ff4b988e3305df5fb83b5edb732 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 7 Oct 2021 10:36:53 +0000 Subject: [PATCH 069/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 0849a07d..7ce38adc 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 📝 Add docs for using Trio with Hypercorn. PR [#4014](https://github.com/tiangolo/fastapi/pull/4014) by [@tiangolo](https://github.com/tiangolo). * ➖ Remove `graphene` as an optional dependency. PR [#4007](https://github.com/tiangolo/fastapi/pull/4007) by [@tiangolo](https://github.com/tiangolo). * ✨ Add support for Trio via AnyIO. PR [#3372](https://github.com/tiangolo/fastapi/pull/3372) by [@graingert](https://github.com/graingert). * 🔧 Lint only in Python 3.7 and above. PR [#4006](https://github.com/tiangolo/fastapi/pull/4006) by [@tiangolo](https://github.com/tiangolo). From 17f0ec8927a7a464bd4a0165b2d9d0ad157be12e Mon Sep 17 00:00:00 2001 From: graue70 <23035329+graue70@users.noreply.github.com> Date: Thu, 7 Oct 2021 14:21:34 +0200 Subject: [PATCH 070/196] =?UTF-8?q?=E2=9C=8F=20Fix=20typo=20re-word=20in?= =?UTF-8?q?=20`docs/tutorial/handling-errors.md`=20(#2700)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- docs/en/docs/tutorial/handling-errors.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/en/docs/tutorial/handling-errors.md b/docs/en/docs/tutorial/handling-errors.md index 4fa82d5d..89f96176 100644 --- a/docs/en/docs/tutorial/handling-errors.md +++ b/docs/en/docs/tutorial/handling-errors.md @@ -260,6 +260,4 @@ You can import and re-use the default exception handlers from `fastapi.exception {!../../../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. From 3b686c37746d1134124751b7bb27e7e2368e5213 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 7 Oct 2021 12:22:16 +0000 Subject: [PATCH 071/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 7ce38adc..5b2c67b3 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ✏ 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). * 📝 Add docs for using Trio with Hypercorn. PR [#4014](https://github.com/tiangolo/fastapi/pull/4014) by [@tiangolo](https://github.com/tiangolo). * ➖ Remove `graphene` as an optional dependency. PR [#4007](https://github.com/tiangolo/fastapi/pull/4007) by [@tiangolo](https://github.com/tiangolo). * ✨ Add support for Trio via AnyIO. PR [#3372](https://github.com/tiangolo/fastapi/pull/3372) by [@graingert](https://github.com/graingert). From 8f4e3a4377293e73b93a1703e8a468d623028b97 Mon Sep 17 00:00:00 2001 From: Hasan Ramezani Date: Thu, 7 Oct 2021 15:56:57 +0330 Subject: [PATCH 072/196] =?UTF-8?q?=F0=9F=93=9D=20Add=20supported=20Python?= =?UTF-8?q?=20versions=20badge=20(#2794)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- README.md | 3 +++ docs/en/docs/index.md | 3 +++ 2 files changed, 6 insertions(+) diff --git a/README.md b/README.md index d376fb74..b1c815dc 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,9 @@ Package version + + Supported Python versions +

--- diff --git a/docs/en/docs/index.md b/docs/en/docs/index.md index cc6982b7..aa2c16a2 100644 --- a/docs/en/docs/index.md +++ b/docs/en/docs/index.md @@ -14,6 +14,9 @@ Package version + + Supported Python versions +

--- From fee3126d3a6f3045a94274caf33f8498f3dd414d Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 7 Oct 2021 12:27:41 +0000 Subject: [PATCH 073/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 5b2c67b3..bff7847a 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 📝 Add supported Python versions badge. PR [#2794](https://github.com/tiangolo/fastapi/pull/2794) by [@hramezani](https://github.com/hramezani). * ✏ 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). * 📝 Add docs for using Trio with Hypercorn. PR [#4014](https://github.com/tiangolo/fastapi/pull/4014) by [@tiangolo](https://github.com/tiangolo). * ➖ Remove `graphene` as an optional dependency. PR [#4007](https://github.com/tiangolo/fastapi/pull/4007) by [@tiangolo](https://github.com/tiangolo). From 0ceacef413f2c6378b7f00db87c9c0b59205afbf Mon Sep 17 00:00:00 2001 From: Vijay Yarramsetty <32131775+vijaykumar1356@users.noreply.github.com> Date: Thu, 7 Oct 2021 18:00:25 +0530 Subject: [PATCH 074/196] =?UTF-8?q?=E2=9C=8F=20Fix=20typo=20in=20`docs/en/?= =?UTF-8?q?docs/tutorial/body-nested-models.md`=20(#2808)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez From 426bd096ad5e4f3f7f3deeaa67106228a3831f00 Mon Sep 17 00:00:00 2001 From: raphaelauv Date: Thu, 7 Oct 2021 14:44:39 +0200 Subject: [PATCH 075/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20note=20about=20?= =?UTF-8?q?Falcon=20and=20ASGI,=20as=20now=20Falcon=20is=20also=20ASGI=20?= =?UTF-8?q?=F0=9F=8E=89=20(#3073)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- docs/en/docs/alternatives.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/en/docs/alternatives.md b/docs/en/docs/alternatives.md index d2792eb0..28d0be8c 100644 --- a/docs/en/docs/alternatives.md +++ b/docs/en/docs/alternatives.md @@ -242,8 +242,6 @@ It was one of the first extremely fast Python frameworks based on `asyncio`. It Falcon is another high performance Python framework, it is designed to be minimal, and work as the foundation of other frameworks like Hug. -It uses the previous standard for Python web frameworks (WSGI) which is synchronous, so it can't handle WebSockets and other use cases. Nevertheless, it also has a very good performance. - It is designed to have functions that receive two parameters, one "request" and one "response". Then you "read" parts from the request, and "write" parts to the response. Because of this design, it is not possible to declare request parameters and bodies with standard Python type hints as function parameters. So, data validation, serialization, and documentation, have to be done in code, not automatically. Or they have to be implemented as a framework on top of Falcon, like Hug. This same distinction happens in other frameworks that are inspired by Falcon's design, of having one request object and one response object as parameters. From afe1f69e792a537b689f7b4733e7149ca123da71 Mon Sep 17 00:00:00 2001 From: Greg Gandenberger Date: Thu, 7 Oct 2021 07:55:51 -0500 Subject: [PATCH 076/196] =?UTF-8?q?=F0=9F=93=9D=20Remove=20note=20about=20?= =?UTF-8?q?(now=20supported)=20feature=20from=20Swagger=20UI=20in=20`docs/?= =?UTF-8?q?en/docs/tutorial/request-files.md`=20(#2803)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- docs/en/docs/tutorial/request-files.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/docs/en/docs/tutorial/request-files.md b/docs/en/docs/tutorial/request-files.md index 1619d6d2..68ea654c 100644 --- a/docs/en/docs/tutorial/request-files.md +++ b/docs/en/docs/tutorial/request-files.md @@ -127,13 +127,6 @@ To use that, declare a `List` of `bytes` or `UploadFile`: 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`. From 12db3926b55ce5698b1b837161706ac86db47a03 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 7 Oct 2021 12:57:15 +0000 Subject: [PATCH 077/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index bff7847a..2baee028 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 📝 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). * 📝 Add supported Python versions badge. PR [#2794](https://github.com/tiangolo/fastapi/pull/2794) by [@hramezani](https://github.com/hramezani). * ✏ 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). * 📝 Add docs for using Trio with Hypercorn. PR [#4014](https://github.com/tiangolo/fastapi/pull/4014) by [@tiangolo](https://github.com/tiangolo). From b69781b161509d51095faa98ddd3bc6c78613eb7 Mon Sep 17 00:00:00 2001 From: Abraham Hinteregger Date: Thu, 7 Oct 2021 15:00:33 +0200 Subject: [PATCH 078/196] =?UTF-8?q?=E2=9C=8F=20Fix=20typo=20(mistranslatio?= =?UTF-8?q?n)=20in=20`docs/en/docs/advanced/templates.md`=20(#3211)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- docs/en/docs/advanced/templates.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/docs/advanced/templates.md b/docs/en/docs/advanced/templates.md index 45e6a20f..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). From 3581034eff447366bcffb896c0fc161e2bd23e75 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 7 Oct 2021 13:01:11 +0000 Subject: [PATCH 079/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 2baee028..8ca74f1c 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ✏ 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). * 📝 Add supported Python versions badge. PR [#2794](https://github.com/tiangolo/fastapi/pull/2794) by [@hramezani](https://github.com/hramezani). * ✏ 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). From e423c8241af70f6fedd5074c161c3befa5af12cf Mon Sep 17 00:00:00 2001 From: mori yuta <59682979+utamori@users.noreply.github.com> Date: Thu, 7 Oct 2021 22:06:17 +0900 Subject: [PATCH 080/196] =?UTF-8?q?=E2=9C=8F=20Fix=20link=20in=20Japanese?= =?UTF-8?q?=20docs=20for=20`docs/ja/docs/deployment/docker.md`=20(#3245)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- docs/ja/docs/deployment/docker.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ja/docs/deployment/docker.md b/docs/ja/docs/deployment/docker.md index 03a95ac6..f10312b5 100644 --- a/docs/ja/docs/deployment/docker.md +++ b/docs/ja/docs/deployment/docker.md @@ -170,7 +170,7 @@ TraefikおよびHTTPS処理を備えたDocker Swarm Modeクラスターをセッ ### FastAPIアプリケーションのデプロイ -すべてを設定するための最も簡単な方法は、[**FastAPI** Project Generators](project-generation.md){.internal-link target=_blank}を使用することでしょう。 +すべてを設定するための最も簡単な方法は、[**FastAPI** Project Generators](../project-generation.md){.internal-link target=_blank}を使用することでしょう。 上述したTraefikとHTTPSを備えたDocker Swarm クラスタが統合されるように設計されています。 From 46882b9ae15ba0a3c312e556d1e8c8982f00c780 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 7 Oct 2021 13:07:24 +0000 Subject: [PATCH 081/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 8ca74f1c..5fa77c93 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ✏ 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). * ✏ 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). * 📝 Add supported Python versions badge. PR [#2794](https://github.com/tiangolo/fastapi/pull/2794) by [@hramezani](https://github.com/hramezani). From 8e68ddbddfa2c1897e6a9a3f7eeab2ae141f6f4d Mon Sep 17 00:00:00 2001 From: Somraj Saha Date: Thu, 7 Oct 2021 18:43:10 +0530 Subject: [PATCH 082/196] =?UTF-8?q?=F0=9F=93=9D=20Add=20external=20link=20?= =?UTF-8?q?to=20article:=20How-to=20deploy=20FastAPI=20app=20to=20Heroku?= =?UTF-8?q?=20(#3241)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- docs/en/data/external_links.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/data/external_links.yml b/docs/en/data/external_links.yml index 40bf5ef0..bb3e0093 100644 --- a/docs/en/data/external_links.yml +++ b/docs/en/data/external_links.yml @@ -1,5 +1,9 @@ articles: english: + - 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 From 5551e7282ce2b16b88a724082763fa22aeb116de Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 7 Oct 2021 13:14:08 +0000 Subject: [PATCH 083/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 5fa77c93..7891c8a4 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 📝 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 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). * ✏ 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). From c29aa0bc8726e2c4e91ada08e4321062782a3455 Mon Sep 17 00:00:00 2001 From: Edd Salkield Date: Thu, 7 Oct 2021 13:26:36 +0000 Subject: [PATCH 084/196] =?UTF-8?q?=F0=9F=94=A7=20Swap=20light/dark=20them?= =?UTF-8?q?e=20button=20icon=20(#3246)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- docs/az/mkdocs.yml | 4 ++-- docs/de/mkdocs.yml | 4 ++-- docs/en/mkdocs.yml | 4 ++-- docs/es/mkdocs.yml | 4 ++-- docs/fr/mkdocs.yml | 4 ++-- docs/id/mkdocs.yml | 4 ++-- docs/it/mkdocs.yml | 4 ++-- docs/ja/mkdocs.yml | 4 ++-- docs/ko/mkdocs.yml | 4 ++-- docs/pl/mkdocs.yml | 4 ++-- docs/pt/mkdocs.yml | 4 ++-- docs/ru/mkdocs.yml | 4 ++-- docs/sq/mkdocs.yml | 4 ++-- docs/tr/mkdocs.yml | 4 ++-- docs/uk/mkdocs.yml | 4 ++-- docs/zh/mkdocs.yml | 4 ++-- 16 files changed, 32 insertions(+), 32 deletions(-) diff --git a/docs/az/mkdocs.yml b/docs/az/mkdocs.yml index edeb6c2e..72ca92a9 100644 --- a/docs/az/mkdocs.yml +++ b/docs/az/mkdocs.yml @@ -9,13 +9,13 @@ 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 diff --git a/docs/de/mkdocs.yml b/docs/de/mkdocs.yml index bcd521c8..621305d2 100644 --- a/docs/de/mkdocs.yml +++ b/docs/de/mkdocs.yml @@ -9,13 +9,13 @@ 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 diff --git a/docs/en/mkdocs.yml b/docs/en/mkdocs.yml index 28a688a9..aa2d0451 100644 --- a/docs/en/mkdocs.yml +++ b/docs/en/mkdocs.yml @@ -9,13 +9,13 @@ 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 diff --git a/docs/es/mkdocs.yml b/docs/es/mkdocs.yml index 35127ac2..ef722715 100644 --- a/docs/es/mkdocs.yml +++ b/docs/es/mkdocs.yml @@ -9,13 +9,13 @@ 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 diff --git a/docs/fr/mkdocs.yml b/docs/fr/mkdocs.yml index 4cd0750c..940e4ff6 100644 --- a/docs/fr/mkdocs.yml +++ b/docs/fr/mkdocs.yml @@ -9,13 +9,13 @@ 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 diff --git a/docs/id/mkdocs.yml b/docs/id/mkdocs.yml index 31488ee3..f9cae22c 100644 --- a/docs/id/mkdocs.yml +++ b/docs/id/mkdocs.yml @@ -9,13 +9,13 @@ 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 diff --git a/docs/it/mkdocs.yml b/docs/it/mkdocs.yml index 10e6198f..f1621060 100644 --- a/docs/it/mkdocs.yml +++ b/docs/it/mkdocs.yml @@ -9,13 +9,13 @@ 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 diff --git a/docs/ja/mkdocs.yml b/docs/ja/mkdocs.yml index 37f6548d..237bc965 100644 --- a/docs/ja/mkdocs.yml +++ b/docs/ja/mkdocs.yml @@ -9,13 +9,13 @@ 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 diff --git a/docs/ko/mkdocs.yml b/docs/ko/mkdocs.yml index bb66c898..0bf1a971 100644 --- a/docs/ko/mkdocs.yml +++ b/docs/ko/mkdocs.yml @@ -9,13 +9,13 @@ 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 diff --git a/docs/pl/mkdocs.yml b/docs/pl/mkdocs.yml index 0775048e..22000860 100644 --- a/docs/pl/mkdocs.yml +++ b/docs/pl/mkdocs.yml @@ -9,13 +9,13 @@ 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 diff --git a/docs/pt/mkdocs.yml b/docs/pt/mkdocs.yml index 3e5fece5..219f41b8 100644 --- a/docs/pt/mkdocs.yml +++ b/docs/pt/mkdocs.yml @@ -9,13 +9,13 @@ 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 diff --git a/docs/ru/mkdocs.yml b/docs/ru/mkdocs.yml index eee98a9b..e15a36bf 100644 --- a/docs/ru/mkdocs.yml +++ b/docs/ru/mkdocs.yml @@ -9,13 +9,13 @@ 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 diff --git a/docs/sq/mkdocs.yml b/docs/sq/mkdocs.yml index 1d2adbb7..a4879521 100644 --- a/docs/sq/mkdocs.yml +++ b/docs/sq/mkdocs.yml @@ -9,13 +9,13 @@ 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 diff --git a/docs/tr/mkdocs.yml b/docs/tr/mkdocs.yml index d78947fa..6fe2b0b6 100644 --- a/docs/tr/mkdocs.yml +++ b/docs/tr/mkdocs.yml @@ -9,13 +9,13 @@ 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 diff --git a/docs/uk/mkdocs.yml b/docs/uk/mkdocs.yml index 41aec880..7f5785e3 100644 --- a/docs/uk/mkdocs.yml +++ b/docs/uk/mkdocs.yml @@ -9,13 +9,13 @@ 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 diff --git a/docs/zh/mkdocs.yml b/docs/zh/mkdocs.yml index 3f27884e..07ffa367 100644 --- a/docs/zh/mkdocs.yml +++ b/docs/zh/mkdocs.yml @@ -9,13 +9,13 @@ 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 From b67532ee5ca6af6d1ae048b60e59d8cf36b5783d Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 7 Oct 2021 13:27:16 +0000 Subject: [PATCH 085/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 7891c8a4..0a54f535 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🔧 Swap light/dark theme button icon. PR [#3246](https://github.com/tiangolo/fastapi/pull/3246) by [@eddsalkield](https://github.com/eddsalkield). * 📝 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 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). * ✏ 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). From 4f168c5ada33c2b02344f0a4c7d8d9983fa981a1 Mon Sep 17 00:00:00 2001 From: Pax <13646646+paxcodes@users.noreply.github.com> Date: Thu, 7 Oct 2021 06:32:14 -0700 Subject: [PATCH 086/196] =?UTF-8?q?=F0=9F=93=9D=20=20Fix=20incorrect=20hig?= =?UTF-8?q?hlighted=20code=20(#3325)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- docs/en/docs/tutorial/body-multiple-params.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/docs/tutorial/body-multiple-params.md b/docs/en/docs/tutorial/body-multiple-params.md index 1bc8f143..a4484147 100644 --- a/docs/en/docs/tutorial/body-multiple-params.md +++ b/docs/en/docs/tutorial/body-multiple-params.md @@ -109,7 +109,7 @@ q: Optional[str] = None as in: -```Python hl_lines="27" +```Python hl_lines="28" {!../../../docs_src/body_multiple_params/tutorial004.py!} ``` From 1a8284725aa8c62d347aeb0c37c09aa889a7336b Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 7 Oct 2021 13:32:54 +0000 Subject: [PATCH 087/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 0a54f535..74fc3767 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 📝 Fix incorrect highlighted code. PR [#3325](https://github.com/tiangolo/fastapi/pull/3325) by [@paxcodes](https://github.com/paxcodes). * 🔧 Swap light/dark theme button icon. PR [#3246](https://github.com/tiangolo/fastapi/pull/3246) by [@eddsalkield](https://github.com/eddsalkield). * 📝 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 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). From d06b4d7bb88156bf101f4106b3ddecbd9867ef82 Mon Sep 17 00:00:00 2001 From: Bharat Raghunathan Date: Thu, 7 Oct 2021 19:27:51 +0530 Subject: [PATCH 088/196] =?UTF-8?q?=E2=9C=8F=20Re-word=20to=20clarify=20te?= =?UTF-8?q?st=20client=20in=20`docs/en/docs/tutorial/testing.md`=20(#3382)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- docs/en/docs/tutorial/testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/docs/tutorial/testing.md b/docs/en/docs/tutorial/testing.md index b225dcb6..9c9a8270 100644 --- a/docs/en/docs/tutorial/testing.md +++ b/docs/en/docs/tutorial/testing.md @@ -10,7 +10,7 @@ With it, you can use Date: Thu, 7 Oct 2021 13:59:14 +0000 Subject: [PATCH 089/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 74fc3767..e75d8dce 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ✏ 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). * 🔧 Swap light/dark theme button icon. PR [#3246](https://github.com/tiangolo/fastapi/pull/3246) by [@eddsalkield](https://github.com/eddsalkield). * 📝 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). From 7efdc110759a318775599d22c34c9a1de2e4e13e Mon Sep 17 00:00:00 2001 From: Denys Marichev Date: Thu, 7 Oct 2021 17:07:46 +0300 Subject: [PATCH 090/196] =?UTF-8?q?=E2=9C=8F=20Fix=20typo=20about=20file?= =?UTF-8?q?=20path=20in=20`docs/en/docs/tutorial/bigger-applications.md`?= =?UTF-8?q?=20(#3285)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- docs/en/docs/tutorial/bigger-applications.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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. 🚨 From 4093c350587c899760399f48434b313e4005f2b9 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 7 Oct 2021 14:09:05 +0000 Subject: [PATCH 091/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index e75d8dce..b38431ca 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ✏ 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). * 🔧 Swap light/dark theme button icon. PR [#3246](https://github.com/tiangolo/fastapi/pull/3246) by [@eddsalkield](https://github.com/eddsalkield). From 5a28f95a93802161967b7ad56fb557b434853134 Mon Sep 17 00:00:00 2001 From: jaystone776 Date: Thu, 7 Oct 2021 22:22:16 +0800 Subject: [PATCH 092/196] =?UTF-8?q?=E2=9C=8F=20Fix=20typo=20in=20`docs/en/?= =?UTF-8?q?docs/help-fastapi.md`=20(#3760)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- docs/en/docs/help-fastapi.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/docs/help-fastapi.md b/docs/en/docs/help-fastapi.md index 394bccab..8d8d708e 100644 --- a/docs/en/docs/help-fastapi.md +++ b/docs/en/docs/help-fastapi.md @@ -121,7 +121,7 @@ Have in mind that as chats allow more "free conversation", it's easy to ask ques In GitHub issues the template will guide you to write the right question so that you can more easily get a good answer, or even solve the problem yourself even before asking. And in GitHub I can make sure I always answer everything, even if it takes some time. I can't personally do that with the chat systems. 😅 -Conversations in the chat systems are also not as easily searchable as in GitHub, so questions and answers might get lost in the conversation. And only the ones in GitHub issues count to become a [FastAPI Expert](fastapi-people.md#experts){.internal-link target=_blank}, so you will most probably receive more attention in GitHub isssues. +Conversations in the chat systems are also not as easily searchable as in GitHub, so questions and answers might get lost in the conversation. And only the ones in GitHub issues count to become a [FastAPI Expert](fastapi-people.md#experts){.internal-link target=_blank}, so you will most probably receive more attention in GitHub issues. On the other side, there are thousands of users in the chat systems, so there's a high chance you'll find someone to talk to there, almost all the time. 😄 From eb453a50b81f4fe5132b737d5fe07ba78c995beb Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 7 Oct 2021 14:23:26 +0000 Subject: [PATCH 093/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index b38431ca..8a5cbcec 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ✏ 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). From e43f43444829ad8e469f2178e473f4b95085c891 Mon Sep 17 00:00:00 2001 From: Jorge Alvarado <65737950+jalvaradosegura@users.noreply.github.com> Date: Thu, 7 Oct 2021 11:36:43 -0300 Subject: [PATCH 094/196] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Add=20a=20missing?= =?UTF-8?q?=20comma=20in=20the=20security=20tutorial=20(#3564)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- docs/en/docs/tutorial/security/get-current-user.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/docs/tutorial/security/get-current-user.md b/docs/en/docs/tutorial/security/get-current-user.md index f50b69ac..a41db2b6 100644 --- a/docs/en/docs/tutorial/security/get-current-user.md +++ b/docs/en/docs/tutorial/security/get-current-user.md @@ -84,7 +84,7 @@ Just use any kind of model, any kind of class, any kind of database that you nee ## 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. From ffaecddbf6b88badce1f08066a1a25b0200e02eb Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 7 Oct 2021 14:37:59 +0000 Subject: [PATCH 095/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 8a5cbcec..ffaf4009 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ✏️ 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). From 7e4887ea55f932e7373a27354d5a6b7cc3fa9017 Mon Sep 17 00:00:00 2001 From: SaintMalik <37118134+saintmalik@users.noreply.github.com> Date: Thu, 7 Oct 2021 16:29:50 +0100 Subject: [PATCH 096/196] =?UTF-8?q?=E2=9C=8F=20Fix=20a=20typo=20in=20`docs?= =?UTF-8?q?/en/docs/advanced/path-operation-advanced-configuration.md`=20a?= =?UTF-8?q?nd=20`docs/en/docs/release-notes.md`=20(#3750)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- docs/en/docs/advanced/path-operation-advanced-configuration.md | 2 +- docs/en/docs/release-notes.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/docs/advanced/path-operation-advanced-configuration.md b/docs/en/docs/advanced/path-operation-advanced-configuration.md index 352fe076..a1c902ef 100644 --- a/docs/en/docs/advanced/path-operation-advanced-configuration.md +++ b/docs/en/docs/advanced/path-operation-advanced-configuration.md @@ -77,7 +77,7 @@ This *path operation*-specific OpenAPI schema is normally generated automaticall !!! tip This is a low level extension point. - If you only need to declare additonal responses, a more convenient way to do it is with [Additional Responses in OpenAPI](./additional-responses.md){.internal-link target=_blank}. + 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`. diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index ffaf4009..03234188 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -94,7 +94,7 @@ Soon there will be a new FastAPI release upgrading Starlette to take advantage o ### 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 additonal 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 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). From bb20f5e60df2384663e94d5abe4f8e67bb7a16a3 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 7 Oct 2021 15:30:34 +0000 Subject: [PATCH 097/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 03234188..6371d48a 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ✏ 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). From b1508dcacc3ebc0001949156b8460c5327bb2f53 Mon Sep 17 00:00:00 2001 From: Jorge Alvarado <65737950+jalvaradosegura@users.noreply.github.com> Date: Thu, 7 Oct 2021 12:39:19 -0300 Subject: [PATCH 098/196] =?UTF-8?q?=E2=9C=8F=20Re-word=20and=20combine=202?= =?UTF-8?q?=20paragraphs=20into=201=20for=20readability=20(#3346)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/es/docs/tutorial/index.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 From 4ba1e81f1671ae252b514fed31f76f2224144df4 Mon Sep 17 00:00:00 2001 From: Nina Hwang <79563565+NinaHwang@users.noreply.github.com> Date: Fri, 8 Oct 2021 00:44:46 +0900 Subject: [PATCH 099/196] =?UTF-8?q?=E2=9C=8F=20Fix=20typo=20in=20file=20pa?= =?UTF-8?q?ths=20in=20`docs/en/docs/contributing.md`=20(#3752)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- docs/en/docs/contributing.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/docs/contributing.md b/docs/en/docs/contributing.md index 9ef6e22c..648c472f 100644 --- a/docs/en/docs/contributing.md +++ b/docs/en/docs/contributing.md @@ -326,7 +326,7 @@ docs/es/docs/features.md * Now open the MkDocs config file for English at: ``` -docs/en/docs/mkdocs.yml +docs/en/mkdocs.yml ``` * Find the place where that `docs/features.md` is located in the config file. Somewhere like: @@ -345,7 +345,7 @@ nav: * Open the MkDocs config file for the language you are editing, e.g.: ``` -docs/es/docs/mkdocs.yml +docs/es/mkdocs.yml ``` * Add it there at the exact same location it was for English, e.g.: From d198f38b4546d62b7cf30c3eae0affb4a429167a Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 7 Oct 2021 15:45:28 +0000 Subject: [PATCH 100/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 6371d48a..f5729765 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ✏ 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). From 9c6e0d02bb8e3de31ca26ee4118119e928eb099f Mon Sep 17 00:00:00 2001 From: Kaustubh Gupta Date: Thu, 7 Oct 2021 21:25:40 +0530 Subject: [PATCH 101/196] =?UTF-8?q?=F0=9F=93=9D=20Add=20external=20link=20?= =?UTF-8?q?to=20article:=20Deploying=20ML=20Models=20as=20API=20Using=20Fa?= =?UTF-8?q?stAPI=20and=20Heroku=20(#3904)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- docs/en/data/external_links.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/data/external_links.yml b/docs/en/data/external_links.yml index bb3e0093..0850d978 100644 --- a/docs/en/data/external_links.yml +++ b/docs/en/data/external_links.yml @@ -1,5 +1,9 @@ articles: english: + - 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/ From 077e5baf2c277ae49b56a5d4e913fbbc29c3cd03 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 7 Oct 2021 15:56:18 +0000 Subject: [PATCH 102/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index f5729765..0654d807 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 📝 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). From c54fb7e20804698bf9ba9422fd92e53fa85114ad Mon Sep 17 00:00:00 2001 From: tomwei7 Date: Thu, 7 Oct 2021 23:57:38 +0800 Subject: [PATCH 103/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20docs=20with=20p?= =?UTF-8?q?ip=20install=20calls=20when=20using=20extras=20with=20brackets,?= =?UTF-8?q?=20use=20quotes=20for=20compatibility=20with=20Zsh=20(#3131)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- README.md | 4 ++-- docs/en/docs/deployment/manually.md | 2 +- docs/en/docs/deployment/server-workers.md | 2 +- docs/en/docs/index.md | 4 ++-- docs/en/docs/tutorial/index.md | 4 ++-- docs/en/docs/tutorial/security/oauth2-jwt.md | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index b1c815dc..83003158 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,7 @@ You will also need an ASGI server, for production such as ```console -$ pip install uvicorn[standard] +$ pip install "uvicorn[standard]" ---> 100% ``` @@ -456,7 +456,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/deployment/manually.md b/docs/en/docs/deployment/manually.md index 6a3619b6..7fd1f4d4 100644 --- a/docs/en/docs/deployment/manually.md +++ b/docs/en/docs/deployment/manually.md @@ -29,7 +29,7 @@ You can install an ASGI compatible server with:
```console - $ pip install uvicorn[standard] + $ pip install "uvicorn[standard]" ---> 100% ``` diff --git a/docs/en/docs/deployment/server-workers.md b/docs/en/docs/deployment/server-workers.md index 2892d579..84a2b0f3 100644 --- a/docs/en/docs/deployment/server-workers.md +++ b/docs/en/docs/deployment/server-workers.md @@ -39,7 +39,7 @@ And then the Gunicorn-compatible **Uvicorn worker** class would be in charge of
```console -$ pip install uvicorn[standard] gunicorn +$ pip install "uvicorn[standard]" gunicorn ---> 100% ``` diff --git a/docs/en/docs/index.md b/docs/en/docs/index.md index aa2c16a2..7de1e50d 100644 --- a/docs/en/docs/index.md +++ b/docs/en/docs/index.md @@ -134,7 +134,7 @@ You will also need an ASGI server, for production such as ```console -$ pip install uvicorn[standard] +$ pip install "uvicorn[standard]" ---> 100% ``` @@ -457,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/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
```console -$ pip install fastapi[all] +$ pip install "fastapi[all]" ---> 100% ``` @@ -64,7 +64,7 @@ $ pip install fastapi[all] Also install `uvicorn` to work as the server: ``` - pip install uvicorn[standard] + pip install "uvicorn[standard]" ``` And the same for each of the optional dependencies that you want to use. diff --git a/docs/en/docs/tutorial/security/oauth2-jwt.md b/docs/en/docs/tutorial/security/oauth2-jwt.md index fda722cd..f88cf23c 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
```console -$ pip install python-jose[cryptography] +$ pip install "python-jose[cryptography]" ---> 100% ``` @@ -76,7 +76,7 @@ So, install PassLib with Bcrypt:
```console -$ pip install passlib[bcrypt] +$ pip install "passlib[bcrypt]" ---> 100% ``` From eb89968b360cebcd0a9add92c5fc336aa7c44935 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 7 Oct 2021 15:58:17 +0000 Subject: [PATCH 104/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 0654d807..6efceb39 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 📝 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). From 53076170c01ad9daff3060ae6c8fdf62772c1b82 Mon Sep 17 00:00:00 2001 From: Andy Challis Date: Fri, 8 Oct 2021 03:14:39 +1100 Subject: [PATCH 105/196] =?UTF-8?q?=E2=9C=8F=20Fix=20typos=20in=20Deployme?= =?UTF-8?q?nt=20Guide=20(#3975)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Anthony Lukach Co-authored-by: Sebastián Ramírez --- docs/en/docs/deployment/concepts.md | 34 +++++++++++------------ docs/en/docs/deployment/deta.md | 8 +++--- docs/en/docs/deployment/docker.md | 18 ++++++------ docs/en/docs/deployment/https.md | 2 +- docs/en/docs/deployment/index.md | 2 +- docs/en/docs/deployment/server-workers.md | 6 ++-- 6 files changed, 35 insertions(+), 35 deletions(-) diff --git a/docs/en/docs/deployment/concepts.md b/docs/en/docs/deployment/concepts.md index d22b53fe..22604cee 100644 --- a/docs/en/docs/deployment/concepts.md +++ b/docs/en/docs/deployment/concepts.md @@ -21,7 +21,7 @@ By considering these concepts, you will be able to **evaluate and design** the b In the next chapters, I'll give you more **concrete recipes** to deploy FastAPI applications. -But for now, let's check these important **conceptual ideas**. These concepts also apply for any other type of web API. 💡 +But for now, let's check these important **conceptual ideas**. These concepts also apply to any other type of web API. 💡 ## Security - HTTPS @@ -47,7 +47,7 @@ Some of the tools you could use as a TLS Termination Proxy are: * With an external component like cert-manager for certificate renewals * Handled internally by a cloud provider as part of their services (read below 👇) -Another option is that you could use a **cloud service** that does more of the work including setting up HTTPS. It could have some restrictions or charge you more, etc. But in that case you wouldn't have to set up a TLS Termination Proxy yourself. +Another option is that you could use a **cloud service** that does more of the work including setting up HTTPS. It could have some restrictions or charge you more, etc. But in that case, you wouldn't have to set up a TLS Termination Proxy yourself. I'll show you some concrete examples in the next chapters. @@ -64,7 +64,7 @@ We will talk a lot about the running "**process**", so it's useful to have clari The word **program** is commonly used to describe many things: * The **code** that you write, the **Python files**. -* The **file** that can be **executed** by the operating system, for example `python`, `python.exe` or `uvicorn`. +* The **file** that can be **executed** by the operating system, for example: `python`, `python.exe` or `uvicorn`. * A particular program while it is **running** on the operating system, using the CPU, and storing things on memory. This is also called a **process**. ### What is a Process @@ -75,7 +75,7 @@ The word **process** is normally used in a more specific way, only referring to * This doesn't refer to the file, nor to the code, it refers **specifically** to the thing that is being **executed** and managed by the operating system. * Any program, any code, **can only do things** when it is being **executed**. So, when there's a **process running**. * The process can be **terminated** (or "killed") by you, or by the operating system. At that point, it stops running/being executed, and it can **no longer do things**. -* Each application that you have running in your computer has some process behind it, each running program, each window, etc. And there are normally many processes running **at the same time** while a computer is on. +* Each application that you have running on your computer has some process behind it, each running program, each window, etc. And there are normally many processes running **at the same time** while a computer is on. * There can be **multiple processes** of the **same program** running at the same time. If you check out the "task manager" or "system monitor" (or similar tools) in your operating system, you will be able to see many of those processes running. @@ -90,13 +90,13 @@ Now that we know the difference between the terms **process** and **program**, l ## 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 on certain situations, but most of the time you want it constantly running and **available**. +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**. +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. @@ -108,7 +108,7 @@ In general, you will probably want the server program (e.g. Uvicorn) to be start ### 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. +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 @@ -177,7 +177,7 @@ For example, this could be handled by: 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. +But in many cases, you will want to run several worker processes at the same time. ### Multiple Processes - Workers @@ -197,11 +197,11 @@ So, to be able to have **multiple processes** at the same time, there has to be 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, its own variables, its own memory. And if you are consuming a large amount of memory in your code, **each process** will consume an equivalent amount of memory. +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 or 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**. +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. 🚨 @@ -253,12 +253,12 @@ But in most cases, you will want to perform these steps only **once**. So, you will want to have a **single process** to perform those **previous steps**, before starting the application. -And you will have to make sure that it's a single process running those previous steps *even* if afterwards you start **multiple processes** (multiple workers) for the application itself. If those steps were run by **multiple processes**, they would **duplicate** the work by running it on **parallel**, and if the steps were something delicate like a database migration, they could cause conflicts with each other. +And you will have to make sure that it's a single process running those previous steps *even* if afterwards, you start **multiple processes** (multiple workers) for the application itself. If those steps were run by **multiple processes**, they would **duplicate** the work by running it on **parallel**, and if the steps were something delicate like a database migration, they could cause conflicts with each other. -Of course, there are some cases where there's no problem in running the previous steps multiple times, in that case it's a lot easier to handle. +Of course, there are some cases where there's no problem in running the previous steps multiple times, in that case, it's a lot easier to handle. !!! tip - Also have in mind that depending on your setup, in some cases you **might not even need any previous steps** before starting your application. + Also, have in mind that depending on your setup, in some cases you **might not even need any previous steps** before starting your application. In that case, you wouldn't have to worry about any of this. 🤷 @@ -279,7 +279,7 @@ Here are some possible ideas: Your server(s) is (are) a **resource**, you can consume or **utilize**, with your programs, the computation time on the CPUs, and the RAM memory available. -How much resources do you want to be consuming/utilizing? It might be easy to think "not much", but in reality, you will probably want to consume **as much as possible without crashing**. +How much of the system resources do you want to be consuming/utilizing? It might be easy to think "not much", but in reality, you will probably want to consume **as much as possible without crashing**. If you are paying for 3 servers but you are using only a little bit of their RAM and CPU, you are probably **wasting money** 💸, and probably **wasting server electric power** 🌎, etc. @@ -291,9 +291,9 @@ In this case, it would be better to get **one extra server** and run some proces There's also the chance that for some reason you have a **spike** of usage of your API. Maybe it went viral, or maybe some other services or bots start using it. And you might want to have extra resources to be safe in those cases. -You could put an **arbitrary number** to target, for example something **between 50% to 90%** of resource utilization. The point is that those are probably the main things you will want to measure and use to tweak your deployments. +You could put an **arbitrary number** to target, for example, something **between 50% to 90%** of resource utilization. The point is that those are probably the main things you will want to measure and use to tweak your deployments. -You can use simple tools like `htop` to see the CPU and RAM used in your server, or the amount used by each process. Or you can use more complex monitoring tools, maybe distributed across servers, etc. +You can use simple tools like `htop` to see the CPU and RAM used in your server or the amount used by each process. Or you can use more complex monitoring tools, which may be distributed across servers, etc. ## Recap @@ -308,4 +308,4 @@ You have been reading here some of the main concepts that you would probably nee Understanding these ideas and how to apply them should give you the intuition necessary to take any decisions when configuring and tweaking your deployments. 🤓 -In the next sections I'll give you more concrete examples of possible strategies you can follow. 🚀 +In the next sections, I'll give you more concrete examples of possible strategies you can follow. 🚀 diff --git a/docs/en/docs/deployment/deta.md b/docs/en/docs/deployment/deta.md index b0d1cb92..c0dc3336 100644 --- a/docs/en/docs/deployment/deta.md +++ b/docs/en/docs/deployment/deta.md @@ -9,7 +9,7 @@ It will take you about **10 minutes**. ## A basic **FastAPI** app -* Create a directory for your app, for example `./fastapideta/` and enter in it. +* Create a directory for your app, for example, `./fastapideta/` and enter into it. ### FastAPI code @@ -213,7 +213,7 @@ Now you can share that URL with anyone and they will be able to access your API. Congrats! You deployed your FastAPI app to Deta! 🎉 🍰 -Also notice that Deta correctly handles HTTPS for you, so you don't have to take care of that and can be sure that your clients will have a secure encrypted connection. ✅ 🔒 +Also, notice that Deta correctly handles HTTPS for you, so you don't have to take care of that and can be sure that your clients will have a secure encrypted connection. ✅ 🔒 ## Check the Visor @@ -235,7 +235,7 @@ You can also edit them and re-play them. ## Learn more -At some point you will probably want to store some data for your app in a way that persists through time. For that you can use Deta Base, it also has a generous **free tier**. +At some point, you will probably want to store some data for your app in a way that persists through time. For that you can use Deta Base, it also has a generous **free tier**. You can also read more in the Deta Docs. @@ -253,6 +253,6 @@ Coming back to the concepts we discussed in [Deployments Concepts](./concepts.md !!! note Deta is designed to make it easy (and free) to deploy simple applications quickly. - It can simplify a lot several use cases, but at the same time it doesn't support others, like using external databases (apart from Deta's own NoSQL database system), custom virtual machines, etc. + It can simplify several use cases, but at the same time, it doesn't support others, like using external databases (apart from Deta's own NoSQL database system), custom virtual machines, etc. You can read more details in the Deta docs to see if it's the right choice for you. diff --git a/docs/en/docs/deployment/docker.md b/docs/en/docs/deployment/docker.md index e25401f2..3f86efcc 100644 --- a/docs/en/docs/deployment/docker.md +++ b/docs/en/docs/deployment/docker.md @@ -1,6 +1,6 @@ # FastAPI in Containers - Docker -When deploying FastAPI applications a common approach is to build a **Linux container image**. It's normally done using **Docker**. And then you can deploy that container image in one of different possible ways. +When deploying FastAPI applications a common approach is to build a **Linux container image**. It's normally done using **Docker**. You can then deploy that container image in one of a few possible ways. Using Linux containers has several advantages including **security**, **replicability**, **simplicity**, and others. @@ -68,13 +68,13 @@ And there are many other images for different things like databases, for example * MongoDB * Redis, etc. -By using a pre-made container image it's very easy to **combine** and use different tools. For example, to try out a new database. In most cases you can use the **official images**, and just configure them with environment variables. +By using a pre-made container image it's very easy to **combine** and use different tools. For example, to try out a new database. In most cases, you can use the **official images**, and just configure them with environment variables. That way, in many cases you can learn about containers and Docker and re-use that knowledge with many different tools and components. So, you would run **multiple containers** with different things, like a database, a Python application, a web server with a React frontend application, and connect them together via their internal network. -All the container management systems (like Docker or Kubernetes) have these networking features integrated in them. +All the container management systems (like Docker or Kubernetes) have these networking features integrated into them. ## Containers and Processes @@ -84,7 +84,7 @@ When a **container** is started, it will run that command/program (although you A container is running as long as the **main process** (command or program) is running. -A container normally has a **single process**, but it's also possible to start subprocesses from the main process, and that way have **multiple processes** in the same container. +A container normally has a **single process**, but it's also possible to start subprocesses from the main process, and that way you will have **multiple processes** in the same container. But it's not possible to have a running container without **at least one running process**. If the main process stops, the container stops. @@ -137,7 +137,7 @@ Successfully installed fastapi pydantic uvicorn ### Create the **FastAPI** Code -* Create an `app` directory and enter in it. +* Create an `app` directory and enter it. * Create an empty file `__init__.py`. * Create a `main.py` file with: @@ -216,7 +216,7 @@ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] 6. Set the **command** to run the `uvicorn` server. - `CMD` takes a list of strings, each of this strings is what you would type in the command line separated by spaces. + `CMD` takes a list of strings, each of these strings is what you would type in the command line separated by spaces. This command will be run from the **current working directory**, the same `/code` directory you set above with `WORKDIR /code`. @@ -338,7 +338,7 @@ You will see the alternative automatic documentation (provided by cluster of machines with **Kubernetes**, Docker Swarm Mode, Nomad, or other similar complex system to manage distributed containers on multiple machines, then you will probably want to **handle replication** at the **cluster level** instead of using a **process manager** (like Gunicorn with workers) in each container. +If you have a cluster of machines with **Kubernetes**, Docker Swarm Mode, Nomad, or another similar complex system to manage distributed containers on multiple machines, then you will probably want to **handle replication** at the **cluster level** instead of using a **process manager** (like Gunicorn with workers) in each container. One of those distributed container management systems like Kubernetes normally has some integrated way of handling **replication of containers** while still supporting **load balancing** for the incoming requests. All at the **cluster level**. @@ -487,7 +487,7 @@ The main point is, **none** of these are **rules written in stone** that you hav ## Memory -If you run **a single process per container** you will have a more or less well defined, stable, and limited amount of memory consumed by each of of those containers (more than one if they are replicated). +If you run **a single process per container** you will have a more or less well-defined, stable, and limited amount of memory consumed by each of those containers (more than one if they are replicated). And then you can set those same memory limits and requirements in your configurations for your container management system (for example in **Kubernetes**). That way it will be able to **replicate the containers** in the **available machines** taking into account the amount of memory needed by them, and the amount available in the machines in the cluster. diff --git a/docs/en/docs/deployment/https.md b/docs/en/docs/deployment/https.md index 1a3b1a0a..790976a7 100644 --- a/docs/en/docs/deployment/https.md +++ b/docs/en/docs/deployment/https.md @@ -187,4 +187,4 @@ Having **HTTPS** is very important, and quite **critical** in most cases. Most o But once you know the basic information of **HTTPS for developers** you can easily combine and configure different tools to help you manage everything in a simple way. -In some of the next chapters I'll show you several concrete examples of how to set up **HTTPS** for **FastAPI** applications. 🔒 +In some of the next chapters, I'll show you several concrete examples of how to set up **HTTPS** for **FastAPI** applications. 🔒 diff --git a/docs/en/docs/deployment/index.md b/docs/en/docs/deployment/index.md index d1941ad9..f0fd001c 100644 --- a/docs/en/docs/deployment/index.md +++ b/docs/en/docs/deployment/index.md @@ -8,7 +8,7 @@ To **deploy** an application means to perform the necessary steps to make it **a For a **web API**, it normally involves putting it in a **remote machine**, with a **server program** that provides good performance, stability, etc, so that your **users** can **access** the application efficiently and without interruptions or problems. -This is in contrast to the the **development** stages, where you are constantly changing the code, breaking it and fixing it, stopping and restarting the development server, etc. +This is in contrast to the **development** stages, where you are constantly changing the code, breaking it and fixing it, stopping and restarting the development server, etc. ## Deployment Strategies diff --git a/docs/en/docs/deployment/server-workers.md b/docs/en/docs/deployment/server-workers.md index 84a2b0f3..4ccd9d9f 100644 --- a/docs/en/docs/deployment/server-workers.md +++ b/docs/en/docs/deployment/server-workers.md @@ -20,7 +20,7 @@ Here I'll show you how to use 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 6fe2b0b6..5ef8fd6a 100644 --- a/docs/tr/mkdocs.yml +++ b/docs/tr/mkdocs.yml @@ -57,6 +57,7 @@ nav: - zh: /zh/ - features.md - fastapi-people.md +- python-types.md markdown_extensions: - toc: permalink: true From 3c93e803d55b67701f14c25a6da8d9b1f5291696 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 26 Oct 2021 18:14:17 +0000 Subject: [PATCH 117/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 31953a78..e2e00f91 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🌐 Add Turkish translation for `docs/python-types.md`. PR [#3926](https://github.com/tiangolo/fastapi/pull/3926) by [@BilalAlpaslan](https://github.com/BilalAlpaslan). ## 0.70.0 From a859497a72a11e9545ca84a1f87b423a0b9f1cca Mon Sep 17 00:00:00 2001 From: Sam Courtemanche Date: Tue, 26 Oct 2021 20:15:30 +0200 Subject: [PATCH 118/196] =?UTF-8?q?=F0=9F=8C=90=20Add=20French=20translati?= =?UTF-8?q?on=20for=20`docs/tutorial/query-params.md`=20(#3556)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ruidy --- docs/fr/docs/tutorial/query-params.md | 198 ++++++++++++++++++++++++++ docs/fr/mkdocs.yml | 1 + 2 files changed, 199 insertions(+) create mode 100644 docs/fr/docs/tutorial/query-params.md 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 940e4ff6..4973d170 100644 --- a/docs/fr/mkdocs.yml +++ b/docs/fr/mkdocs.yml @@ -59,6 +59,7 @@ nav: - fastapi-people.md - python-types.md - Tutoriel - Guide utilisateur: + - tutorial/query-params.md - tutorial/body.md - tutorial/background-tasks.md - async.md From b2c9574fc60bd5fa6606851f29d03f6b32facbe8 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 26 Oct 2021 18:16:12 +0000 Subject: [PATCH 119/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index e2e00f91..38791f2a 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🌐 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). ## 0.70.0 From 0d5e0ba5d590ca8de7814ae4ad926c891033530f Mon Sep 17 00:00:00 2001 From: Sam Courtemanche Date: Tue, 26 Oct 2021 20:33:34 +0200 Subject: [PATCH 120/196] =?UTF-8?q?=F0=9F=8C=90=20Add=20French=20translati?= =?UTF-8?q?on=20for=20`docs/tutorial/path-params.md`=20(#3548)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- docs/fr/docs/tutorial/path-params.md | 254 +++++++++++++++++++++++++++ docs/fr/mkdocs.yml | 1 + 2 files changed, 255 insertions(+) create mode 100644 docs/fr/docs/tutorial/path-params.md 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/mkdocs.yml b/docs/fr/mkdocs.yml index 4973d170..01cf8d5e 100644 --- a/docs/fr/mkdocs.yml +++ b/docs/fr/mkdocs.yml @@ -59,6 +59,7 @@ nav: - fastapi-people.md - python-types.md - Tutoriel - Guide utilisateur: + - tutorial/path-params.md - tutorial/query-params.md - tutorial/body.md - tutorial/background-tasks.md From 08410ca5688e81963d681cbeb8aeef69486942eb Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 26 Oct 2021 18:34:11 +0000 Subject: [PATCH 121/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 38791f2a..d15ca610 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🌐 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). From 652cf4bb6bf82a03361838f098ccde358ca71c56 Mon Sep 17 00:00:00 2001 From: Sam Courtemanche Date: Tue, 26 Oct 2021 20:47:01 +0200 Subject: [PATCH 122/196] =?UTF-8?q?=F0=9F=8C=90=20Add=20French=20translati?= =?UTF-8?q?on=20for=20Tutorial=20-=20First=20steps=20(#3455)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- docs/fr/docs/tutorial/first-steps.md | 334 +++++++++++++++++++++++++++ docs/fr/mkdocs.yml | 1 + 2 files changed, 335 insertions(+) create mode 100644 docs/fr/docs/tutorial/first-steps.md 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 : + +
+ +```console +$ uvicorn main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +INFO: Started reloader process [28720] +INFO: Started server process [28722] +INFO: Waiting for application startup. +INFO: Application startup complete. +``` + +
+ +!!! note + La commande `uvicorn main:app` fait référence à : + + * `main` : le fichier `main.py` (le module Python). + * `app` : l'objet créé dans `main.py` via la ligne `app = FastAPI()`. + * `--reload` : l'option disant à uvicorn de redémarrer le serveur à chaque changement du code. À ne pas utiliser en production ! + +Vous devriez voir dans la console, une ligne semblable à la suivante : + +```hl_lines="4" +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +Cette ligne montre l'URL par laquelle l'app est actuellement accessible, sur votre machine locale. + +### Allez voir le résultat + +Ouvrez votre navigateur à l'adresse http://127.0.0.1:8000. + +Vous obtiendrez cette réponse JSON : + +```JSON +{"message": "Hello World"} +``` + +### Documentation interactive de l'API + +Rendez-vous sur http://127.0.0.1:8000/docs. + +Vous verrez la documentation interactive de l'API générée automatiquement (via Swagger UI) : + +![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) + +### Documentation alternative + +Ensuite, rendez-vous sur http://127.0.0.1:8000/redoc. + +Vous y verrez la documentation alternative (via ReDoc) : + +![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) + +### OpenAPI + +**FastAPI** génère un "schéma" contenant toute votre API dans le standard de définition d'API **OpenAPI**. + +#### "Schéma" + +Un "schéma" est une définition ou une description de quelque chose. Pas le code qui l'implémente, uniquement une description abstraite. + +#### "Schéma" d'API + +Ici, OpenAPI est une spécification qui dicte comment définir le schéma de votre API. + +Le schéma inclut les chemins de votre API, les paramètres potentiels de chaque chemin, etc. + +#### "Schéma" de données + +Le terme "schéma" peut aussi faire référence à la forme de la donnée, comme un contenu JSON. + +Dans ce cas, cela signifierait les attributs JSON, ainsi que les types de ces attributs, etc. + +#### OpenAPI et JSON Schema + +**OpenAPI** définit un schéma d'API pour votre API. Il inclut des définitions (ou "schémas") de la donnée envoyée et reçue par votre API en utilisant **JSON Schema**, le standard des schémas de données JSON. + +#### Allez voir `openapi.json` + +Si vous êtes curieux d'à quoi ressemble le schéma brut **OpenAPI**, **FastAPI** génère automatiquement un (schéma) JSON avec les descriptions de toute votre API. + +Vous pouvez le voir directement à cette adresse : http://127.0.0.1:8000/openapi.json. + +Le schéma devrait ressembler à ceci : + + +```JSON +{ + "openapi": "3.0.2", + "info": { + "title": "FastAPI", + "version": "0.1.0" + }, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + + + +... +``` + +#### À quoi sert OpenAPI + +Le schéma **OpenAPI** est ce qui alimente les deux systèmes de documentation interactive. + +Et il existe des dizaines d'alternatives, toutes basées sur **OpenAPI**. Vous pourriez facilement ajouter n'importe laquelle de ces alternatives à votre application **FastAPI**. + +Vous pourriez aussi l'utiliser pour générer du code automatiquement, pour les clients qui communiquent avec votre API. Comme par exemple, des applications frontend, mobiles ou IOT. + +## Récapitulatif, étape par étape + +### Étape 1 : import `FastAPI` + +```Python hl_lines="1" +{!../../../docs_src/first_steps/tutorial001.py!} +``` + +`FastAPI` est une classe Python qui fournit toutes les fonctionnalités nécessaires au lancement de votre API. + +!!! note "Détails techniques" + `FastAPI` est une classe héritant directement de `Starlette`. + + Vous pouvez donc aussi utiliser toutes les fonctionnalités de Starlette depuis `FastAPI`. + +### Étape 2 : créer une "instance" `FastAPI` + +```Python hl_lines="3" +{!../../../docs_src/first_steps/tutorial001.py!} +``` + +Ici la variable `app` sera une "instance" de la classe `FastAPI`. + +Ce sera le point principal d'interaction pour créer toute votre API. + +Cette `app` est la même que celle à laquelle fait référence `uvicorn` dans la commande : + +
+ +```console +$ uvicorn main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Si vous créez votre app avec : + +```Python hl_lines="3" +{!../../../docs_src/first_steps/tutorial002.py!} +``` + +Et la mettez dans un fichier `main.py`, alors vous appeleriez `uvicorn` avec : + +
+ +```console +$ uvicorn main:my_awesome_api --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +### Étape 3: créer une *opération de chemin* + +#### Chemin + +Chemin, ou "path" fait référence ici à la dernière partie de l'URL démarrant au premier `/`. + +Donc, dans un URL tel que : + +``` +https://example.com/items/foo +``` + +...le "path" serait : + +``` +/items/foo +``` + +!!! info + Un chemin, ou "path" est aussi souvent appelé route ou "endpoint". + + +#### Opération + +"Opération" fait référence à une des "méthodes" HTTP. + +Une de : + +* `POST` +* `GET` +* `PUT` +* `DELETE` + +...ou une des plus exotiques : + +* `OPTIONS` +* `HEAD` +* `PATCH` +* `TRACE` + +Dans le protocol HTTP, vous pouvez communiquer avec chaque chemin en utilisant une (ou plus) de ces "méthodes". + +--- + +En construisant des APIs, vous utilisez généralement ces méthodes HTTP spécifiques pour effectuer une action précise. + +Généralement vous utilisez : + +* `POST` : pour créer de la donnée. +* `GET` : pour lire de la donnée. +* `PUT` : pour mettre à jour de la donnée. +* `DELETE` : pour supprimer de la donnée. + +Donc, dans **OpenAPI**, chaque méthode HTTP est appelée une "opération". + +Nous allons donc aussi appeler ces dernières des "**opérations**". + + +#### Définir un *décorateur d'opération de chemin* + +```Python hl_lines="6" +{!../../../docs_src/first_steps/tutorial001.py!} +``` + +Le `@app.get("/")` dit à **FastAPI** que la fonction en dessous est chargée de gérer les requêtes qui vont sur : + +* le chemin `/` +* en utilisant une opération 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/mkdocs.yml b/docs/fr/mkdocs.yml index 01cf8d5e..6242a88f 100644 --- a/docs/fr/mkdocs.yml +++ b/docs/fr/mkdocs.yml @@ -59,6 +59,7 @@ 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 From fb1f34123124641850d5cfbe1d247a654ab51586 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 26 Oct 2021 18:47:42 +0000 Subject: [PATCH 123/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index d15ca610..cec6c274 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🌐 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). From 0a61a6c8656da738ee6438c1fa927e3cbad64bdd Mon Sep 17 00:00:00 2001 From: "weekwith.me" <63915557+0417taehyun@users.noreply.github.com> Date: Wed, 27 Oct 2021 03:53:40 +0900 Subject: [PATCH 124/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20`docs/tutorial/?= =?UTF-8?q?dependencies/classes-as-dependencies`:=20Add=20type=20of=20quer?= =?UTF-8?q?y=20parameters=20in=20a=20description=20of=20`Classes=20as=20de?= =?UTF-8?q?pendencies`=20(#4015)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../docs/tutorial/dependencies/classes-as-dependencies.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/en/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/en/docs/tutorial/dependencies/classes-as-dependencies.md index 8c00374b..7747e3e1 100644 --- a/docs/en/docs/tutorial/dependencies/classes-as-dependencies.md +++ b/docs/en/docs/tutorial/dependencies/classes-as-dependencies.md @@ -91,9 +91,9 @@ 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. From 58ab733f19846b4875c5b79bfb1f4d1cb7f4823f Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 26 Oct 2021 18:54:22 +0000 Subject: [PATCH 125/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index cec6c274..72f2e9b5 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 📝 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). From 146ff28d9cd226783aac1d65336ee694471455f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Wed, 8 Dec 2021 16:04:04 +0100 Subject: [PATCH 126/196] =?UTF-8?q?=F0=9F=94=A7=20Add=20CryptAPI=20sponsor?= =?UTF-8?q?=20(#4264)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + docs/en/data/sponsors.yml | 3 + docs/en/data/sponsors_badge.yml | 1 + docs/en/docs/img/sponsors/cryptapi-banner.svg | 1375 +++++++++++++++++ docs/en/docs/img/sponsors/cryptapi.svg | 1216 +++++++++++++++ docs/en/overrides/main.html | 6 + 6 files changed, 2602 insertions(+) create mode 100644 docs/en/docs/img/sponsors/cryptapi-banner.svg create mode 100644 docs/en/docs/img/sponsors/cryptapi.svg diff --git a/README.md b/README.md index 83003158..53de38bd 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ The key features are: + diff --git a/docs/en/data/sponsors.yml b/docs/en/data/sponsors.yml index 4949e6c5..baa2e440 100644 --- a/docs/en/data/sponsors.yml +++ b/docs/en/data/sponsors.yml @@ -2,6 +2,9 @@ gold: - 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 silver: - url: https://www.deta.sh/?ref=fastapi title: The launchpad for all your (team's) ideas diff --git a/docs/en/data/sponsors_badge.yml b/docs/en/data/sponsors_badge.yml index 0496c627..75974872 100644 --- a/docs/en/data/sponsors_badge.yml +++ b/docs/en/data/sponsors_badge.yml @@ -6,3 +6,4 @@ logins: - mikeckennedy - koaning - deepset-ai + - cryptapi diff --git a/docs/en/docs/img/sponsors/cryptapi-banner.svg b/docs/en/docs/img/sponsors/cryptapi-banner.svg new file mode 100644 index 00000000..29cd772d --- /dev/null +++ b/docs/en/docs/img/sponsors/cryptapi-banner.svg @@ -0,0 +1,1375 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/en/docs/img/sponsors/cryptapi.svg b/docs/en/docs/img/sponsors/cryptapi.svg new file mode 100644 index 00000000..db4e0934 --- /dev/null +++ b/docs/en/docs/img/sponsors/cryptapi.svg @@ -0,0 +1,1216 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/en/overrides/main.html b/docs/en/overrides/main.html index aa381faa..70b0253b 100644 --- a/docs/en/overrides/main.html +++ b/docs/en/overrides/main.html @@ -40,6 +40,12 @@
+
{% endblock %} From f282c0e20718e50063a4c4ea164e83f13d020872 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 8 Dec 2021 15:04:50 +0000 Subject: [PATCH 127/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 72f2e9b5..d37959dc 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🔧 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). From dabb4898f140d98f5e5a4668fdbcd0361459fa8d Mon Sep 17 00:00:00 2001 From: kimjaeyoonn <87240205+kimjaeyoonn@users.noreply.github.com> Date: Thu, 9 Dec 2021 00:32:24 +0900 Subject: [PATCH 128/196] =?UTF-8?q?=F0=9F=8C=90=20Fix=20Korean=20translati?= =?UTF-8?q?on=20for=20`docs/ko/docs/tutorial/index.md`=20(#4193)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/ko/docs/tutorial/index.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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] 하지만 (지금 읽고 있는) **자습서 - 사용자 안내서**를 먼저 읽는게 좋습니다. -**자습서 - 사용자 안내서**만으로 완전한 애플리케이션을 구축한 다음, **고급 사용자 안내서**의 몇 가지 추가 아이디어를 사용하여 필요에 따라 다양한 방식으로 확장할 수 있도록 설계되었습니다. +**자습서 - 사용자 안내서**만으로도 완전한 애플리케이션을 구축할 수 있으며, 필요에 따라 **고급 사용자 안내서**에서 제공하는 몇 가지 추가적인 기능을 사용하여 다양한 방식으로 확장할 수 있도록 설계되었습니다. From cace5a79f77c7518eecb92ab1909a7d14cf466ff Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 8 Dec 2021 15:33:09 +0000 Subject: [PATCH 129/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index d37959dc..efb84de4 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🌐 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). From 446d194c4675e8b3e0f6315dce31af22a229aeca Mon Sep 17 00:00:00 2001 From: daehyeon kim <87962045+DevDae@users.noreply.github.com> Date: Thu, 9 Dec 2021 00:37:30 +0900 Subject: [PATCH 130/196] =?UTF-8?q?=F0=9F=8C=90=20Add=20Korean=20translati?= =?UTF-8?q?on=20for=20`docs/ko/docs/deployment/versions.md`=20(#4121)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: weekwith.me <63915557+0417taehyun@users.noreply.github.com> --- docs/ko/docs/deployment/versions.md | 88 +++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 docs/ko/docs/deployment/versions.md diff --git a/docs/ko/docs/deployment/versions.md b/docs/ko/docs/deployment/versions.md new file mode 100644 index 00000000..4c1bcdc2 --- /dev/null +++ b/docs/ko/docs/deployment/versions.md @@ -0,0 +1,88 @@ +# FastAPI 버전들에 대하여 + +**FastAPI** 는 이미 많은 응용 프로그램과 시스템들을 만드는데 사용되고 있습니다. 그리고 100%의 테스트 정확성을 가지고 있습니다. 하지만 이것은 아직까지도 빠르게 발전하고 있습니다. + +새로운 특징들이 빈번하게 추가되고, 오류들이 지속적으로 수정되고 있습니다. 그리고 코드가 계속적으로 향상되고 있습니다. + +이것이 아직도 최신 버전이 `0.x.x`인 이유입니다. 이것은 각각의 버전들이 잠재적으로 변할 수 있다는 것을 보여줍니다. 이는 유의적 버전 관습을 따릅니다. + +지금 바로 **FastAPI**로 응용 프로그램을 만들 수 있습니다. 이때 (아마 지금까지 그래 왔던 것처럼), 사용하는 버전이 코드와 잘 맞는지 확인해야합니다. + +## `fastapi` 버전을 표시 + +가장 먼저 해야할 것은 응용 프로그램이 잘 작동하는 가장 최신의 구체적인 **FastAPI** 버전을 표시하는 것입니다. + +예를 들어, 응용 프로그램에 `0.45.0` 버전을 사용했다고 가정합니다. + +만약에 `requirements.txt` 파일을 사용했다면, 다음과 같이 버전을 명세할 수 있습니다: + +```txt +fastapi==0.45.0 +``` + +이것은 `0.45.0` 버전을 사용했다는 것을 의미합니다. + +또는 다음과 같이 표시할 수 있습니다: + +```txt +fastapi>=0.45.0,<0.46.0 +``` + +이것은 `0.45.0` 버전과 같거나 높으면서 `0.46.0` 버전 보다는 낮은 버전을 사용했다는 것을 의미합니다. 예를 들어, `0.45.2` 버전과 같은 경우는 해당 조건을 만족합니다. + +만약에 Poetry, Pipenv, 또는 그밖의 다양한 설치 도구를 사용한다면, 패키지에 구체적인 버전을 정의할 수 있는 방법을 가지고 있을 것입니다. + +## 이용가능한 버전들 + +[Release Notes](../release-notes.md){.internal-link target=_blank}를 통해 사용할 수 있는 버전들을 확인할 수 있습니다.(예를 들어, 가장 최신의 버전을 확인할 수 있습니다.) + + +## 버전들에 대해 + +유의적 버전 관습을 따라서, `1.0.0` 이하의 모든 버전들은 잠재적으로 급변할 수 있습니다. + +FastAPI는 오류를 수정하고, 일반적인 변경사항을 위해 "패치"버전의 관습을 따릅니다. + +!!! tip "팁" + 여기서 말하는 "패치"란 버전의 마지막 숫자로, 예를 들어 `0.2.3` 버전에서 "패치"는 `3`을 의미합니다. + +따라서 다음과 같이 버전을 표시할 수 있습니다: + +```txt +fastapi>=0.45.0,<0.46.0 +``` + +수정된 사항과 새로운 요소들이 "마이너" 버전에 추가되었습니다. + +!!! tip "팁" + "마이너"란 버전 넘버의 가운데 숫자로, 예를 들어서 `0.2.3`의 "마이너" 버전은 `2`입니다. + +## FastAPI 버전의 업그레이드 + +응용 프로그램을 검사해야합니다. + +(Starlette 덕분에), **FastAPI** 를 이용하여 굉장히 쉽게 할 수 있습니다. [Testing](../tutorial/testing.md){.internal-link target=_blank}문서를 확인해 보십시오: + +검사를 해보고 난 후에, **FastAPI** 버전을 더 최신으로 업그레이드 할 수 있습니다. 그리고 코드들이 테스트에 정상적으로 작동하는지 확인을 해야합니다. + +만약에 모든 것이 정상 작동하거나 필요한 부분을 변경하고, 모든 검사를 통과한다면, 새로운 버전의 `fastapi`를 표시할 수 있습니다. + +## Starlette에 대해 + +`starlette`의 버전은 표시할 수 없습니다. + +서로다른 버전의 **FastAPI**가 구체적이고 새로운 버전의 Starlette을 사용할 것입니다. + +그러므로 **FastAPI**가 알맞은 Starlette 버전을 사용하도록 하십시오. + +## Pydantic에 대해 + +Pydantic은 **FastAPI** 를 위한 검사를 포함하고 있습니다. 따라서, 새로운 버전의 Pydantic(`1.0.0`이상)은 항상 FastAPI와 호환됩니다. + +작업을 하고 있는 `1.0.0` 이상의 모든 버전과 `2.0.0` 이하의 Pydantic 버전을 표시할 수 있습니다. + +예를 들어 다음과 같습니다: + +```txt +pydantic>=1.2.0,<2.0.0 +``` From 8e416875ce98e89b05da53cf95fccd40818c903f Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 8 Dec 2021 15:38:10 +0000 Subject: [PATCH 131/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index efb84de4..0f0edaa7 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🌐 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). From a5d697b9b56443ac1abfc09f358e9eb56031422e Mon Sep 17 00:00:00 2001 From: Spike Date: Thu, 9 Dec 2021 00:41:26 +0900 Subject: [PATCH 132/196] =?UTF-8?q?=F0=9F=8C=90=20Add=20Korean=20translati?= =?UTF-8?q?on=20for=20Tutorial=20-=20Path=20Parameters=20and=20Numeric=20V?= =?UTF-8?q?alidations=20(#2432)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../path-params-numeric-validations.md | 122 ++++++++++++++++++ docs/ko/mkdocs.yml | 1 + 2 files changed, 123 insertions(+) create mode 100644 docs/ko/docs/tutorial/path-params-numeric-validations.md 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/mkdocs.yml b/docs/ko/mkdocs.yml index 0bf1a971..0cd0e2f9 100644 --- a/docs/ko/mkdocs.yml +++ b/docs/ko/mkdocs.yml @@ -61,6 +61,7 @@ nav: - tutorial/path-params.md - tutorial/query-params.md - tutorial/header-params.md + - tutorial/path-params-numeric-validations.md markdown_extensions: - toc: permalink: true From da69a1c4c0460ab475e3d2ad351bac3a12bf6690 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 8 Dec 2021 15:42:12 +0000 Subject: [PATCH 133/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 0f0edaa7..907df643 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🌐 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). From 461e0d4cce8542cbed53791fbc80f5731883c061 Mon Sep 17 00:00:00 2001 From: Kwang Soo Jeong Date: Thu, 9 Dec 2021 00:43:31 +0900 Subject: [PATCH 134/196] =?UTF-8?q?=F0=9F=8C=90=20Add=20Korean=20translati?= =?UTF-8?q?on=20for=20Tutorial=20-=20JSON=20Compatible=20Encoder=20(#3152)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/ko/docs/tutorial/encoder.md | 34 ++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 docs/ko/docs/tutorial/encoder.md 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** 에서 내부적으로 데이터를 변환하는 데 사용하지만, 다른 많은 곳에서도 이는 유용합니다. From dd463d0dc2d48777bd9149d6938a672e8f81b203 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 8 Dec 2021 15:44:10 +0000 Subject: [PATCH 135/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 907df643..31034c84 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🌐 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). From 39dbee9d563967aeab15637974d40a374e6df85c Mon Sep 17 00:00:00 2001 From: Nina Hwang <79563565+NinaHwang@users.noreply.github.com> Date: Thu, 9 Dec 2021 00:45:37 +0900 Subject: [PATCH 136/196] =?UTF-8?q?=F0=9F=8C=90=20Add=20Korean=20translati?= =?UTF-8?q?on=20for=20`docs/tutorial/response-status-code.md`=20(#3742)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: weekwith.me <63915557+0417taehyun@users.noreply.github.com> Co-authored-by: Sebastián Ramírez --- docs/ko/docs/tutorial/response-status-code.md | 89 +++++++++++++++++++ docs/ko/mkdocs.yml | 1 + 2 files changed, 90 insertions(+) create mode 100644 docs/ko/docs/tutorial/response-status-code.md 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 0cd0e2f9..dd2d6a7a 100644 --- a/docs/ko/mkdocs.yml +++ b/docs/ko/mkdocs.yml @@ -62,6 +62,7 @@ nav: - tutorial/query-params.md - tutorial/header-params.md - tutorial/path-params-numeric-validations.md + - tutorial/response-status-code.md markdown_extensions: - toc: permalink: true From fb9c4b31b91fe9f9601302046997e007fe35ef95 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 8 Dec 2021 15:46:19 +0000 Subject: [PATCH 137/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 31034c84..ca755385 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🌐 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). From 10e3a02582cfbf58b9c93dd782b4c2cb7a615da5 Mon Sep 17 00:00:00 2001 From: Leandro de Souza <85115541+leandrodesouzadev@users.noreply.github.com> Date: Wed, 8 Dec 2021 12:50:35 -0300 Subject: [PATCH 138/196] =?UTF-8?q?=F0=9F=8C=90=20Add=20portuguese=20trans?= =?UTF-8?q?lation=20for=20`docs/tutorial/query-params-str-validations.md`?= =?UTF-8?q?=20(#3965)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mário Victor Ribeiro Silva Co-authored-by: Leandro de Souza Co-authored-by: Sebastián Ramírez --- .../tutorial/query-params-str-validations.md | 303 ++++++++++++++++++ docs/pt/mkdocs.yml | 1 + 2 files changed, 304 insertions(+) create mode 100644 docs/pt/docs/tutorial/query-params-str-validations.md 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/mkdocs.yml b/docs/pt/mkdocs.yml index 219f41b8..ea4af852 100644 --- a/docs/pt/mkdocs.yml +++ b/docs/pt/mkdocs.yml @@ -62,6 +62,7 @@ nav: - tutorial/first-steps.md - tutorial/path-params.md - tutorial/body-fields.md + - tutorial/query-params-str-validations.md - Segurança: - tutorial/security/index.md - Guia de Usuário Avançado: From fa5639cb3564a475335f468b1d2d80ae5d8845d0 Mon Sep 17 00:00:00 2001 From: Nina Hwang <79563565+NinaHwang@users.noreply.github.com> Date: Thu, 9 Dec 2021 00:52:01 +0900 Subject: [PATCH 139/196] =?UTF-8?q?=F0=9F=8C=90=20Add=20Korean=20translati?= =?UTF-8?q?on=20for=20`docs/tutorial/request-files.md`=20(#3743)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Spike Co-authored-by: weekwith.me <63915557+0417taehyun@users.noreply.github.com> Co-authored-by: Sebastián Ramírez --- docs/ko/docs/tutorial/request-files.md | 144 +++++++++++++++++++++++++ docs/ko/mkdocs.yml | 1 + 2 files changed, 145 insertions(+) create mode 100644 docs/ko/docs/tutorial/request-files.md 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/mkdocs.yml b/docs/ko/mkdocs.yml index dd2d6a7a..124e7b1d 100644 --- a/docs/ko/mkdocs.yml +++ b/docs/ko/mkdocs.yml @@ -63,6 +63,7 @@ nav: - tutorial/header-params.md - tutorial/path-params-numeric-validations.md - tutorial/response-status-code.md + - tutorial/request-files.md markdown_extensions: - toc: permalink: true From eb79441a7f23c0df42f13547af67159d037f43c5 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 9 Dec 2021 10:40:14 +0000 Subject: [PATCH 140/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index ca755385..05324443 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🌐 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). From aa4a69f790fb9a78ba7ed7767273f8734a5d028d Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 9 Dec 2021 10:45:00 +0000 Subject: [PATCH 141/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 05324443..b96e81c7 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🌐 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). From 062efcbb5d19efb075ed1b50cb3f01d813c04460 Mon Sep 17 00:00:00 2001 From: Nina Hwang <79563565+NinaHwang@users.noreply.github.com> Date: Thu, 9 Dec 2021 19:47:26 +0900 Subject: [PATCH 142/196] =?UTF-8?q?=F0=9F=8C=90=20Add=20Korean=20translati?= =?UTF-8?q?on=20for=20`docs/tutorial/request-forms-and-files.md`=20(#3744)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: weekwith.me <63915557+0417taehyun@users.noreply.github.com> --- .../docs/tutorial/request-forms-and-files.md | 35 +++++++++++++++++++ docs/ko/mkdocs.yml | 1 + 2 files changed, 36 insertions(+) create mode 100644 docs/ko/docs/tutorial/request-forms-and-files.md 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/mkdocs.yml b/docs/ko/mkdocs.yml index 124e7b1d..7a75940a 100644 --- a/docs/ko/mkdocs.yml +++ b/docs/ko/mkdocs.yml @@ -64,6 +64,7 @@ nav: - 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 From 0a87bc88b85445018d3ced9d07ac6a37f9e6d8f1 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 9 Dec 2021 10:48:10 +0000 Subject: [PATCH 143/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index b96e81c7..271aab5d 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🌐 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). From b0cd4d7e7ebdbab130d663d73d5474361116ce63 Mon Sep 17 00:00:00 2001 From: Eric Jolibois Date: Sun, 12 Dec 2021 12:28:35 +0100 Subject: [PATCH 144/196] =?UTF-8?q?=F0=9F=90=9B=20Fix=20JSON=20Schema=20fo?= =?UTF-8?q?r=20dataclasses,=20supporting=20the=20fixes=20in=20Pydantic=201?= =?UTF-8?q?.9=20(#4272)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- .../test_dataclasses/test_tutorial002.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial002.py b/tests/test_tutorial/test_dataclasses/test_tutorial002.py index 10d8d227..34aeb0be 100644 --- a/tests/test_tutorial/test_dataclasses/test_tutorial002.py +++ b/tests/test_tutorial/test_dataclasses/test_tutorial002.py @@ -1,3 +1,5 @@ +from copy import deepcopy + from fastapi.testclient import TestClient from docs_src.dataclasses.tutorial002 import app @@ -29,7 +31,7 @@ openapi_schema = { "schemas": { "Item": { "title": "Item", - "required": ["name", "price", "tags"], + "required": ["name", "price"], "type": "object", "properties": { "name": {"title": "Name", "type": "string"}, @@ -51,7 +53,18 @@ openapi_schema = { def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200 - assert response.json() == openapi_schema + # 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(): From 6d642ef5fb60eb48d178de2846657cf4d90e882a Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 12 Dec 2021 11:29:36 +0000 Subject: [PATCH 145/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 271aab5d..2a0d2736 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🐛 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). * 🌐 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). From e7158bc59212ddc5ecd6a45a83e6d99f8c6bb3d4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 12 Dec 2021 12:34:18 +0100 Subject: [PATCH 146/196] =?UTF-8?q?=F0=9F=91=A5=20Update=20FastAPI=20Peopl?= =?UTF-8?q?e=20(#4274)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions --- docs/en/data/people.yml | 258 ++++++++++++++++++++-------------------- 1 file changed, 127 insertions(+), 131 deletions(-) diff --git a/docs/en/data/people.yml b/docs/en/data/people.yml index 8f1710d3..89c6f8dc 100644 --- a/docs/en/data/people.yml +++ b/docs/en/data/people.yml @@ -1,12 +1,12 @@ maintainers: - login: tiangolo answers: 1230 - prs: 260 + prs: 269 avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=5cad72c846b7aba2e960546af490edc7375dafc4&v=4 url: https://github.com/tiangolo experts: - login: Kludex - count: 299 + count: 315 avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=cf8455cb899806b774a3a71073f88583adec99f6&v=4 url: https://github.com/Kludex - login: dmontagu @@ -30,41 +30,45 @@ experts: avatarUrl: https://avatars.githubusercontent.com/u/331403?v=4 url: https://github.com/phy25 - login: ArcLightSlavik - count: 68 + count: 71 avatarUrl: https://avatars.githubusercontent.com/u/31127044?u=81a84af39c89b898b0fbc5a04e8834f60f23e55a&v=4 url: https://github.com/ArcLightSlavik +- login: raphaelauv + count: 63 + avatarUrl: https://avatars.githubusercontent.com/u/10202690?u=e6f86f5c0c3026a15d6b51792fa3e532b12f1371&v=4 + url: https://github.com/raphaelauv - login: falkben - count: 57 + count: 58 avatarUrl: https://avatars.githubusercontent.com/u/653031?u=0c8d8f33d87f1aa1a6488d3f02105e9abc838105&v=4 url: https://github.com/falkben - login: sm-Fifteen count: 48 avatarUrl: https://avatars.githubusercontent.com/u/516999?u=437c0c5038558c67e887ccd863c1ba0f846c03da&v=4 url: https://github.com/sm-Fifteen -- login: raphaelauv - count: 48 - avatarUrl: https://avatars.githubusercontent.com/u/10202690?u=e6f86f5c0c3026a15d6b51792fa3e532b12f1371&v=4 - url: https://github.com/raphaelauv +- login: Dustyposa + count: 42 + avatarUrl: https://avatars.githubusercontent.com/u/27180793?u=5cf2877f50b3eb2bc55086089a78a36f07042889&v=4 + url: https://github.com/Dustyposa - login: includeamin count: 38 avatarUrl: https://avatars.githubusercontent.com/u/11836741?u=8bd5ef7e62fe6a82055e33c4c0e0a7879ff8cfb6&v=4 url: https://github.com/includeamin -- login: Dustyposa - count: 38 - avatarUrl: https://avatars.githubusercontent.com/u/27180793?u=5cf2877f50b3eb2bc55086089a78a36f07042889&v=4 - url: https://github.com/Dustyposa +- login: insomnes + count: 37 + avatarUrl: https://avatars.githubusercontent.com/u/16958893?u=f8be7088d5076d963984a21f95f44e559192d912&v=4 + url: https://github.com/insomnes - login: prostomarkeloff count: 33 avatarUrl: https://avatars.githubusercontent.com/u/28061158?u=72309cc1f2e04e40fa38b29969cb4e9d3f722e7b&v=4 url: https://github.com/prostomarkeloff +- login: STeveShary + count: 32 + avatarUrl: https://avatars.githubusercontent.com/u/5167622?u=de8f597c81d6336fcebc37b32dfd61a3f877160c&v=4 + url: https://github.com/STeveShary - login: krishnardt 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 @@ -73,6 +77,14 @@ experts: count: 29 avatarUrl: https://avatars.githubusercontent.com/u/1144727?u=85c025e3fcc7bd79a5665c63ee87cdf8aae13374&v=4 url: https://github.com/frankie567 +- login: adriangb + count: 26 + avatarUrl: https://avatars.githubusercontent.com/u/1755071?u=81f0262df34e1460ca546fbd0c211169c2478532&v=4 + url: https://github.com/adriangb +- 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=9bcce836bbce55835291c5b2ac93a4e311f4b3c3&v=4 @@ -85,13 +97,9 @@ experts: count: 24 avatarUrl: https://avatars.githubusercontent.com/u/9435877?u=719327b7d2c4c62212456d771bfa7c6b8dbb9eac&v=4 url: https://github.com/SirTelemak -- login: STeveShary - count: 24 - avatarUrl: https://avatars.githubusercontent.com/u/5167622?u=de8f597c81d6336fcebc37b32dfd61a3f877160c&v=4 - url: https://github.com/STeveShary - 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 @@ -101,22 +109,18 @@ experts: count: 21 avatarUrl: https://avatars.githubusercontent.com/u/565544?v=4 url: https://github.com/chris-allnutt +- login: panla + count: 21 + avatarUrl: https://avatars.githubusercontent.com/u/41326348?u=ba2fda6b30110411ecbf406d187907e2b420ac19&v=4 + url: https://github.com/panla - login: retnikt count: 19 avatarUrl: https://avatars.githubusercontent.com/u/24581770?v=4 url: https://github.com/retnikt -- login: ghandic - count: 18 - avatarUrl: https://avatars.githubusercontent.com/u/23500353?u=e2e1d736f924d9be81e8bfc565b6d8836ba99773&v=4 - url: https://github.com/ghandic - login: Hultner count: 18 avatarUrl: https://avatars.githubusercontent.com/u/2669034?u=115e53df959309898ad8dc9443fbb35fee71df07&v=4 url: https://github.com/Hultner -- login: panla - count: 18 - avatarUrl: https://avatars.githubusercontent.com/u/41326348?u=ba2fda6b30110411ecbf406d187907e2b420ac19&v=4 - url: https://github.com/panla - login: jorgerpo count: 17 avatarUrl: https://avatars.githubusercontent.com/u/12537771?u=7444d20019198e34911082780cc7ad73f2b97cb3&v=4 @@ -125,26 +129,30 @@ experts: count: 17 avatarUrl: https://avatars.githubusercontent.com/u/28262306?u=66ee21316275ef356081c2efc4ed7a4572e690dc&v=4 url: https://github.com/nkhitrov -- login: adriangb - count: 17 - avatarUrl: https://avatars.githubusercontent.com/u/1755071?u=81f0262df34e1460ca546fbd0c211169c2478532&v=4 - url: https://github.com/adriangb - login: waynerv count: 16 avatarUrl: https://avatars.githubusercontent.com/u/39515546?u=ec35139777597cdbbbddda29bf8b9d4396b429a9&v=4 url: https://github.com/waynerv +- login: acidjunk + count: 15 + avatarUrl: https://avatars.githubusercontent.com/u/685002?u=b5094ab4527fc84b006c0ac9ff54367bdebb2267&v=4 + url: https://github.com/acidjunk - login: haizaar count: 13 avatarUrl: https://avatars.githubusercontent.com/u/58201?u=4f1f9843d69433ca0d380d95146cfe119e5fdac4&v=4 url: https://github.com/haizaar -- login: acidjunk +- login: dstlny count: 13 - avatarUrl: https://avatars.githubusercontent.com/u/685002?u=b5094ab4527fc84b006c0ac9ff54367bdebb2267&v=4 - url: https://github.com/acidjunk + avatarUrl: https://avatars.githubusercontent.com/u/41964673?u=9f2174f9d61c15c6e3a4c9e3aeee66f711ce311f&v=4 + url: https://github.com/dstlny - login: David-Lor count: 12 avatarUrl: https://avatars.githubusercontent.com/u/17401854?u=474680c02b94cba810cb9032fb7eb787d9cc9d22&v=4 url: https://github.com/David-Lor +- login: lowercase00 + count: 11 + 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 @@ -153,10 +161,6 @@ experts: count: 11 avatarUrl: https://avatars.githubusercontent.com/u/8134632?v=4 url: https://github.com/juntatalor -- login: dstlny - count: 11 - avatarUrl: https://avatars.githubusercontent.com/u/41964673?u=9f2174f9d61c15c6e3a4c9e3aeee66f711ce311f&v=4 - url: https://github.com/dstlny - login: valentin994 count: 11 avatarUrl: https://avatars.githubusercontent.com/u/42819267?u=fdeeaa9242a59b243f8603496b00994f6951d5a2&v=4 @@ -173,43 +177,27 @@ experts: count: 10 avatarUrl: https://avatars.githubusercontent.com/u/47581948?v=4 url: https://github.com/hellocoldworld +- login: oligond + count: 10 + avatarUrl: https://avatars.githubusercontent.com/u/2858306?u=1bb1182a5944e93624b7fb26585f22c8f7a9d76e&v=4 + url: https://github.com/oligond last_month_active: +- login: raphaelauv + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/10202690?u=e6f86f5c0c3026a15d6b51792fa3e532b12f1371&v=4 + url: https://github.com/raphaelauv +- login: oligond + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/2858306?u=1bb1182a5944e93624b7fb26585f22c8f7a9d76e&v=4 + url: https://github.com/oligond - login: Kludex - count: 13 + count: 5 avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=cf8455cb899806b774a3a71073f88583adec99f6&v=4 url: https://github.com/Kludex -- login: ghandic - count: 12 - avatarUrl: https://avatars.githubusercontent.com/u/23500353?u=e2e1d736f924d9be81e8bfc565b6d8836ba99773&v=4 - url: https://github.com/ghandic -- login: STeveShary - count: 8 - avatarUrl: https://avatars.githubusercontent.com/u/5167622?u=de8f597c81d6336fcebc37b32dfd61a3f877160c&v=4 - url: https://github.com/STeveShary -- login: Honda-a +- login: Dustyposa count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/22759187?u=f45bd5fb17b4dca331529b8e9e5eab6122b84b8b&v=4 - url: https://github.com/Honda-a -- login: frankie567 - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/1144727?u=85c025e3fcc7bd79a5665c63ee87cdf8aae13374&v=4 - url: https://github.com/frankie567 -- login: panla - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/41326348?u=ba2fda6b30110411ecbf406d187907e2b420ac19&v=4 - url: https://github.com/panla -- login: dstlny - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/41964673?u=9f2174f9d61c15c6e3a4c9e3aeee66f711ce311f&v=4 - url: https://github.com/dstlny -- login: trevorwang - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/121966?v=4 - url: https://github.com/trevorwang -- login: klaa97 - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/39653693?v=4 - url: https://github.com/klaa97 + avatarUrl: https://avatars.githubusercontent.com/u/27180793?u=5cf2877f50b3eb2bc55086089a78a36f07042889&v=4 + url: https://github.com/Dustyposa top_contributors: - login: waynerv count: 25 @@ -227,14 +215,18 @@ top_contributors: count: 13 avatarUrl: https://avatars.githubusercontent.com/u/1104190?u=321a2e953e6645a7d09b732786c7a8061e0f8a8b&v=4 url: https://github.com/euri10 +- login: jaystone776 + count: 13 + avatarUrl: https://avatars.githubusercontent.com/u/11191137?u=299205a95e9b6817a43144a48b643346a5aac5cc&v=4 + url: https://github.com/jaystone776 - login: mariacamilagl count: 12 avatarUrl: https://avatars.githubusercontent.com/u/11489395?u=4adb6986bf3debfc2b8216ae701f2bd47d73da7d&v=4 url: https://github.com/mariacamilagl -- login: jaystone776 - count: 11 - avatarUrl: https://avatars.githubusercontent.com/u/11191137?u=299205a95e9b6817a43144a48b643346a5aac5cc&v=4 - url: https://github.com/jaystone776 +- 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 @@ -248,8 +240,8 @@ top_contributors: avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=cf8455cb899806b774a3a71073f88583adec99f6&v=4 url: https://github.com/Kludex - 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 @@ -263,10 +255,6 @@ top_contributors: count: 5 avatarUrl: https://avatars.githubusercontent.com/u/1175560?v=4 url: https://github.com/Attsun1031 -- login: Smlep - count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/16785985?v=4 - url: https://github.com/Smlep - login: jekirl count: 4 avatarUrl: https://avatars.githubusercontent.com/u/2546697?u=a027452387d85bd4a14834e19d716c99255fb3b7&v=4 @@ -275,13 +263,21 @@ top_contributors: count: 4 avatarUrl: https://avatars.githubusercontent.com/u/805749?v=4 url: https://github.com/jfunez +- login: ycd + 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=36ca24be1fc3daa967b0f409bab0e17132d8c80c&v=4 + url: https://github.com/NinaHwang top_reviewers: - login: Kludex - count: 90 + count: 91 avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=cf8455cb899806b774a3a71073f88583adec99f6&v=4 url: https://github.com/Kludex - login: waynerv @@ -297,29 +293,37 @@ top_reviewers: avatarUrl: https://avatars.githubusercontent.com/u/41147016?u=55010621aece725aa702270b54fed829b6a1fe60&v=4 url: https://github.com/tokusumi - login: ycd - count: 43 + count: 44 avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=826f228edf0bab0d19ad1d5c4ba4df1047ccffef&v=4 url: https://github.com/ycd - login: AdrianDeAnda - count: 32 + count: 33 avatarUrl: https://avatars.githubusercontent.com/u/1024932?u=bb7f8a0d6c9de4e9d0320a9f271210206e202250&v=4 url: https://github.com/AdrianDeAnda - login: ArcLightSlavik - count: 27 + count: 31 avatarUrl: https://avatars.githubusercontent.com/u/31127044?u=81a84af39c89b898b0fbc5a04e8834f60f23e55a&v=4 url: https://github.com/ArcLightSlavik - login: dmontagu count: 23 avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=58ed2a45798a4339700e2f62b2e12e6e54bf0396&v=4 url: https://github.com/dmontagu +- login: cassiobotaro + count: 22 + avatarUrl: https://avatars.githubusercontent.com/u/3127847?u=b0a652331da17efeb85cd6e3a4969182e5004804&v=4 + url: https://github.com/cassiobotaro - login: komtaki count: 21 avatarUrl: https://avatars.githubusercontent.com/u/39375566?u=260ad6b1a4b34c07dbfa728da5e586f16f6d1824&v=4 url: https://github.com/komtaki -- login: cassiobotaro +- login: hard-coders count: 19 - avatarUrl: https://avatars.githubusercontent.com/u/3127847?u=b0a652331da17efeb85cd6e3a4969182e5004804&v=4 - url: https://github.com/cassiobotaro + 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: yanever count: 16 avatarUrl: https://avatars.githubusercontent.com/u/21978760?v=4 @@ -328,10 +332,14 @@ top_reviewers: count: 16 avatarUrl: https://avatars.githubusercontent.com/u/52768429?u=6a3aa15277406520ad37f6236e89466ed44bc5b8&v=4 url: https://github.com/SwftAlpc -- login: hard-coders +- login: Smlep count: 16 - avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=f2d3d2038c55d86d7f9348f4e6c5e30191e4ee8b&v=4 - url: https://github.com/hard-coders + 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 @@ -340,10 +348,6 @@ top_reviewers: count: 15 avatarUrl: https://avatars.githubusercontent.com/u/63476957?u=6c86e59b48e0394d4db230f37fc9ad4d7e2c27c7&v=4 url: https://github.com/delhi09 -- login: Smlep - count: 15 - avatarUrl: https://avatars.githubusercontent.com/u/16785985?v=4 - url: https://github.com/Smlep - login: lsglucas count: 14 avatarUrl: https://avatars.githubusercontent.com/u/61513630?u=320e43fe4dc7bc6efc64e9b8f325f8075634fd20&v=4 @@ -372,18 +376,38 @@ top_reviewers: count: 10 avatarUrl: https://avatars.githubusercontent.com/u/7887703?v=4 url: https://github.com/maoyibo +- login: yezz123 + count: 9 + avatarUrl: https://avatars.githubusercontent.com/u/52716203?u=636b4f79645176df4527dd45c12d5dbb5a4193cf&v=4 + url: https://github.com/yezz123 +- 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: solomein-sv + count: 9 + avatarUrl: https://avatars.githubusercontent.com/u/46193920?u=46acfb4aeefb1d7b9fdc5a8cbd9eb8744683c47a&v=4 + url: https://github.com/solomein-sv - login: blt232018 count: 8 avatarUrl: https://avatars.githubusercontent.com/u/43393471?u=172b0e0391db1aa6c1706498d6dfcb003c8a4857&v=4 url: https://github.com/blt232018 +- login: ComicShrimp + count: 8 + avatarUrl: https://avatars.githubusercontent.com/u/43503750?u=b3e4d9a14d9a65d429ce62c566aef73178b7111d&v=4 + url: https://github.com/ComicShrimp - login: Serrones count: 7 avatarUrl: https://avatars.githubusercontent.com/u/22691749?u=4795b880e13ca33a73e52fc0ef7dc9c60c8fce47&v=4 @@ -396,10 +420,6 @@ top_reviewers: count: 7 avatarUrl: https://avatars.githubusercontent.com/u/10202690?u=e6f86f5c0c3026a15d6b51792fa3e532b12f1371&v=4 url: https://github.com/raphaelauv -- login: graingert - count: 7 - avatarUrl: https://avatars.githubusercontent.com/u/413772?v=4 - url: https://github.com/graingert - login: NastasiaSaby count: 7 avatarUrl: https://avatars.githubusercontent.com/u/8245071?u=b3afd005f9e4bf080c219ef61a592b3a8004b764&v=4 @@ -408,14 +428,10 @@ top_reviewers: count: 7 avatarUrl: https://avatars.githubusercontent.com/u/1405026?v=4 url: https://github.com/Mause -- login: solomein-sv +- login: krocdort count: 7 - avatarUrl: https://avatars.githubusercontent.com/u/46193920?u=46acfb4aeefb1d7b9fdc5a8cbd9eb8744683c47a&v=4 - url: https://github.com/solomein-sv -- login: ComicShrimp - count: 7 - avatarUrl: https://avatars.githubusercontent.com/u/43503750?u=b3e4d9a14d9a65d429ce62c566aef73178b7111d&v=4 - url: https://github.com/ComicShrimp + 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 @@ -424,6 +440,10 @@ top_reviewers: count: 6 avatarUrl: https://avatars.githubusercontent.com/u/47563997?u=63ed66e304fe8d765762c70587d61d9196e5c82d&v=4 url: https://github.com/BilalAlpaslan +- login: diogoduartec + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/31852339?u=b50fc11c531e9b77922e19edfc9e7233d4d7b92e&v=4 + url: https://github.com/diogoduartec - login: nimctl count: 5 avatarUrl: https://avatars.githubusercontent.com/u/49960770?u=e39b11d47188744ee07b2a1c7ce1a1bdf3c80760&v=4 @@ -452,27 +472,3 @@ top_reviewers: count: 4 avatarUrl: https://avatars.githubusercontent.com/u/23391143?u=56ab6bff50be950fa8cae5cf736f2ae66e319ff3&v=4 url: https://github.com/rkbeatss -- login: izaguerreiro - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/2241504?v=4 - url: https://github.com/izaguerreiro -- login: aviramha - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/41201924?u=6883cc4fc13a7b2e60d4deddd4be06f9c5287880&v=4 - url: https://github.com/aviramha -- login: Cajuteq - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/26676532?u=8ee0422981810e51480855de1c0d67b6b79cd3f2&v=4 - url: https://github.com/Cajuteq -- login: Zxilly - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/31370133?v=4 - url: https://github.com/Zxilly -- login: dukkee - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/36825394?u=ccfd86e6a4f2d093dad6f7544cc875af67fa2df8&v=4 - url: https://github.com/dukkee -- login: Bluenix2 - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/38372706?u=c9d28aff15958d6ebf1971148bfb3154ff943c4f&v=4 - url: https://github.com/Bluenix2 From 9c25f9615c037cdfdeccb06b002e6ed8fd50d903 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 12 Dec 2021 11:34:58 +0000 Subject: [PATCH 147/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 2a0d2736..2d3a0f4e 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 👥 Update FastAPI People. PR [#4274](https://github.com/tiangolo/fastapi/pull/4274) by [@github-actions[bot]](https://github.com/apps/github-actions). * 🐛 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). * 🌐 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). From 3efb4f7edff99fdc12802a85ae9d140ec4772497 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 12 Dec 2021 12:39:32 +0100 Subject: [PATCH 148/196] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.70?= =?UTF-8?q?.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 14 +++++++++++++- fastapi/__init__.py | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 2d3a0f4e..5995ecca 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,8 +2,16 @@ ## Latest Changes -* 👥 Update FastAPI People. PR [#4274](https://github.com/tiangolo/fastapi/pull/4274) by [@github-actions[bot]](https://github.com/apps/github-actions). +## 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). @@ -19,6 +27,10 @@ * 🌐 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. diff --git a/fastapi/__init__.py b/fastapi/__init__.py index 4d4d4333..8bb6ce15 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.70.0" +__version__ = "0.70.1" from starlette import status as status From 2b10ca1cc47146b07b6598ac9473cbe8cac50cc0 Mon Sep 17 00:00:00 2001 From: simondale00 <33907262+simondale00@users.noreply.github.com> Date: Fri, 7 Jan 2022 04:34:28 -0500 Subject: [PATCH 149/196] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Upgrade=20Starlett?= =?UTF-8?q?e=20to=200.17.1=20(#4145)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dima Tisnek Co-authored-by: simond Co-authored-by: Samuel Colvin Co-authored-by: Samuel Colvin --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 50e0afe8..4feae552 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,7 @@ classifiers = [ "Topic :: Internet :: WWW/HTTP", ] requires = [ - "starlette ==0.16.0", + "starlette ==0.17.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" From 4e9f75912f75f09816328d5f8a28060c9d1ddd69 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 7 Jan 2022 09:35:10 +0000 Subject: [PATCH 150/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 5995ecca..add51ca5 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ⬆️ Upgrade Starlette to 0.17.1. PR [#4145](https://github.com/tiangolo/fastapi/pull/4145) by [@simondale00](https://github.com/simondale00). ## 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. 🤩 From 764ecae2d477c48ba2e61dda44ee3d663d54b84d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 7 Jan 2022 11:24:00 +0100 Subject: [PATCH 151/196] =?UTF-8?q?=E2=AC=86=20Upgrade=20MkDocs=20Material?= =?UTF-8?q?=20and=20configs=20(#4385)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/az/mkdocs.yml | 9 +++++---- docs/de/mkdocs.yml | 9 +++++---- docs/en/mkdocs.yml | 9 +++++---- docs/es/mkdocs.yml | 9 +++++---- docs/fr/mkdocs.yml | 9 +++++---- docs/id/mkdocs.yml | 9 +++++---- docs/it/mkdocs.yml | 9 +++++---- docs/ja/mkdocs.yml | 9 +++++---- docs/ko/mkdocs.yml | 9 +++++---- docs/pl/mkdocs.yml | 9 +++++---- docs/pt/mkdocs.yml | 9 +++++---- docs/ru/mkdocs.yml | 9 +++++---- docs/sq/mkdocs.yml | 9 +++++---- docs/tr/mkdocs.yml | 9 +++++---- docs/uk/mkdocs.yml | 9 +++++---- docs/zh/mkdocs.yml | 9 +++++---- pyproject.toml | 2 +- 17 files changed, 81 insertions(+), 65 deletions(-) diff --git a/docs/az/mkdocs.yml b/docs/az/mkdocs.yml index 72ca92a9..66220f63 100644 --- a/docs/az/mkdocs.yml +++ b/docs/az/mkdocs.yml @@ -29,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: @@ -70,8 +67,12 @@ markdown_extensions: - name: mermaid class: mermaid format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed +- 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 diff --git a/docs/de/mkdocs.yml b/docs/de/mkdocs.yml index 621305d2..360fa8c4 100644 --- a/docs/de/mkdocs.yml +++ b/docs/de/mkdocs.yml @@ -29,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: @@ -71,8 +68,12 @@ markdown_extensions: - name: mermaid class: mermaid format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed +- 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 diff --git a/docs/en/mkdocs.yml b/docs/en/mkdocs.yml index aa2d0451..5bdd2c54 100644 --- a/docs/en/mkdocs.yml +++ b/docs/en/mkdocs.yml @@ -29,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: @@ -176,8 +173,12 @@ markdown_extensions: - name: mermaid class: mermaid format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed +- 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 diff --git a/docs/es/mkdocs.yml b/docs/es/mkdocs.yml index ef722715..a4bc4115 100644 --- a/docs/es/mkdocs.yml +++ b/docs/es/mkdocs.yml @@ -29,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: @@ -80,8 +77,12 @@ markdown_extensions: - name: mermaid class: mermaid format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed +- 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 diff --git a/docs/fr/mkdocs.yml b/docs/fr/mkdocs.yml index 6242a88f..ff16e1d7 100644 --- a/docs/fr/mkdocs.yml +++ b/docs/fr/mkdocs.yml @@ -29,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: @@ -85,8 +82,12 @@ markdown_extensions: - name: mermaid class: mermaid format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed +- 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 diff --git a/docs/id/mkdocs.yml b/docs/id/mkdocs.yml index f9cae22c..d70d2b3c 100644 --- a/docs/id/mkdocs.yml +++ b/docs/id/mkdocs.yml @@ -29,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: @@ -70,8 +67,12 @@ markdown_extensions: - name: mermaid class: mermaid format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed +- 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 diff --git a/docs/it/mkdocs.yml b/docs/it/mkdocs.yml index f1621060..e6d01fbd 100644 --- a/docs/it/mkdocs.yml +++ b/docs/it/mkdocs.yml @@ -29,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: @@ -70,8 +67,12 @@ markdown_extensions: - name: mermaid class: mermaid format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed +- 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 diff --git a/docs/ja/mkdocs.yml b/docs/ja/mkdocs.yml index 237bc965..39fd8a21 100644 --- a/docs/ja/mkdocs.yml +++ b/docs/ja/mkdocs.yml @@ -29,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: @@ -110,8 +107,12 @@ markdown_extensions: - name: mermaid class: mermaid format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed +- 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 diff --git a/docs/ko/mkdocs.yml b/docs/ko/mkdocs.yml index 7a75940a..1d4d3091 100644 --- a/docs/ko/mkdocs.yml +++ b/docs/ko/mkdocs.yml @@ -29,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: @@ -80,8 +77,12 @@ markdown_extensions: - name: mermaid class: mermaid format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed +- 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 diff --git a/docs/pl/mkdocs.yml b/docs/pl/mkdocs.yml index 22000860..3c1351a1 100644 --- a/docs/pl/mkdocs.yml +++ b/docs/pl/mkdocs.yml @@ -29,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: @@ -70,8 +67,12 @@ markdown_extensions: - name: mermaid class: mermaid format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed +- 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 diff --git a/docs/pt/mkdocs.yml b/docs/pt/mkdocs.yml index ea4af852..f202f306 100644 --- a/docs/pt/mkdocs.yml +++ b/docs/pt/mkdocs.yml @@ -29,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: @@ -90,8 +87,12 @@ markdown_extensions: - name: mermaid class: mermaid format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed +- 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 diff --git a/docs/ru/mkdocs.yml b/docs/ru/mkdocs.yml index e15a36bf..6e17c287 100644 --- a/docs/ru/mkdocs.yml +++ b/docs/ru/mkdocs.yml @@ -29,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: @@ -70,8 +67,12 @@ markdown_extensions: - name: mermaid class: mermaid format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed +- 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 diff --git a/docs/sq/mkdocs.yml b/docs/sq/mkdocs.yml index a4879521..d9c3dad4 100644 --- a/docs/sq/mkdocs.yml +++ b/docs/sq/mkdocs.yml @@ -29,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: @@ -70,8 +67,12 @@ markdown_extensions: - name: mermaid class: mermaid format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed +- 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 diff --git a/docs/tr/mkdocs.yml b/docs/tr/mkdocs.yml index 5ef8fd6a..f6ed7f5b 100644 --- a/docs/tr/mkdocs.yml +++ b/docs/tr/mkdocs.yml @@ -29,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: @@ -73,8 +70,12 @@ markdown_extensions: - name: mermaid class: mermaid format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed +- 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 diff --git a/docs/uk/mkdocs.yml b/docs/uk/mkdocs.yml index 7f5785e3..d0de8cc0 100644 --- a/docs/uk/mkdocs.yml +++ b/docs/uk/mkdocs.yml @@ -29,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: @@ -70,8 +67,12 @@ markdown_extensions: - name: mermaid class: mermaid format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed +- 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 diff --git a/docs/zh/mkdocs.yml b/docs/zh/mkdocs.yml index 07ffa367..a929e338 100644 --- a/docs/zh/mkdocs.yml +++ b/docs/zh/mkdocs.yml @@ -29,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: @@ -120,8 +117,12 @@ markdown_extensions: - name: mermaid class: mermaid format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed +- 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 diff --git a/pyproject.toml b/pyproject.toml index 4feae552..e6148831 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,7 +69,7 @@ test = [ ] doc = [ "mkdocs >=1.1.2,<2.0.0", - "mkdocs-material >=7.1.9,<8.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", "typer-cli >=0.0.12,<0.0.13", From 83f67810371f48c22b7955de926d86e42ac30416 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 7 Jan 2022 10:24:43 +0000 Subject: [PATCH 152/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index add51ca5..ce1d3144 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ⬆ Upgrade MkDocs Material and configs. PR [#4385](https://github.com/tiangolo/fastapi/pull/4385) by [@tiangolo](https://github.com/tiangolo). * ⬆️ Upgrade Starlette to 0.17.1. PR [#4145](https://github.com/tiangolo/fastapi/pull/4145) by [@simondale00](https://github.com/simondale00). ## 0.70.1 From d08a062ee2121f446537f06c8425cdeb1209e4ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 7 Jan 2022 15:11:31 +0100 Subject: [PATCH 153/196] =?UTF-8?q?=E2=9C=A8=20Add=20docs=20and=20tests=20?= =?UTF-8?q?for=20Python=203.9=20and=20Python=203.10=20(#3712)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Thomas Grainger --- .github/workflows/test.yml | 2 +- docs/az/mkdocs.yml | 1 - docs/de/mkdocs.yml | 1 - docs/en/docs/python-types.md | 208 ++++++++-- docs/en/docs/tutorial/background-tasks.md | 14 +- docs/en/docs/tutorial/body-fields.md | 28 +- docs/en/docs/tutorial/body-multiple-params.md | 76 +++- docs/en/docs/tutorial/body-nested-models.md | 208 ++++++++-- docs/en/docs/tutorial/body-updates.md | 80 +++- docs/en/docs/tutorial/body.md | 84 +++- docs/en/docs/tutorial/cookie-params.md | 28 +- .../dependencies/classes-as-dependencies.md | 98 ++++- docs/en/docs/tutorial/dependencies/index.md | 42 +- .../tutorial/dependencies/sub-dependencies.md | 48 ++- docs/en/docs/tutorial/encoder.md | 14 +- docs/en/docs/tutorial/extra-data-types.md | 28 +- docs/en/docs/tutorial/extra-models.md | 86 +++- docs/en/docs/tutorial/header-params.md | 63 ++- .../tutorial/path-operation-configuration.md | 100 ++++- .../path-params-numeric-validations.md | 30 +- .../tutorial/query-params-str-validations.md | 182 +++++++-- docs/en/docs/tutorial/query-params.md | 61 ++- docs/en/docs/tutorial/request-files.md | 16 +- docs/en/docs/tutorial/response-model.md | 158 +++++-- docs/en/docs/tutorial/schema-extra-example.md | 56 ++- .../tutorial/security/get-current-user.md | 72 +++- docs/en/docs/tutorial/security/oauth2-jwt.md | 56 ++- .../docs/tutorial/security/simple-oauth2.md | 70 +++- docs/en/docs/tutorial/sql-databases.md | 164 ++++++-- docs/en/docs/tutorial/testing.md | 20 +- docs/en/mkdocs.yml | 1 - docs/es/mkdocs.yml | 1 - docs/fr/mkdocs.yml | 1 - docs/id/mkdocs.yml | 1 - docs/it/mkdocs.yml | 1 - docs/ja/mkdocs.yml | 1 - docs/ko/mkdocs.yml | 1 - docs/pl/mkdocs.yml | 1 - docs/pt/mkdocs.yml | 1 - docs/ru/mkdocs.yml | 1 - docs/sq/mkdocs.yml | 1 - docs/tr/mkdocs.yml | 1 - docs/uk/mkdocs.yml | 1 - docs/zh/mkdocs.yml | 1 - docs_src/app_testing/app_b/__init__.py | 0 .../app_testing/{main_b.py => app_b/main.py} | 0 .../{test_main_b.py => app_b/test_main.py} | 2 +- docs_src/app_testing/app_b_py310/__init__.py | 0 docs_src/app_testing/app_b_py310/main.py | 36 ++ docs_src/app_testing/app_b_py310/test_main.py | 65 +++ .../background_tasks/tutorial002_py310.py | 24 ++ docs_src/body/tutorial001_py310.py | 17 + docs_src/body/tutorial002_py310.py | 21 + docs_src/body/tutorial003_py310.py | 17 + docs_src/body/tutorial004_py310.py | 20 + docs_src/body_fields/tutorial001_py310.py | 19 + .../body_multiple_params/tutorial001_py310.py | 26 ++ .../body_multiple_params/tutorial002_py310.py | 22 + .../body_multiple_params/tutorial003_py310.py | 24 ++ .../body_multiple_params/tutorial004_py310.py | 31 ++ .../body_multiple_params/tutorial005_py310.py | 17 + .../body_nested_models/tutorial001_py310.py | 18 + .../body_nested_models/tutorial002_py310.py | 18 + .../body_nested_models/tutorial002_py39.py | 20 + .../body_nested_models/tutorial003_py310.py | 18 + .../body_nested_models/tutorial003_py39.py | 20 + .../body_nested_models/tutorial004_py310.py | 24 ++ .../body_nested_models/tutorial004_py39.py | 26 ++ .../body_nested_models/tutorial005_py310.py | 24 ++ .../body_nested_models/tutorial005_py39.py | 26 ++ .../body_nested_models/tutorial006_py310.py | 24 ++ .../body_nested_models/tutorial006_py39.py | 26 ++ .../body_nested_models/tutorial007_py310.py | 30 ++ .../body_nested_models/tutorial007_py39.py | 32 ++ .../body_nested_models/tutorial008_py39.py | 14 + .../body_nested_models/tutorial009_py39.py | 8 + docs_src/body_updates/tutorial001_py310.py | 32 ++ docs_src/body_updates/tutorial001_py39.py | 34 ++ docs_src/body_updates/tutorial002_py310.py | 35 ++ docs_src/body_updates/tutorial002_py39.py | 37 ++ docs_src/cookie_params/tutorial001_py310.py | 8 + docs_src/dependencies/tutorial001_py310.py | 17 + docs_src/dependencies/tutorial002_py310.py | 23 ++ docs_src/dependencies/tutorial003_py310.py | 23 ++ docs_src/dependencies/tutorial004_py310.py | 23 ++ docs_src/dependencies/tutorial005_py310.py | 20 + docs_src/encoder/tutorial001_py310.py | 22 + .../extra_data_types/tutorial001_py310.py | 27 ++ docs_src/extra_models/tutorial001_py310.py | 41 ++ docs_src/extra_models/tutorial002_py310.py | 39 ++ docs_src/extra_models/tutorial003_py310.py | 35 ++ docs_src/extra_models/tutorial004_py39.py | 20 + docs_src/extra_models/tutorial005_py39.py | 8 + docs_src/header_params/tutorial001_py310.py | 8 + docs_src/header_params/tutorial002_py310.py | 10 + docs_src/header_params/tutorial003_py310.py | 8 + docs_src/header_params/tutorial003_py39.py | 10 + .../tutorial001.py | 2 +- .../tutorial001_py310.py | 17 + .../tutorial001_py39.py | 19 + .../tutorial002.py | 2 +- .../tutorial002_py310.py | 27 ++ .../tutorial002_py39.py | 29 ++ .../tutorial003.py | 2 +- .../tutorial003_py310.py | 22 + .../tutorial003_py39.py | 24 ++ .../tutorial004.py | 2 +- .../tutorial004_py310.py | 26 ++ .../tutorial004_py39.py | 28 ++ .../tutorial005.py | 2 +- .../tutorial005_py310.py | 31 ++ .../tutorial005_py39.py | 33 ++ .../tutorial001_py310.py | 14 + docs_src/python_types/tutorial006_py39.py | 3 + docs_src/python_types/tutorial007_py39.py | 2 + docs_src/python_types/tutorial008.py | 5 +- docs_src/python_types/tutorial008b.py | 5 + docs_src/python_types/tutorial008b_py310.py | 2 + docs_src/python_types/tutorial009_py310.py | 5 + docs_src/python_types/tutorial009b.py | 8 + docs_src/python_types/tutorial011_py310.py | 22 + docs_src/python_types/tutorial011_py39.py | 23 ++ docs_src/query_params/tutorial002_py310.py | 10 + docs_src/query_params/tutorial003_py310.py | 15 + docs_src/query_params/tutorial004_py310.py | 17 + docs_src/query_params/tutorial006_py310.py | 11 + docs_src/query_params/tutorial006b.py | 13 + .../tutorial001_py310.py | 11 + .../tutorial002_py310.py | 11 + .../tutorial003_py310.py | 11 + .../tutorial004_py310.py | 13 + .../tutorial007_py310.py | 11 + .../tutorial008_py310.py | 19 + .../tutorial009_py310.py | 11 + .../tutorial010_py310.py | 23 ++ .../tutorial011_py310.py | 9 + .../tutorial011_py39.py | 11 + .../tutorial012_py39.py | 9 + docs_src/request_files/tutorial002_py39.py | 31 ++ docs_src/response_model/tutorial001_py310.py | 17 + docs_src/response_model/tutorial001_py39.py | 19 + docs_src/response_model/tutorial002_py310.py | 17 + docs_src/response_model/tutorial003_py310.py | 22 + docs_src/response_model/tutorial004_py310.py | 24 ++ docs_src/response_model/tutorial004_py39.py | 26 ++ docs_src/response_model/tutorial005_py310.py | 37 ++ docs_src/response_model/tutorial006_py310.py | 37 ++ .../schema_extra_example/tutorial001_py310.py | 27 ++ .../schema_extra_example/tutorial002_py310.py | 17 + .../schema_extra_example/tutorial003_py310.py | 28 ++ .../schema_extra_example/tutorial004_py310.py | 50 +++ docs_src/security/tutorial002_py310.py | 30 ++ docs_src/security/tutorial003_py310.py | 88 ++++ docs_src/security/tutorial004_py310.py | 137 +++++++ docs_src/security/tutorial005_py310.py | 172 ++++++++ docs_src/security/tutorial005_py39.py | 173 ++++++++ .../sql_databases/sql_app_py310/__init__.py | 0 .../sql_databases/sql_app_py310/alt_main.py | 60 +++ docs_src/sql_databases/sql_app_py310/crud.py | 36 ++ .../sql_databases/sql_app_py310/database.py | 13 + docs_src/sql_databases/sql_app_py310/main.py | 53 +++ .../sql_databases/sql_app_py310/models.py | 26 ++ .../sql_databases/sql_app_py310/schemas.py | 35 ++ .../sql_app_py310/tests/__init__.py | 0 .../sql_app_py310/tests/test_sql_app.py | 47 +++ .../sql_databases/sql_app_py39/__init__.py | 0 .../sql_databases/sql_app_py39/alt_main.py | 60 +++ docs_src/sql_databases/sql_app_py39/crud.py | 36 ++ .../sql_databases/sql_app_py39/database.py | 13 + docs_src/sql_databases/sql_app_py39/main.py | 53 +++ docs_src/sql_databases/sql_app_py39/models.py | 26 ++ .../sql_databases/sql_app_py39/schemas.py | 37 ++ .../sql_app_py39/tests/__init__.py | 0 .../sql_app_py39/tests/test_sql_app.py | 47 +++ pyproject.toml | 1 + .../test_tutorial002_py310.py | 21 + .../test_body/test_tutorial001_py310.py | 292 +++++++++++++ .../test_tutorial001_py310.py | 176 ++++++++ .../test_tutorial001_py310.py | 155 +++++++ .../test_tutorial003_py310.py | 206 ++++++++++ .../test_tutorial009_py39.py | 113 ++++++ .../test_tutorial001_py310.py | 172 ++++++++ .../test_tutorial001_py39.py | 172 ++++++++ .../test_tutorial001_py310.py | 100 +++++ .../test_tutorial001_py310.py | 157 +++++++ .../test_tutorial004_py310.py | 152 +++++++ .../test_tutorial001_py310.py | 146 +++++++ .../test_tutorial003_py310.py | 135 ++++++ .../test_tutorial004_py39.py | 69 ++++ .../test_tutorial005_py39.py | 53 +++ .../test_tutorial001_py310.py | 94 +++++ .../test_tutorial005_py310.py | 121 ++++++ .../test_tutorial005_py39.py | 121 ++++++ .../test_tutorial006_py310.py | 148 +++++++ .../test_tutorial001_py310.py | 132 ++++++ .../test_tutorial011_py310.py | 105 +++++ .../test_tutorial011_py39.py | 105 +++++ .../test_tutorial012_py39.py | 106 +++++ .../test_tutorial002_py39.py | 235 +++++++++++ .../test_tutorial003_py310.py | 129 ++++++ .../test_tutorial004_py310.py | 133 ++++++ .../test_tutorial004_py39.py | 133 ++++++ .../test_tutorial005_py310.py | 152 +++++++ .../test_tutorial006_py310.py | 152 +++++++ .../test_tutorial004_py310.py | 143 +++++++ .../test_security/test_tutorial003_py310.py | 192 +++++++++ .../test_security/test_tutorial005_py310.py | 375 +++++++++++++++++ .../test_security/test_tutorial005_py39.py | 375 +++++++++++++++++ .../test_sql_databases/test_sql_databases.py | 7 +- .../test_sql_databases_middleware_py310.py | 384 ++++++++++++++++++ .../test_sql_databases_middleware_py39.py | 384 ++++++++++++++++++ .../test_sql_databases_py310.py | 383 +++++++++++++++++ .../test_sql_databases_py39.py | 383 +++++++++++++++++ .../test_testing_databases.py | 9 +- .../test_testing_databases_py310.py | 26 ++ .../test_testing_databases_py39.py | 26 ++ .../test_tutorial/test_testing/test_main_b.py | 14 +- .../test_testing/test_main_b_py310.py | 13 + tests/utils.py | 3 + 219 files changed, 11562 insertions(+), 452 deletions(-) create mode 100644 docs_src/app_testing/app_b/__init__.py rename docs_src/app_testing/{main_b.py => app_b/main.py} (100%) rename docs_src/app_testing/{test_main_b.py => app_b/test_main.py} (98%) create mode 100644 docs_src/app_testing/app_b_py310/__init__.py create mode 100644 docs_src/app_testing/app_b_py310/main.py create mode 100644 docs_src/app_testing/app_b_py310/test_main.py create mode 100644 docs_src/background_tasks/tutorial002_py310.py create mode 100644 docs_src/body/tutorial001_py310.py create mode 100644 docs_src/body/tutorial002_py310.py create mode 100644 docs_src/body/tutorial003_py310.py create mode 100644 docs_src/body/tutorial004_py310.py create mode 100644 docs_src/body_fields/tutorial001_py310.py create mode 100644 docs_src/body_multiple_params/tutorial001_py310.py create mode 100644 docs_src/body_multiple_params/tutorial002_py310.py create mode 100644 docs_src/body_multiple_params/tutorial003_py310.py create mode 100644 docs_src/body_multiple_params/tutorial004_py310.py create mode 100644 docs_src/body_multiple_params/tutorial005_py310.py create mode 100644 docs_src/body_nested_models/tutorial001_py310.py create mode 100644 docs_src/body_nested_models/tutorial002_py310.py create mode 100644 docs_src/body_nested_models/tutorial002_py39.py create mode 100644 docs_src/body_nested_models/tutorial003_py310.py create mode 100644 docs_src/body_nested_models/tutorial003_py39.py create mode 100644 docs_src/body_nested_models/tutorial004_py310.py create mode 100644 docs_src/body_nested_models/tutorial004_py39.py create mode 100644 docs_src/body_nested_models/tutorial005_py310.py create mode 100644 docs_src/body_nested_models/tutorial005_py39.py create mode 100644 docs_src/body_nested_models/tutorial006_py310.py create mode 100644 docs_src/body_nested_models/tutorial006_py39.py create mode 100644 docs_src/body_nested_models/tutorial007_py310.py create mode 100644 docs_src/body_nested_models/tutorial007_py39.py create mode 100644 docs_src/body_nested_models/tutorial008_py39.py create mode 100644 docs_src/body_nested_models/tutorial009_py39.py create mode 100644 docs_src/body_updates/tutorial001_py310.py create mode 100644 docs_src/body_updates/tutorial001_py39.py create mode 100644 docs_src/body_updates/tutorial002_py310.py create mode 100644 docs_src/body_updates/tutorial002_py39.py create mode 100644 docs_src/cookie_params/tutorial001_py310.py create mode 100644 docs_src/dependencies/tutorial001_py310.py create mode 100644 docs_src/dependencies/tutorial002_py310.py create mode 100644 docs_src/dependencies/tutorial003_py310.py create mode 100644 docs_src/dependencies/tutorial004_py310.py create mode 100644 docs_src/dependencies/tutorial005_py310.py create mode 100644 docs_src/encoder/tutorial001_py310.py create mode 100644 docs_src/extra_data_types/tutorial001_py310.py create mode 100644 docs_src/extra_models/tutorial001_py310.py create mode 100644 docs_src/extra_models/tutorial002_py310.py create mode 100644 docs_src/extra_models/tutorial003_py310.py create mode 100644 docs_src/extra_models/tutorial004_py39.py create mode 100644 docs_src/extra_models/tutorial005_py39.py create mode 100644 docs_src/header_params/tutorial001_py310.py create mode 100644 docs_src/header_params/tutorial002_py310.py create mode 100644 docs_src/header_params/tutorial003_py310.py create mode 100644 docs_src/header_params/tutorial003_py39.py create mode 100644 docs_src/path_operation_configuration/tutorial001_py310.py create mode 100644 docs_src/path_operation_configuration/tutorial001_py39.py create mode 100644 docs_src/path_operation_configuration/tutorial002_py310.py create mode 100644 docs_src/path_operation_configuration/tutorial002_py39.py create mode 100644 docs_src/path_operation_configuration/tutorial003_py310.py create mode 100644 docs_src/path_operation_configuration/tutorial003_py39.py create mode 100644 docs_src/path_operation_configuration/tutorial004_py310.py create mode 100644 docs_src/path_operation_configuration/tutorial004_py39.py create mode 100644 docs_src/path_operation_configuration/tutorial005_py310.py create mode 100644 docs_src/path_operation_configuration/tutorial005_py39.py create mode 100644 docs_src/path_params_numeric_validations/tutorial001_py310.py create mode 100644 docs_src/python_types/tutorial006_py39.py create mode 100644 docs_src/python_types/tutorial007_py39.py create mode 100644 docs_src/python_types/tutorial008b.py create mode 100644 docs_src/python_types/tutorial008b_py310.py create mode 100644 docs_src/python_types/tutorial009_py310.py create mode 100644 docs_src/python_types/tutorial009b.py create mode 100644 docs_src/python_types/tutorial011_py310.py create mode 100644 docs_src/python_types/tutorial011_py39.py create mode 100644 docs_src/query_params/tutorial002_py310.py create mode 100644 docs_src/query_params/tutorial003_py310.py create mode 100644 docs_src/query_params/tutorial004_py310.py create mode 100644 docs_src/query_params/tutorial006_py310.py create mode 100644 docs_src/query_params/tutorial006b.py create mode 100644 docs_src/query_params_str_validations/tutorial001_py310.py create mode 100644 docs_src/query_params_str_validations/tutorial002_py310.py create mode 100644 docs_src/query_params_str_validations/tutorial003_py310.py create mode 100644 docs_src/query_params_str_validations/tutorial004_py310.py create mode 100644 docs_src/query_params_str_validations/tutorial007_py310.py create mode 100644 docs_src/query_params_str_validations/tutorial008_py310.py create mode 100644 docs_src/query_params_str_validations/tutorial009_py310.py create mode 100644 docs_src/query_params_str_validations/tutorial010_py310.py create mode 100644 docs_src/query_params_str_validations/tutorial011_py310.py create mode 100644 docs_src/query_params_str_validations/tutorial011_py39.py create mode 100644 docs_src/query_params_str_validations/tutorial012_py39.py create mode 100644 docs_src/request_files/tutorial002_py39.py create mode 100644 docs_src/response_model/tutorial001_py310.py create mode 100644 docs_src/response_model/tutorial001_py39.py create mode 100644 docs_src/response_model/tutorial002_py310.py create mode 100644 docs_src/response_model/tutorial003_py310.py create mode 100644 docs_src/response_model/tutorial004_py310.py create mode 100644 docs_src/response_model/tutorial004_py39.py create mode 100644 docs_src/response_model/tutorial005_py310.py create mode 100644 docs_src/response_model/tutorial006_py310.py create mode 100644 docs_src/schema_extra_example/tutorial001_py310.py create mode 100644 docs_src/schema_extra_example/tutorial002_py310.py create mode 100644 docs_src/schema_extra_example/tutorial003_py310.py create mode 100644 docs_src/schema_extra_example/tutorial004_py310.py create mode 100644 docs_src/security/tutorial002_py310.py create mode 100644 docs_src/security/tutorial003_py310.py create mode 100644 docs_src/security/tutorial004_py310.py create mode 100644 docs_src/security/tutorial005_py310.py create mode 100644 docs_src/security/tutorial005_py39.py create mode 100644 docs_src/sql_databases/sql_app_py310/__init__.py create mode 100644 docs_src/sql_databases/sql_app_py310/alt_main.py create mode 100644 docs_src/sql_databases/sql_app_py310/crud.py create mode 100644 docs_src/sql_databases/sql_app_py310/database.py create mode 100644 docs_src/sql_databases/sql_app_py310/main.py create mode 100644 docs_src/sql_databases/sql_app_py310/models.py create mode 100644 docs_src/sql_databases/sql_app_py310/schemas.py create mode 100644 docs_src/sql_databases/sql_app_py310/tests/__init__.py create mode 100644 docs_src/sql_databases/sql_app_py310/tests/test_sql_app.py create mode 100644 docs_src/sql_databases/sql_app_py39/__init__.py create mode 100644 docs_src/sql_databases/sql_app_py39/alt_main.py create mode 100644 docs_src/sql_databases/sql_app_py39/crud.py create mode 100644 docs_src/sql_databases/sql_app_py39/database.py create mode 100644 docs_src/sql_databases/sql_app_py39/main.py create mode 100644 docs_src/sql_databases/sql_app_py39/models.py create mode 100644 docs_src/sql_databases/sql_app_py39/schemas.py create mode 100644 docs_src/sql_databases/sql_app_py39/tests/__init__.py create mode 100644 docs_src/sql_databases/sql_app_py39/tests/test_sql_app.py create mode 100644 tests/test_tutorial/test_background_tasks/test_tutorial002_py310.py create mode 100644 tests/test_tutorial/test_body/test_tutorial001_py310.py create mode 100644 tests/test_tutorial/test_body_fields/test_tutorial001_py310.py create mode 100644 tests/test_tutorial/test_body_multiple_params/test_tutorial001_py310.py create mode 100644 tests/test_tutorial/test_body_multiple_params/test_tutorial003_py310.py create mode 100644 tests/test_tutorial/test_body_nested_models/test_tutorial009_py39.py create mode 100644 tests/test_tutorial/test_body_updates/test_tutorial001_py310.py create mode 100644 tests/test_tutorial/test_body_updates/test_tutorial001_py39.py create mode 100644 tests/test_tutorial/test_cookie_params/test_tutorial001_py310.py create mode 100644 tests/test_tutorial/test_dependencies/test_tutorial001_py310.py create mode 100644 tests/test_tutorial/test_dependencies/test_tutorial004_py310.py create mode 100644 tests/test_tutorial/test_extra_data_types/test_tutorial001_py310.py create mode 100644 tests/test_tutorial/test_extra_models/test_tutorial003_py310.py create mode 100644 tests/test_tutorial/test_extra_models/test_tutorial004_py39.py create mode 100644 tests/test_tutorial/test_extra_models/test_tutorial005_py39.py create mode 100644 tests/test_tutorial/test_header_params/test_tutorial001_py310.py create mode 100644 tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py310.py create mode 100644 tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py39.py create mode 100644 tests/test_tutorial/test_query_params/test_tutorial006_py310.py create mode 100644 tests/test_tutorial/test_query_params_str_validations/test_tutorial001_py310.py create mode 100644 tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py310.py create mode 100644 tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py39.py create mode 100644 tests/test_tutorial/test_query_params_str_validations/test_tutorial012_py39.py create mode 100644 tests/test_tutorial/test_request_files/test_tutorial002_py39.py create mode 100644 tests/test_tutorial/test_response_model/test_tutorial003_py310.py create mode 100644 tests/test_tutorial/test_response_model/test_tutorial004_py310.py create mode 100644 tests/test_tutorial/test_response_model/test_tutorial004_py39.py create mode 100644 tests/test_tutorial/test_response_model/test_tutorial005_py310.py create mode 100644 tests/test_tutorial/test_response_model/test_tutorial006_py310.py create mode 100644 tests/test_tutorial/test_schema_extra_example/test_tutorial004_py310.py create mode 100644 tests/test_tutorial/test_security/test_tutorial003_py310.py create mode 100644 tests/test_tutorial/test_security/test_tutorial005_py310.py create mode 100644 tests/test_tutorial/test_security/test_tutorial005_py39.py create mode 100644 tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py310.py create mode 100644 tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py39.py create mode 100644 tests/test_tutorial/test_sql_databases/test_sql_databases_py310.py create mode 100644 tests/test_tutorial/test_sql_databases/test_sql_databases_py39.py create mode 100644 tests/test_tutorial/test_sql_databases/test_testing_databases_py310.py create mode 100644 tests/test_tutorial/test_sql_databases/test_testing_databases_py39.py create mode 100644 tests/test_tutorial/test_testing/test_main_b_py310.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7ab0f123..21ea7c1a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.6, 3.7, 3.8, 3.9] + python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] fail-fast: false steps: diff --git a/docs/az/mkdocs.yml b/docs/az/mkdocs.yml index 66220f63..dbff7b26 100644 --- a/docs/az/mkdocs.yml +++ b/docs/az/mkdocs.yml @@ -20,7 +20,6 @@ theme: features: - search.suggest - search.highlight - - content.tabs.link icon: repo: fontawesome/brands/github-alt logo: https://fastapi.tiangolo.com/img/icon-white.svg diff --git a/docs/de/mkdocs.yml b/docs/de/mkdocs.yml index 360fa8c4..8f29ef31 100644 --- a/docs/de/mkdocs.yml +++ b/docs/de/mkdocs.yml @@ -20,7 +20,6 @@ theme: features: - search.suggest - search.highlight - - content.tabs.link icon: repo: fontawesome/brands/github-alt logo: https://fastapi.tiangolo.com/img/icon-white.svg diff --git a/docs/en/docs/python-types.md b/docs/en/docs/python-types.md index 9bbf955b..76d44285 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.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/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/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 a4484147..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="28" -{!../../../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 5bacf166..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 @@ -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="9" -{!../../../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 7747e3e1..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,21 +79,45 @@ 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. @@ -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/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/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/path-operation-configuration.md b/docs/en/docs/tutorial/path-operation-configuration.md index 0d606331..1ff448e7 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,9 +42,23 @@ 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: @@ -40,9 +68,23 @@ They will be added to the OpenAPI schema and used by the automatic documentation 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 +92,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 +118,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..fcac1a4e 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,9 +371,17 @@ 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: 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 68ea654c..b7257c7e 100644 --- a/docs/en/docs/tutorial/request-files.md +++ b/docs/en/docs/tutorial/request-files.md @@ -119,11 +119,19 @@ 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. diff --git a/docs/en/docs/tutorial/response-model.md b/docs/en/docs/tutorial/response-model.md index e2b89ca9..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,15 +49,31 @@ 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. @@ -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 71f9a391..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 diff --git a/docs/en/docs/tutorial/security/get-current-user.md b/docs/en/docs/tutorial/security/get-current-user.md index a41db2b6..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,7 +112,6 @@ 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. @@ -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 f88cf23c..09557f1a 100644 --- a/docs/en/docs/tutorial/security/oauth2-jwt.md +++ b/docs/en/docs/tutorial/security/oauth2-jwt.md @@ -109,9 +109,17 @@ And another utility to verify if a received password matches the hash stored. And another one to authenticate and return a user. -```Python hl_lines="7 48 55-56 59-60 69-75" -{!../../../docs_src/security/tutorial004.py!} -``` +=== "Python 3.6 and above" + + ```Python hl_lines="7 48 55-56 59-60 69-75" + {!> ../../../docs_src/security/tutorial004.py!} + ``` + +=== "Python 3.10 and above" + + ```Python hl_lines="6 47 54-55 58-59 68-74" + {!> ../../../docs_src/security/tutorial004_py310.py!} + ``` !!! note If you check the new (fake) database `fake_users_db`, you will see how the hashed password looks like now: `"$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW"`. @@ -144,9 +152,17 @@ Define a Pydantic Model that will be used in the token endpoint for the response Create a utility function to generate a new access token. -```Python hl_lines="6 12-14 28-30 78-86" -{!../../../docs_src/security/tutorial004.py!} -``` +=== "Python 3.6 and above" + + ```Python hl_lines="6 12-14 28-30 78-86" + {!> ../../../docs_src/security/tutorial004.py!} + ``` + +=== "Python 3.10 and above" + + ```Python hl_lines="5 11-13 27-29 77-85" + {!> ../../../docs_src/security/tutorial004_py310.py!} + ``` ## Update the dependencies @@ -156,9 +172,17 @@ Decode the received token, verify it, and return the current user. If the token is invalid, return an HTTP error right away. -```Python hl_lines="89-106" -{!../../../docs_src/security/tutorial004.py!} -``` +=== "Python 3.6 and above" + + ```Python hl_lines="89-106" + {!> ../../../docs_src/security/tutorial004.py!} + ``` + +=== "Python 3.10 and above" + + ```Python hl_lines="88-105" + {!> ../../../docs_src/security/tutorial004_py310.py!} + ``` ## Update the `/token` *path operation* @@ -166,9 +190,17 @@ Create a `timedelta` with the expiration time of the token. Create a real JWT access token and return it. -```Python hl_lines="115-128" -{!../../../docs_src/security/tutorial004.py!} -``` +=== "Python 3.6 and above" + + ```Python hl_lines="115-128" + {!> ../../../docs_src/security/tutorial004.py!} + ``` + +=== "Python 3.10 and above" + + ```Python hl_lines="114-127" + {!> ../../../docs_src/security/tutorial004_py310.py!} + ``` ### Technical details about the JWT "subject" `sub` diff --git a/docs/en/docs/tutorial/security/simple-oauth2.md b/docs/en/docs/tutorial/security/simple-oauth2.md index 2aa74784..505b223b 100644 --- a/docs/en/docs/tutorial/security/simple-oauth2.md +++ b/docs/en/docs/tutorial/security/simple-oauth2.md @@ -49,9 +49,17 @@ Now let's use the utilities provided by **FastAPI** to handle this. First, import `OAuth2PasswordRequestForm`, and use it as a dependency with `Depends` in the *path operation* for `/token`: -```Python hl_lines="4 76" -{!../../../docs_src/security/tutorial003.py!} -``` +=== "Python 3.6 and above" + + ```Python hl_lines="4 76" + {!> ../../../docs_src/security/tutorial003.py!} + ``` + +=== "Python 3.10 and above" + + ```Python hl_lines="2 74" + {!> ../../../docs_src/security/tutorial003_py310.py!} + ``` `OAuth2PasswordRequestForm` is a class dependency that declares a form body with: @@ -90,9 +98,17 @@ If there is no such user, we return an error saying "incorrect username or passw For the error, we use the exception `HTTPException`: -```Python hl_lines="3 77-79" -{!../../../docs_src/security/tutorial003.py!} -``` +=== "Python 3.6 and above" + + ```Python hl_lines="3 77-79" + {!> ../../../docs_src/security/tutorial003.py!} + ``` + +=== "Python 3.10 and above" + + ```Python hl_lines="1 75-77" + {!> ../../../docs_src/security/tutorial003_py310.py!} + ``` ### Check the password @@ -118,9 +134,17 @@ If your database is stolen, the thief won't have your users' plaintext passwords So, the thief won't be able to try to use those same passwords in another system (as many users use the same password everywhere, this would be dangerous). -```Python hl_lines="80-83" -{!../../../docs_src/security/tutorial003.py!} -``` +=== "Python 3.6 and above" + + ```Python hl_lines="80-83" + {!> ../../../docs_src/security/tutorial003.py!} + ``` + +=== "Python 3.10 and above" + + ```Python hl_lines="78-81" + {!> ../../../docs_src/security/tutorial003_py310.py!} + ``` #### About `**user_dict` @@ -156,9 +180,17 @@ For this simple example, we are going to just be completely insecure and return But for now, let's focus on the specific details we need. -```Python hl_lines="85" -{!../../../docs_src/security/tutorial003.py!} -``` +=== "Python 3.6 and above" + + ```Python hl_lines="85" + {!> ../../../docs_src/security/tutorial003.py!} + ``` + +=== "Python 3.10 and above" + + ```Python hl_lines="83" + {!> ../../../docs_src/security/tutorial003_py310.py!} + ``` !!! tip By the spec, you should return a JSON with an `access_token` and a `token_type`, the same as in this example. @@ -181,9 +213,17 @@ Both of these dependencies will just return an HTTP error if the user doesn't ex So, in our endpoint, we will only get a user if the user exists, was correctly authenticated, and is active: -```Python hl_lines="58-67 69-72 90" -{!../../../docs_src/security/tutorial003.py!} -``` +=== "Python 3.6 and above" + + ```Python hl_lines="58-66 69-72 90" + {!> ../../../docs_src/security/tutorial003.py!} + ``` + +=== "Python 3.10 and above" + + ```Python hl_lines="55-64 67-70 88" + {!> ../../../docs_src/security/tutorial003_py310.py!} + ``` !!! info The additional header `WWW-Authenticate` with value `Bearer` we are returning here is also part of the spec. diff --git a/docs/en/docs/tutorial/sql-databases.md b/docs/en/docs/tutorial/sql-databases.md index e8ebb29c..9dc2f64c 100644 --- a/docs/en/docs/tutorial/sql-databases.md +++ b/docs/en/docs/tutorial/sql-databases.md @@ -248,9 +248,23 @@ So, the user will also have a `password` when creating it. But for security, the `password` won't be in other Pydantic *models*, for example, it won't be sent from the API when reading a user. -```Python hl_lines="3 6-8 11-12 23-24 27-28" -{!../../../docs_src/sql_databases/sql_app/schemas.py!} -``` +=== "Python 3.6 and above" + + ```Python hl_lines="3 6-8 11-12 23-24 27-28" + {!> ../../../docs_src/sql_databases/sql_app/schemas.py!} + ``` + +=== "Python 3.9 and above" + + ```Python hl_lines="3 6-8 11-12 23-24 27-28" + {!> ../../../docs_src/sql_databases/sql_app_py39/schemas.py!} + ``` + +=== "Python 3.10 and above" + + ```Python hl_lines="1 4-6 9-10 21-22 25-26" + {!> ../../../docs_src/sql_databases/sql_app_py310/schemas.py!} + ``` #### SQLAlchemy style and Pydantic style @@ -278,9 +292,23 @@ The same way, when reading a user, we can now declare that `items` will contain Not only the IDs of those items, but all the data that we defined in the Pydantic *model* for reading items: `Item`. -```Python hl_lines="15-17 31-34" -{!../../../docs_src/sql_databases/sql_app/schemas.py!} -``` +=== "Python 3.6 and above" + + ```Python hl_lines="15-17 31-34" + {!> ../../../docs_src/sql_databases/sql_app/schemas.py!} + ``` + +=== "Python 3.9 and above" + + ```Python hl_lines="15-17 31-34" + {!> ../../../docs_src/sql_databases/sql_app_py39/schemas.py!} + ``` + +=== "Python 3.10 and above" + + ```Python hl_lines="13-15 29-32" + {!> ../../../docs_src/sql_databases/sql_app_py310/schemas.py!} + ``` !!! tip Notice that the `User`, the Pydantic *model* that will be used when reading a user (returning it from the API) doesn't include the `password`. @@ -293,9 +321,23 @@ This ../../../docs_src/sql_databases/sql_app/schemas.py!} + ``` + +=== "Python 3.9 and above" + + ```Python hl_lines="15 19-20 31 36-37" + {!> ../../../docs_src/sql_databases/sql_app_py39/schemas.py!} + ``` + +=== "Python 3.10 and above" + + ```Python hl_lines="13 17-18 29 34-35" + {!> ../../../docs_src/sql_databases/sql_app_py310/schemas.py!} + ``` !!! tip Notice it's assigning a value with `=`, like: @@ -425,9 +467,17 @@ And now in the file `sql_app/main.py` let's integrate and use all the other part In a very simplistic way create the database tables: -```Python hl_lines="9" -{!../../../docs_src/sql_databases/sql_app/main.py!} -``` +=== "Python 3.6 and above" + + ```Python hl_lines="9" + {!> ../../../docs_src/sql_databases/sql_app/main.py!} + ``` + +=== "Python 3.9 and above" + + ```Python hl_lines="7" + {!> ../../../docs_src/sql_databases/sql_app_py39/main.py!} + ``` #### Alembic Note @@ -451,9 +501,17 @@ For that, we will create a new dependency with `yield`, as explained before in t Our dependency will create a new SQLAlchemy `SessionLocal` that will be used in a single request, and then close it once the request is finished. -```Python hl_lines="15-20" -{!../../../docs_src/sql_databases/sql_app/main.py!} -``` +=== "Python 3.6 and above" + + ```Python hl_lines="15-20" + {!> ../../../docs_src/sql_databases/sql_app/main.py!} + ``` + +=== "Python 3.9 and above" + + ```Python hl_lines="13-18" + {!> ../../../docs_src/sql_databases/sql_app_py39/main.py!} + ``` !!! info We put the creation of the `SessionLocal()` and handling of the requests in a `try` block. @@ -468,9 +526,17 @@ And then, when using the dependency in a *path operation function*, we declare i This will then give us better editor support inside the *path operation function*, because the editor will know that the `db` parameter is of type `Session`: -```Python hl_lines="24 32 38 47 53" -{!../../../docs_src/sql_databases/sql_app/main.py!} -``` +=== "Python 3.6 and above" + + ```Python hl_lines="24 32 38 47 53" + {!> ../../../docs_src/sql_databases/sql_app/main.py!} + ``` + +=== "Python 3.9 and above" + + ```Python hl_lines="22 30 36 45 51" + {!> ../../../docs_src/sql_databases/sql_app_py39/main.py!} + ``` !!! info "Technical Details" The parameter `db` is actually of type `SessionLocal`, but this class (created with `sessionmaker()`) is a "proxy" of a SQLAlchemy `Session`, so, the editor doesn't really know what methods are provided. @@ -481,9 +547,17 @@ This will then give us better editor support inside the *path operation function Now, finally, here's the standard **FastAPI** *path operations* code. -```Python hl_lines="23-28 31-34 37-42 45-49 52-55" -{!../../../docs_src/sql_databases/sql_app/main.py!} -``` +=== "Python 3.6 and above" + + ```Python hl_lines="23-28 31-34 37-42 45-49 52-55" + {!> ../../../docs_src/sql_databases/sql_app/main.py!} + ``` + +=== "Python 3.9 and above" + + ```Python hl_lines="21-26 29-32 35-40 43-47 50-53" + {!> ../../../docs_src/sql_databases/sql_app_py39/main.py!} + ``` We are creating the database session before each request in the dependency with `yield`, and then closing it afterwards. @@ -566,9 +640,23 @@ For example, in a background task worker with ../../../docs_src/sql_databases/sql_app/schemas.py!} + ``` + +=== "Python 3.9 and above" + + ```Python + {!> ../../../docs_src/sql_databases/sql_app_py39/schemas.py!} + ``` + +=== "Python 3.10 and above" + + ```Python + {!> ../../../docs_src/sql_databases/sql_app_py310/schemas.py!} + ``` * `sql_app/crud.py`: @@ -578,9 +666,17 @@ For example, in a background task worker with ../../../docs_src/sql_databases/sql_app/main.py!} + ``` + +=== "Python 3.9 and above" + + ```Python + {!> ../../../docs_src/sql_databases/sql_app_py39/main.py!} + ``` ## Check it @@ -629,9 +725,17 @@ A "middleware" is basically a function that is always executed for each request, The middleware we'll add (just a function) will create a new SQLAlchemy `SessionLocal` for each request, add it to the request and then close it once the request is finished. -```Python hl_lines="14-22" -{!../../../docs_src/sql_databases/sql_app/alt_main.py!} -``` +=== "Python 3.6 and above" + + ```Python hl_lines="14-22" + {!> ../../../docs_src/sql_databases/sql_app/alt_main.py!} + ``` + +=== "Python 3.9 and above" + + ```Python hl_lines="12-20" + {!> ../../../docs_src/sql_databases/sql_app_py39/alt_main.py!} + ``` !!! info We put the creation of the `SessionLocal()` and handling of the requests in a `try` block. diff --git a/docs/en/docs/tutorial/testing.md b/docs/en/docs/tutorial/testing.md index 9c9a8270..7e2ae84d 100644 --- a/docs/en/docs/tutorial/testing.md +++ b/docs/en/docs/tutorial/testing.md @@ -65,7 +65,7 @@ Now let's extend this example and add more details to see how to test different ### Extended **FastAPI** app file -Let's say you have a file `main_b.py` with your **FastAPI** app. +Let's say that now the file `main.py` with your **FastAPI** app has some other **path operations**. It has a `GET` operation that could return an error. @@ -73,16 +73,24 @@ It has a `POST` operation that could return several errors. Both *path operations* require an `X-Token` header. -```Python -{!../../../docs_src/app_testing/main_b.py!} -``` +=== "Python 3.6 and above" + + ```Python + {!> ../../../docs_src/app_testing/app_b/main.py!} + ``` + +=== "Python 3.10 and above" + + ```Python + {!> ../../../docs_src/app_testing/app_b_py310/main.py!} + ``` ### Extended testing file -You could then have a `test_main_b.py`, the same as before, with the extended tests: +You could then update `test_main.py` with the extended tests: ```Python -{!../../../docs_src/app_testing/test_main_b.py!} +{!> ../../../docs_src/app_testing/app_b/test_main.py!} ``` Whenever you need the client to pass information in the request and you don't know how to, you can search (Google) how to do it in `requests`. diff --git a/docs/en/mkdocs.yml b/docs/en/mkdocs.yml index 5bdd2c54..b3716f50 100644 --- a/docs/en/mkdocs.yml +++ b/docs/en/mkdocs.yml @@ -20,7 +20,6 @@ theme: features: - search.suggest - search.highlight - - content.tabs.link icon: repo: fontawesome/brands/github-alt logo: img/icon-white.svg diff --git a/docs/es/mkdocs.yml b/docs/es/mkdocs.yml index a4bc4115..06b015a1 100644 --- a/docs/es/mkdocs.yml +++ b/docs/es/mkdocs.yml @@ -20,7 +20,6 @@ theme: features: - search.suggest - search.highlight - - content.tabs.link icon: repo: fontawesome/brands/github-alt logo: https://fastapi.tiangolo.com/img/icon-white.svg diff --git a/docs/fr/mkdocs.yml b/docs/fr/mkdocs.yml index ff16e1d7..c42f92b8 100644 --- a/docs/fr/mkdocs.yml +++ b/docs/fr/mkdocs.yml @@ -20,7 +20,6 @@ theme: features: - search.suggest - search.highlight - - content.tabs.link icon: repo: fontawesome/brands/github-alt logo: https://fastapi.tiangolo.com/img/icon-white.svg diff --git a/docs/id/mkdocs.yml b/docs/id/mkdocs.yml index d70d2b3c..0dc34a14 100644 --- a/docs/id/mkdocs.yml +++ b/docs/id/mkdocs.yml @@ -20,7 +20,6 @@ theme: features: - search.suggest - search.highlight - - content.tabs.link icon: repo: fontawesome/brands/github-alt logo: https://fastapi.tiangolo.com/img/icon-white.svg diff --git a/docs/it/mkdocs.yml b/docs/it/mkdocs.yml index e6d01fbd..18afbd54 100644 --- a/docs/it/mkdocs.yml +++ b/docs/it/mkdocs.yml @@ -20,7 +20,6 @@ theme: features: - search.suggest - search.highlight - - content.tabs.link icon: repo: fontawesome/brands/github-alt logo: https://fastapi.tiangolo.com/img/icon-white.svg diff --git a/docs/ja/mkdocs.yml b/docs/ja/mkdocs.yml index 39fd8a21..327fe862 100644 --- a/docs/ja/mkdocs.yml +++ b/docs/ja/mkdocs.yml @@ -20,7 +20,6 @@ theme: features: - search.suggest - search.highlight - - content.tabs.link icon: repo: fontawesome/brands/github-alt logo: https://fastapi.tiangolo.com/img/icon-white.svg diff --git a/docs/ko/mkdocs.yml b/docs/ko/mkdocs.yml index 1d4d3091..5ffb7187 100644 --- a/docs/ko/mkdocs.yml +++ b/docs/ko/mkdocs.yml @@ -20,7 +20,6 @@ theme: features: - search.suggest - search.highlight - - content.tabs.link icon: repo: fontawesome/brands/github-alt logo: https://fastapi.tiangolo.com/img/icon-white.svg diff --git a/docs/pl/mkdocs.yml b/docs/pl/mkdocs.yml index 3c1351a1..724dc272 100644 --- a/docs/pl/mkdocs.yml +++ b/docs/pl/mkdocs.yml @@ -20,7 +20,6 @@ theme: features: - search.suggest - search.highlight - - content.tabs.link icon: repo: fontawesome/brands/github-alt logo: https://fastapi.tiangolo.com/img/icon-white.svg diff --git a/docs/pt/mkdocs.yml b/docs/pt/mkdocs.yml index f202f306..f36ecf49 100644 --- a/docs/pt/mkdocs.yml +++ b/docs/pt/mkdocs.yml @@ -20,7 +20,6 @@ theme: features: - search.suggest - search.highlight - - content.tabs.link icon: repo: fontawesome/brands/github-alt logo: https://fastapi.tiangolo.com/img/icon-white.svg diff --git a/docs/ru/mkdocs.yml b/docs/ru/mkdocs.yml index 6e17c287..eb2597de 100644 --- a/docs/ru/mkdocs.yml +++ b/docs/ru/mkdocs.yml @@ -20,7 +20,6 @@ theme: features: - search.suggest - search.highlight - - content.tabs.link icon: repo: fontawesome/brands/github-alt logo: https://fastapi.tiangolo.com/img/icon-white.svg diff --git a/docs/sq/mkdocs.yml b/docs/sq/mkdocs.yml index d9c3dad4..5afe0a66 100644 --- a/docs/sq/mkdocs.yml +++ b/docs/sq/mkdocs.yml @@ -20,7 +20,6 @@ theme: features: - search.suggest - search.highlight - - content.tabs.link icon: repo: fontawesome/brands/github-alt logo: https://fastapi.tiangolo.com/img/icon-white.svg diff --git a/docs/tr/mkdocs.yml b/docs/tr/mkdocs.yml index f6ed7f5b..40ba6d52 100644 --- a/docs/tr/mkdocs.yml +++ b/docs/tr/mkdocs.yml @@ -20,7 +20,6 @@ theme: features: - search.suggest - search.highlight - - content.tabs.link icon: repo: fontawesome/brands/github-alt logo: https://fastapi.tiangolo.com/img/icon-white.svg diff --git a/docs/uk/mkdocs.yml b/docs/uk/mkdocs.yml index d0de8cc0..a116d90a 100644 --- a/docs/uk/mkdocs.yml +++ b/docs/uk/mkdocs.yml @@ -20,7 +20,6 @@ theme: features: - search.suggest - search.highlight - - content.tabs.link icon: repo: fontawesome/brands/github-alt logo: https://fastapi.tiangolo.com/img/icon-white.svg diff --git a/docs/zh/mkdocs.yml b/docs/zh/mkdocs.yml index a929e338..33d40129 100644 --- a/docs/zh/mkdocs.yml +++ b/docs/zh/mkdocs.yml @@ -20,7 +20,6 @@ theme: features: - search.suggest - search.highlight - - content.tabs.link icon: repo: fontawesome/brands/github-alt logo: https://fastapi.tiangolo.com/img/icon-white.svg 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/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/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/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/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/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/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.py b/docs_src/python_types/tutorial008.py index 9fb1043b..a393385b 100644 --- a/docs_src/python_types/tutorial008.py +++ b/docs_src/python_types/tutorial008.py @@ -1,7 +1,4 @@ -from typing import Dict - - -def process_items(prices: Dict[str, float]): +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/request_files/tutorial002_py39.py b/docs_src/request_files/tutorial002_py39.py new file mode 100644 index 00000000..26cd5676 --- /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] = File(...)): + 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/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/pyproject.toml b/pyproject.toml index e6148831..fa399c49 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,7 @@ classifiers = [ "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", ] 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_body/test_tutorial001_py310.py b/tests/test_tutorial/test_body/test_tutorial001_py310.py new file mode 100644 index 00000000..e292b534 --- /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": {"type": "string"}, + }, + "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_py310.py b/tests/test_tutorial/test_body_fields/test_tutorial001_py310.py new file mode 100644 index 00000000..d7a525ea --- /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": {"type": "string"}, + }, + "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_py310.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial001_py310.py new file mode 100644 index 00000000..85ba41ce --- /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": {"type": "string"}, + }, + "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_py310.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003_py310.py new file mode 100644 index 00000000..f896f7bf --- /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": {"type": "string"}, + }, + "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_py39.py b/tests/test_tutorial/test_body_nested_models/test_tutorial009_py39.py new file mode 100644 index 00000000..17ca29ce --- /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": {"type": "string"}, + }, + "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_py310.py b/tests/test_tutorial/test_body_updates/test_tutorial001_py310.py new file mode 100644 index 00000000..ca1d8c58 --- /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": {"type": "string"}, + }, + "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..f2b184c4 --- /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": {"type": "string"}, + }, + "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_cookie_params/test_tutorial001_py310.py b/tests/test_tutorial/test_cookie_params/test_tutorial001_py310.py new file mode 100644 index 00000000..587a328d --- /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": {"type": "string"}, + }, + "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_dependencies/test_tutorial001_py310.py b/tests/test_tutorial/test_dependencies/test_tutorial001_py310.py new file mode 100644 index 00000000..a7991170 --- /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": {"type": "string"}, + }, + "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_py310.py b/tests/test_tutorial/test_dependencies/test_tutorial004_py310.py new file mode 100644 index 00000000..f66a36a9 --- /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": {"type": "string"}, + }, + "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_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..3d4c1d07 --- /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": {"type": "string"}, + }, + "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_py310.py b/tests/test_tutorial/test_extra_models/test_tutorial003_py310.py new file mode 100644 index 00000000..185bc3a3 --- /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": {"type": "string"}, + }, + "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_header_params/test_tutorial001_py310.py b/tests/test_tutorial/test_header_params/test_tutorial001_py310.py new file mode 100644 index 00000000..f5ee1742 --- /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": {"type": "string"}, + }, + "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_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..1f617da7 --- /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": {"type": "string"}, + }, + "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..ffdf0508 --- /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": {"type": "string"}, + }, + "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_query_params/test_tutorial006_py310.py b/tests/test_tutorial/test_query_params/test_tutorial006_py310.py new file mode 100644 index 00000000..1986d27d --- /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": {"type": "string"}, + }, + "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_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial001_py310.py new file mode 100644 index 00000000..66b24017 --- /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": {"type": "string"}, + }, + "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_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py310.py new file mode 100644 index 00000000..8894ee1b --- /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": {"type": "string"}, + }, + "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..b10e70af --- /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": {"type": "string"}, + }, + "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_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_py39.py new file mode 100644 index 00000000..a9cbce02 --- /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": {"type": "string"}, + }, + "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_request_files/test_tutorial002_py39.py b/tests/test_tutorial/test_request_files/test_tutorial002_py39.py new file mode 100644 index 00000000..bbdf25cd --- /dev/null +++ b/tests/test_tutorial/test_request_files/test_tutorial002_py39.py @@ -0,0 +1,235 @@ +import pytest +from fastapi import FastAPI +from fastapi.testclient import TestClient + +from ...utils import needs_py39 + +openapi_schema = { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/files/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create Files", + "operationId": "create_files_files__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_create_files_files__post" + } + } + }, + "required": True, + }, + } + }, + "/uploadfiles/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create Upload Files", + "operationId": "create_upload_files_uploadfiles__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_create_upload_files_uploadfiles__post" + } + } + }, + "required": True, + }, + } + }, + "/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Main", + "operationId": "main__get", + } + }, + }, + "components": { + "schemas": { + "Body_create_upload_files_uploadfiles__post": { + "title": "Body_create_upload_files_uploadfiles__post", + "required": ["files"], + "type": "object", + "properties": { + "files": { + "title": "Files", + "type": "array", + "items": {"type": "string", "format": "binary"}, + } + }, + }, + "Body_create_files_files__post": { + "title": "Body_create_files_files__post", + "required": ["files"], + "type": "object", + "properties": { + "files": { + "title": "Files", + "type": "array", + "items": {"type": "string", "format": "binary"}, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": {"type": "string"}, + }, + "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="app") +def get_app(): + from docs_src.request_files.tutorial002_py39 import app + + return app + + +@pytest.fixture(name="client") +def get_client(app: FastAPI): + + 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 + + +file_required = { + "detail": [ + { + "loc": ["body", "files"], + "msg": "field required", + "type": "value_error.missing", + } + ] +} + + +@needs_py39 +def test_post_form_no_body(client: TestClient): + response = client.post("/files/") + assert response.status_code == 422, response.text + assert response.json() == file_required + + +@needs_py39 +def test_post_body_json(client: TestClient): + response = client.post("/files/", json={"file": "Foo"}) + assert response.status_code == 422, response.text + assert response.json() == file_required + + +@needs_py39 +def test_post_files(tmp_path, app: FastAPI): + path = tmp_path / "test.txt" + path.write_bytes(b"") + path2 = tmp_path / "test2.txt" + path2.write_bytes(b"") + + client = TestClient(app) + with path.open("rb") as file, path2.open("rb") as file2: + response = client.post( + "/files/", + files=( + ("files", ("test.txt", file)), + ("files", ("test2.txt", file2)), + ), + ) + assert response.status_code == 200, response.text + assert response.json() == {"file_sizes": [14, 15]} + + +@needs_py39 +def test_post_upload_file(tmp_path, app: FastAPI): + path = tmp_path / "test.txt" + path.write_bytes(b"") + path2 = tmp_path / "test2.txt" + path2.write_bytes(b"") + + client = TestClient(app) + with path.open("rb") as file, path2.open("rb") as file2: + response = client.post( + "/uploadfiles/", + files=( + ("files", ("test.txt", file)), + ("files", ("test2.txt", file2)), + ), + ) + assert response.status_code == 200, response.text + assert response.json() == {"filenames": ["test.txt", "test2.txt"]} + + +@needs_py39 +def test_get_root(app: FastAPI): + client = TestClient(app) + response = client.get("/") + assert response.status_code == 200, response.text + assert b" Date: Fri, 7 Jan 2022 14:12:16 +0000 Subject: [PATCH 154/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index ce1d3144..f6e66d80 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ✨ 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). * ⬆ Upgrade MkDocs Material and configs. PR [#4385](https://github.com/tiangolo/fastapi/pull/4385) by [@tiangolo](https://github.com/tiangolo). * ⬆️ Upgrade Starlette to 0.17.1. PR [#4145](https://github.com/tiangolo/fastapi/pull/4145) by [@simondale00](https://github.com/simondale00). ## 0.70.1 From a1ede32f29366902033e701f09bfe77c29d0442e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 7 Jan 2022 15:17:13 +0100 Subject: [PATCH 155/196] =?UTF-8?q?=F0=9F=94=A7=20Add=20FastAPI=20Trove=20?= =?UTF-8?q?Classifier=20for=20PyPI=20(#4386)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index fa399c49..77c01322 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,7 @@ 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", From 4da33e303148cbabeb96bb3e17cd236ef4402d33 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 7 Jan 2022 14:17:49 +0000 Subject: [PATCH 156/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index f6e66d80..9453909b 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🔧 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). * ✨ 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). * ⬆ Upgrade MkDocs Material and configs. PR [#4385](https://github.com/tiangolo/fastapi/pull/4385) by [@tiangolo](https://github.com/tiangolo). * ⬆️ Upgrade Starlette to 0.17.1. PR [#4145](https://github.com/tiangolo/fastapi/pull/4145) by [@simondale00](https://github.com/simondale00). From 44f4885c663f498c5902ba1a951d6e0e3d318fa1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 7 Jan 2022 15:18:45 +0100 Subject: [PATCH 157/196] =?UTF-8?q?=F0=9F=91=A5=20Update=20FastAPI=20Peopl?= =?UTF-8?q?e=20(#4354)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions --- docs/en/data/people.yml | 98 ++++++++++++++++++++++------------------- 1 file changed, 53 insertions(+), 45 deletions(-) diff --git a/docs/en/data/people.yml b/docs/en/data/people.yml index 89c6f8dc..df088f39 100644 --- a/docs/en/data/people.yml +++ b/docs/en/data/people.yml @@ -6,8 +6,8 @@ maintainers: url: https://github.com/tiangolo experts: - login: Kludex - count: 315 - avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=cf8455cb899806b774a3a71073f88583adec99f6&v=4 + count: 316 + avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=3682d9b9b93bef272f379ab623dc031c8d71432e&v=4 url: https://github.com/Kludex - login: dmontagu count: 262 @@ -34,7 +34,7 @@ experts: avatarUrl: https://avatars.githubusercontent.com/u/31127044?u=81a84af39c89b898b0fbc5a04e8834f60f23e55a&v=4 url: https://github.com/ArcLightSlavik - login: raphaelauv - count: 63 + count: 67 avatarUrl: https://avatars.githubusercontent.com/u/10202690?u=e6f86f5c0c3026a15d6b51792fa3e532b12f1371&v=4 url: https://github.com/raphaelauv - login: falkben @@ -45,6 +45,10 @@ experts: 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: 42 avatarUrl: https://avatars.githubusercontent.com/u/27180793?u=5cf2877f50b3eb2bc55086089a78a36f07042889&v=4 @@ -53,10 +57,6 @@ experts: count: 38 avatarUrl: https://avatars.githubusercontent.com/u/11836741?u=8bd5ef7e62fe6a82055e33c4c0e0a7879ff8cfb6&v=4 url: https://github.com/includeamin -- login: insomnes - count: 37 - avatarUrl: https://avatars.githubusercontent.com/u/16958893?u=f8be7088d5076d963984a21f95f44e559192d912&v=4 - url: https://github.com/insomnes - login: prostomarkeloff count: 33 avatarUrl: https://avatars.githubusercontent.com/u/28061158?u=72309cc1f2e04e40fa38b29969cb4e9d3f722e7b&v=4 @@ -78,7 +78,7 @@ experts: avatarUrl: https://avatars.githubusercontent.com/u/1144727?u=85c025e3fcc7bd79a5665c63ee87cdf8aae13374&v=4 url: https://github.com/frankie567 - login: adriangb - count: 26 + count: 28 avatarUrl: https://avatars.githubusercontent.com/u/1755071?u=81f0262df34e1460ca546fbd0c211169c2478532&v=4 url: https://github.com/adriangb - login: ghandic @@ -97,6 +97,10 @@ experts: count: 24 avatarUrl: https://avatars.githubusercontent.com/u/9435877?u=719327b7d2c4c62212456d771bfa7c6b8dbb9eac&v=4 url: https://github.com/SirTelemak +- login: panla + count: 23 + avatarUrl: https://avatars.githubusercontent.com/u/41326348?u=ba2fda6b30110411ecbf406d187907e2b420ac19&v=4 + url: https://github.com/panla - login: acnebs count: 22 avatarUrl: https://avatars.githubusercontent.com/u/9054108?u=c27e50269f1ef8ea950cc6f0268c8ec5cebbe9c9&v=4 @@ -109,10 +113,6 @@ experts: count: 21 avatarUrl: https://avatars.githubusercontent.com/u/565544?v=4 url: https://github.com/chris-allnutt -- login: panla - count: 21 - avatarUrl: https://avatars.githubusercontent.com/u/41326348?u=ba2fda6b30110411ecbf406d187907e2b420ac19&v=4 - url: https://github.com/panla - login: retnikt count: 19 avatarUrl: https://avatars.githubusercontent.com/u/24581770?v=4 @@ -137,14 +137,14 @@ experts: count: 15 avatarUrl: https://avatars.githubusercontent.com/u/685002?u=b5094ab4527fc84b006c0ac9ff54367bdebb2267&v=4 url: https://github.com/acidjunk +- login: dstlny + count: 14 + 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: dstlny - count: 13 - avatarUrl: https://avatars.githubusercontent.com/u/41964673?u=9f2174f9d61c15c6e3a4c9e3aeee66f711ce311f&v=4 - url: https://github.com/dstlny - login: David-Lor count: 12 avatarUrl: https://avatars.githubusercontent.com/u/17401854?u=474680c02b94cba810cb9032fb7eb787d9cc9d22&v=4 @@ -182,22 +182,30 @@ experts: avatarUrl: https://avatars.githubusercontent.com/u/2858306?u=1bb1182a5944e93624b7fb26585f22c8f7a9d76e&v=4 url: https://github.com/oligond last_month_active: +- login: insomnes + count: 10 + avatarUrl: https://avatars.githubusercontent.com/u/16958893?u=f8be7088d5076d963984a21f95f44e559192d912&v=4 + url: https://github.com/insomnes - login: raphaelauv count: 6 avatarUrl: https://avatars.githubusercontent.com/u/10202690?u=e6f86f5c0c3026a15d6b51792fa3e532b12f1371&v=4 url: https://github.com/raphaelauv -- login: oligond - count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/2858306?u=1bb1182a5944e93624b7fb26585f22c8f7a9d76e&v=4 - url: https://github.com/oligond +- login: jgould22 + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4 + url: https://github.com/jgould22 +- login: harunyasar + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/1765494?u=5b1ab7c582db4b4016fa31affe977d10af108ad4&v=4 + url: https://github.com/harunyasar - login: Kludex - count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=cf8455cb899806b774a3a71073f88583adec99f6&v=4 - url: https://github.com/Kludex -- login: Dustyposa count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/27180793?u=5cf2877f50b3eb2bc55086089a78a36f07042889&v=4 - url: https://github.com/Dustyposa + avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=3682d9b9b93bef272f379ab623dc031c8d71432e&v=4 + url: https://github.com/Kludex +- login: panla + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/41326348?u=ba2fda6b30110411ecbf406d187907e2b420ac19&v=4 + url: https://github.com/panla top_contributors: - login: waynerv count: 25 @@ -237,7 +245,7 @@ top_contributors: url: https://github.com/RunningIkkyu - login: Kludex count: 7 - avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=cf8455cb899806b774a3a71073f88583adec99f6&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=3682d9b9b93bef272f379ab623dc031c8d71432e&v=4 url: https://github.com/Kludex - login: hard-coders count: 7 @@ -273,12 +281,12 @@ top_contributors: url: https://github.com/komtaki - login: NinaHwang count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/79563565?u=36ca24be1fc3daa967b0f409bab0e17132d8c80c&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/79563565?u=1741703bd6c8f491503354b363a86e879b4c1cab&v=4 url: https://github.com/NinaHwang top_reviewers: - login: Kludex count: 91 - avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=cf8455cb899806b774a3a71073f88583adec99f6&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=3682d9b9b93bef272f379ab623dc031c8d71432e&v=4 url: https://github.com/Kludex - login: waynerv count: 47 @@ -364,6 +372,10 @@ top_reviewers: count: 12 avatarUrl: https://avatars.githubusercontent.com/u/6478810?u=af15d724875cec682ed8088a86d36b2798f981c0&v=4 url: https://github.com/sh0nk +- login: yezz123 + count: 11 + avatarUrl: https://avatars.githubusercontent.com/u/52716203?u=636b4f79645176df4527dd45c12d5dbb5a4193cf&v=4 + url: https://github.com/yezz123 - login: mariacamilagl count: 10 avatarUrl: https://avatars.githubusercontent.com/u/11489395?u=4adb6986bf3debfc2b8216ae701f2bd47d73da7d&v=4 @@ -376,10 +388,6 @@ top_reviewers: count: 10 avatarUrl: https://avatars.githubusercontent.com/u/7887703?v=4 url: https://github.com/maoyibo -- login: yezz123 - count: 9 - avatarUrl: https://avatars.githubusercontent.com/u/52716203?u=636b4f79645176df4527dd45c12d5dbb5a4193cf&v=4 - url: https://github.com/yezz123 - login: graingert count: 9 avatarUrl: https://avatars.githubusercontent.com/u/413772?v=4 @@ -420,6 +428,10 @@ top_reviewers: count: 7 avatarUrl: https://avatars.githubusercontent.com/u/10202690?u=e6f86f5c0c3026a15d6b51792fa3e532b12f1371&v=4 url: https://github.com/raphaelauv +- login: BilalAlpaslan + count: 7 + avatarUrl: https://avatars.githubusercontent.com/u/47563997?u=63ed66e304fe8d765762c70587d61d9196e5c82d&v=4 + url: https://github.com/BilalAlpaslan - login: NastasiaSaby count: 7 avatarUrl: https://avatars.githubusercontent.com/u/8245071?u=b3afd005f9e4bf080c219ef61a592b3a8004b764&v=4 @@ -436,10 +448,6 @@ top_reviewers: count: 6 avatarUrl: https://avatars.githubusercontent.com/u/21287303?u=b049eac3e51a4c0473c2efe66b4d28a7d8f2b572&v=4 url: https://github.com/jovicon -- login: BilalAlpaslan - count: 6 - avatarUrl: https://avatars.githubusercontent.com/u/47563997?u=63ed66e304fe8d765762c70587d61d9196e5c82d&v=4 - url: https://github.com/BilalAlpaslan - login: diogoduartec count: 5 avatarUrl: https://avatars.githubusercontent.com/u/31852339?u=b50fc11c531e9b77922e19edfc9e7233d4d7b92e&v=4 @@ -448,6 +456,10 @@ top_reviewers: count: 5 avatarUrl: https://avatars.githubusercontent.com/u/49960770?u=e39b11d47188744ee07b2a1c7ce1a1bdf3c80760&v=4 url: https://github.com/nimctl +- login: israteneda + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/20668624?u=d7b2961d330aca65fbce5bdb26a0800a3d23ed2d&v=4 + url: https://github.com/israteneda - login: juntatalor count: 5 avatarUrl: https://avatars.githubusercontent.com/u/8134632?v=4 @@ -464,11 +476,7 @@ top_reviewers: count: 5 avatarUrl: https://avatars.githubusercontent.com/u/83456692?v=4 url: https://github.com/oandersonmagalhaes -- 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: qysfblog + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/52229895?v=4 + url: https://github.com/qysfblog From 66c27c3e0752b63bfe6e843dcf2af28d774caaa7 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 7 Jan 2022 14:19:23 +0000 Subject: [PATCH 158/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 9453909b..02e2c887 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 👥 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). * ✨ 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). * ⬆ Upgrade MkDocs Material and configs. PR [#4385](https://github.com/tiangolo/fastapi/pull/4385) by [@tiangolo](https://github.com/tiangolo). From 672c55ac626b20cc6219696023cdcc5871e5141d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 7 Jan 2022 18:07:59 +0100 Subject: [PATCH 159/196] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.71?= =?UTF-8?q?.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 14 ++++++++++++-- fastapi/__init__.py | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 02e2c887..01df3126 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,11 +2,21 @@ ## Latest Changes +## 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). -* ✨ 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). * ⬆ Upgrade MkDocs Material and configs. PR [#4385](https://github.com/tiangolo/fastapi/pull/4385) by [@tiangolo](https://github.com/tiangolo). -* ⬆️ Upgrade Starlette to 0.17.1. PR [#4145](https://github.com/tiangolo/fastapi/pull/4145) by [@simondale00](https://github.com/simondale00). + ## 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. 🤩 diff --git a/fastapi/__init__.py b/fastapi/__init__.py index 8bb6ce15..5b735aed 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.70.1" +__version__ = "0.71.0" from starlette import status as status From ca2b1dbb648b24ba776cf73f4dfa7026d5ff4a0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 16 Jan 2022 15:34:45 +0100 Subject: [PATCH 160/196] =?UTF-8?q?=F0=9F=94=A7=20Enable=20MkDocs=20Materi?= =?UTF-8?q?al=20Insiders'=20`content.tabs.link`=20(#4399)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/az/mkdocs.yml | 1 + docs/de/mkdocs.yml | 1 + docs/en/mkdocs.yml | 1 + docs/es/mkdocs.yml | 1 + docs/fr/mkdocs.yml | 1 + docs/id/mkdocs.yml | 1 + docs/it/mkdocs.yml | 1 + docs/ja/mkdocs.yml | 1 + docs/ko/mkdocs.yml | 1 + docs/pl/mkdocs.yml | 1 + docs/pt/mkdocs.yml | 1 + docs/ru/mkdocs.yml | 1 + docs/sq/mkdocs.yml | 1 + docs/tr/mkdocs.yml | 1 + docs/uk/mkdocs.yml | 1 + docs/zh/mkdocs.yml | 1 + 16 files changed, 16 insertions(+) diff --git a/docs/az/mkdocs.yml b/docs/az/mkdocs.yml index dbff7b26..66220f63 100644 --- a/docs/az/mkdocs.yml +++ b/docs/az/mkdocs.yml @@ -20,6 +20,7 @@ theme: features: - search.suggest - search.highlight + - content.tabs.link icon: repo: fontawesome/brands/github-alt logo: https://fastapi.tiangolo.com/img/icon-white.svg diff --git a/docs/de/mkdocs.yml b/docs/de/mkdocs.yml index 8f29ef31..360fa8c4 100644 --- a/docs/de/mkdocs.yml +++ b/docs/de/mkdocs.yml @@ -20,6 +20,7 @@ theme: features: - search.suggest - search.highlight + - content.tabs.link icon: repo: fontawesome/brands/github-alt logo: https://fastapi.tiangolo.com/img/icon-white.svg diff --git a/docs/en/mkdocs.yml b/docs/en/mkdocs.yml index b3716f50..5bdd2c54 100644 --- a/docs/en/mkdocs.yml +++ b/docs/en/mkdocs.yml @@ -20,6 +20,7 @@ theme: features: - search.suggest - search.highlight + - content.tabs.link icon: repo: fontawesome/brands/github-alt logo: img/icon-white.svg diff --git a/docs/es/mkdocs.yml b/docs/es/mkdocs.yml index 06b015a1..a4bc4115 100644 --- a/docs/es/mkdocs.yml +++ b/docs/es/mkdocs.yml @@ -20,6 +20,7 @@ theme: features: - search.suggest - search.highlight + - content.tabs.link icon: repo: fontawesome/brands/github-alt logo: https://fastapi.tiangolo.com/img/icon-white.svg diff --git a/docs/fr/mkdocs.yml b/docs/fr/mkdocs.yml index c42f92b8..ff16e1d7 100644 --- a/docs/fr/mkdocs.yml +++ b/docs/fr/mkdocs.yml @@ -20,6 +20,7 @@ theme: features: - search.suggest - search.highlight + - content.tabs.link icon: repo: fontawesome/brands/github-alt logo: https://fastapi.tiangolo.com/img/icon-white.svg diff --git a/docs/id/mkdocs.yml b/docs/id/mkdocs.yml index 0dc34a14..d70d2b3c 100644 --- a/docs/id/mkdocs.yml +++ b/docs/id/mkdocs.yml @@ -20,6 +20,7 @@ theme: features: - search.suggest - search.highlight + - content.tabs.link icon: repo: fontawesome/brands/github-alt logo: https://fastapi.tiangolo.com/img/icon-white.svg diff --git a/docs/it/mkdocs.yml b/docs/it/mkdocs.yml index 18afbd54..e6d01fbd 100644 --- a/docs/it/mkdocs.yml +++ b/docs/it/mkdocs.yml @@ -20,6 +20,7 @@ theme: features: - search.suggest - search.highlight + - content.tabs.link icon: repo: fontawesome/brands/github-alt logo: https://fastapi.tiangolo.com/img/icon-white.svg diff --git a/docs/ja/mkdocs.yml b/docs/ja/mkdocs.yml index 327fe862..39fd8a21 100644 --- a/docs/ja/mkdocs.yml +++ b/docs/ja/mkdocs.yml @@ -20,6 +20,7 @@ theme: features: - search.suggest - search.highlight + - content.tabs.link icon: repo: fontawesome/brands/github-alt logo: https://fastapi.tiangolo.com/img/icon-white.svg diff --git a/docs/ko/mkdocs.yml b/docs/ko/mkdocs.yml index 5ffb7187..1d4d3091 100644 --- a/docs/ko/mkdocs.yml +++ b/docs/ko/mkdocs.yml @@ -20,6 +20,7 @@ theme: features: - search.suggest - search.highlight + - content.tabs.link icon: repo: fontawesome/brands/github-alt logo: https://fastapi.tiangolo.com/img/icon-white.svg diff --git a/docs/pl/mkdocs.yml b/docs/pl/mkdocs.yml index 724dc272..3c1351a1 100644 --- a/docs/pl/mkdocs.yml +++ b/docs/pl/mkdocs.yml @@ -20,6 +20,7 @@ theme: features: - search.suggest - search.highlight + - content.tabs.link icon: repo: fontawesome/brands/github-alt logo: https://fastapi.tiangolo.com/img/icon-white.svg diff --git a/docs/pt/mkdocs.yml b/docs/pt/mkdocs.yml index f36ecf49..f202f306 100644 --- a/docs/pt/mkdocs.yml +++ b/docs/pt/mkdocs.yml @@ -20,6 +20,7 @@ theme: features: - search.suggest - search.highlight + - content.tabs.link icon: repo: fontawesome/brands/github-alt logo: https://fastapi.tiangolo.com/img/icon-white.svg diff --git a/docs/ru/mkdocs.yml b/docs/ru/mkdocs.yml index eb2597de..6e17c287 100644 --- a/docs/ru/mkdocs.yml +++ b/docs/ru/mkdocs.yml @@ -20,6 +20,7 @@ theme: features: - search.suggest - search.highlight + - content.tabs.link icon: repo: fontawesome/brands/github-alt logo: https://fastapi.tiangolo.com/img/icon-white.svg diff --git a/docs/sq/mkdocs.yml b/docs/sq/mkdocs.yml index 5afe0a66..d9c3dad4 100644 --- a/docs/sq/mkdocs.yml +++ b/docs/sq/mkdocs.yml @@ -20,6 +20,7 @@ theme: features: - search.suggest - search.highlight + - content.tabs.link icon: repo: fontawesome/brands/github-alt logo: https://fastapi.tiangolo.com/img/icon-white.svg diff --git a/docs/tr/mkdocs.yml b/docs/tr/mkdocs.yml index 40ba6d52..f6ed7f5b 100644 --- a/docs/tr/mkdocs.yml +++ b/docs/tr/mkdocs.yml @@ -20,6 +20,7 @@ theme: features: - search.suggest - search.highlight + - content.tabs.link icon: repo: fontawesome/brands/github-alt logo: https://fastapi.tiangolo.com/img/icon-white.svg diff --git a/docs/uk/mkdocs.yml b/docs/uk/mkdocs.yml index a116d90a..d0de8cc0 100644 --- a/docs/uk/mkdocs.yml +++ b/docs/uk/mkdocs.yml @@ -20,6 +20,7 @@ theme: features: - search.suggest - search.highlight + - content.tabs.link icon: repo: fontawesome/brands/github-alt logo: https://fastapi.tiangolo.com/img/icon-white.svg diff --git a/docs/zh/mkdocs.yml b/docs/zh/mkdocs.yml index 33d40129..a929e338 100644 --- a/docs/zh/mkdocs.yml +++ b/docs/zh/mkdocs.yml @@ -20,6 +20,7 @@ theme: features: - search.suggest - search.highlight + - content.tabs.link icon: repo: fontawesome/brands/github-alt logo: https://fastapi.tiangolo.com/img/icon-white.svg From b8ae84d460f331b8adc4d25b5b6f0bcb40baaac8 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 16 Jan 2022 14:35:21 +0000 Subject: [PATCH 161/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 01df3126..5bf2302b 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🔧 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 From 7fe79441c1cb7cf3baecc271a3e9680006c8f2c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 16 Jan 2022 15:44:08 +0100 Subject: [PATCH 162/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20Python=20Types?= =?UTF-8?q?=20docs,=20add=20missing=203.6=20/=203.9=20example=20(#4434)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/python-types.md | 2 +- docs_src/python_types/tutorial008.py | 5 ++++- docs_src/python_types/tutorial008_py39.py | 4 ++++ 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 docs_src/python_types/tutorial008_py39.py diff --git a/docs/en/docs/python-types.md b/docs/en/docs/python-types.md index 76d44285..fe56dade 100644 --- a/docs/en/docs/python-types.md +++ b/docs/en/docs/python-types.md @@ -252,7 +252,7 @@ The second type parameter is for the values of the `dict`: === "Python 3.9 and above" ```Python hl_lines="1" - {!> ../../../docs_src/python_types/tutorial008.py!} + {!> ../../../docs_src/python_types/tutorial008_py39.py!} ``` This means: diff --git a/docs_src/python_types/tutorial008.py b/docs_src/python_types/tutorial008.py index a393385b..9fb1043b 100644 --- a/docs_src/python_types/tutorial008.py +++ b/docs_src/python_types/tutorial008.py @@ -1,4 +1,7 @@ -def process_items(prices: dict[str, float]): +from typing import Dict + + +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/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) From 9af8cc69d509cd8538bba3d481687d9f85cb52b4 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 16 Jan 2022 14:44:43 +0000 Subject: [PATCH 163/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 5bf2302b..b33e9dcf 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 📝 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). * 🔧 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 From a85aa125d24acb225b7b722bb4f3e331a21ec267 Mon Sep 17 00:00:00 2001 From: John Riebold Date: Sun, 16 Jan 2022 11:26:24 -0800 Subject: [PATCH 164/196] =?UTF-8?q?=E2=9C=A8=20Enable=20configuring=20Swag?= =?UTF-8?q?ger=20UI=20parameters=20(#2568)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Artem Ivanov Co-authored-by: Sebastián Ramírez --- docs/en/docs/advanced/extending-openapi.md | 79 ++++++++++++++++++ .../tutorial/extending-openapi/image02.png | Bin 0 -> 11292 bytes .../tutorial/extending-openapi/image03.png | Bin 0 -> 11176 bytes .../tutorial/extending-openapi/image04.png | Bin 0 -> 11180 bytes docs_src/extending_openapi/tutorial003.py | 8 ++ docs_src/extending_openapi/tutorial004.py | 8 ++ docs_src/extending_openapi/tutorial005.py | 8 ++ fastapi/applications.py | 3 + fastapi/openapi/docs.py | 22 +++-- .../test_tutorial003.py | 41 +++++++++ .../test_tutorial004.py | 44 ++++++++++ .../test_tutorial005.py | 44 ++++++++++ 12 files changed, 251 insertions(+), 6 deletions(-) create mode 100644 docs/en/docs/img/tutorial/extending-openapi/image02.png create mode 100644 docs/en/docs/img/tutorial/extending-openapi/image03.png create mode 100644 docs/en/docs/img/tutorial/extending-openapi/image04.png create mode 100644 docs_src/extending_openapi/tutorial003.py create mode 100644 docs_src/extending_openapi/tutorial004.py create mode 100644 docs_src/extending_openapi/tutorial005.py create mode 100644 tests/test_tutorial/test_extending_openapi/test_tutorial003.py create mode 100644 tests/test_tutorial/test_extending_openapi/test_tutorial004.py create mode 100644 tests/test_tutorial/test_extending_openapi/test_tutorial005.py diff --git a/docs/en/docs/advanced/extending-openapi.md b/docs/en/docs/advanced/extending-openapi.md index d8d280ba..d1b14bc0 100644 --- a/docs/en/docs/advanced/extending-openapi.md +++ b/docs/en/docs/advanced/extending-openapi.md @@ -233,3 +233,82 @@ Now, to be able to test that everything works, create a *path operation*: Now, you should be able to disconnect your WiFi, go to your docs at
http://127.0.0.1:8000/docs, and reload the page. And even without Internet, you would be able to see the docs for your API and interact with it. + +## Configuring Swagger UI + +You can configure some extra Swagger UI parameters. + +To configure them, pass the `swagger_ui_parameters` argument when creating the `FastAPI()` app object or to the `get_swagger_ui_html()` function. + +`swagger_ui_parameters` receives a dictionary with the configurations passed to Swagger UI directly. + +FastAPI converts the configurations to **JSON** to make them compatible with JavaScript, as that's what Swagger UI needs. + +### Disable Syntax Highlighting + +For example, you could disable syntax highlighting in Swagger UI. + +Without changing the settings, syntax highlighting is enabled by default: + + + +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/img/tutorial/extending-openapi/image02.png b/docs/en/docs/img/tutorial/extending-openapi/image02.png new file mode 100644 index 0000000000000000000000000000000000000000..91453fb56f687b99fc725afa0a7fa07eb1955137 GIT binary patch literal 11292 zcmbVyXIN8Bw>HY7*Z?CKpd!6WfY3V$J+uIV3WO%oq=w#$NbjQ3d+!A4 zgkD0Z;oJDU&vo8&&U>97Uw+KqnZ5SxH8ZQ+Ypn@XR+J;ZMt_ZjgoOOnOBq!X64Gqo z9)IN`(DqL#bpRJq#}}{EuUxq@@mpyI_nH^rXFlvWbH<t`aPg%qXVr!|vD~ z5ee5_{7(^dZtl);POep%j>4mXNE+#Y=I+7yT>pXrjnB+=p#4Ke;>!cv{zG=K%Otf!1^F*J z4`D~q)1in{e{#mHUCaeiStUuG{iP%Xz7wtT6WTNC38@+~3;ocyN#UJ6Hy;vHWV`6O z7SVD<$xzC^?JX?i-*e}Q!AZW_LX>xn_w}QX62gWn zydSKAy_w|ut?b+Ic6T~r!`jX{f7xoN=#~^ZvgQ)rdZB*xy}kFwClx_zP+%Ck%VBHX z87tvE)nkII>M<{pB#d?{c^r!53{SR36?MZZx1}6E4&VI^Oxbr+FT2zfL*{>Qwi8ua zRwP-%UelC7SS)uIKi!a5OP!SCi6v+k?41V1coj==YuN9vZ*6rH-LPD7by(RFmb}hf z^Kz`_si=Yux%Y11ICK$J&C^l5KYAy~Ul{-LO8lOY_Lz?xsS+ZjQa2qTasuQ3SYdy7 z7((5qq+PS;hi{3<=a!se_A+%iTO7}WsWxS3U_GqqDc2=cpX6rw`{_P)eNdO&=#vuC zm9rYdqwT$Alhjr9s-k!xiA8sFDSZQ2P&s0m0%6Cz!^3@9&C7LvB`Pp&(LW5Y#j^&h zw!^;U^Is(VSDLLiH?XE0L3hHdLHN0qmA6AEBG%B{fHcJ{*E`6KtIWrqtedHG*f$;oy8%`rZ?>jN`$(Qkthz>)1>_cG z;vULyIMx1Ld$AzDP{Qc~G-mCskmJzy=jUoMbt0wD_t2KG>E7mKYiwPKW9)Ws-$h6% z8*8ds2dZY@BKD>vVF1Nuo20$hy&T2PG%`k;F!MxN0m8?vC8r!1{q*gi-MHnb*a7P+VXKFPl(So4e zE2s-p#M8s}qUIx+!fDjo@Er*b^E=j3_%?KpB=kvR=?VtR8WdpB;r-gh-Kl%c6hYBn za5keBCTHH|eOBjHUi8Bl^1y3or)V|o?H=B{-VO0ZZgeT0&sG;hLNZFF>3L>`3ngGi z?YRQ0Kh%SSMAWf%gOOyE4Yj|^Vux#e!db`>4{}{^#Fgh_a-I2aU%PfOnt3|X>FqBC znQJJ~VE1IbypOE)Pj4m-rko{r7w{>noYotaKy!Wo%zc4 z!FqcDvXx=e4KZD5IMZD~!0Lm0)tvBTrF~nvIJyuPs>_|KozJZp!-Fn85J6f^;IHh59$CU{suyR* z)-oyg^EZ>L9QZtm=*`)Ip!R%l>UJ^lh>WA~AWRzW_3q{2$2-zLX;ca{A*FY!z|0kD z4DsD!2FvLy(ySudJKQp;nu9u#U9;0;k%+cNgWfzaI9E}Nx7VudXjKJD6aEbxkzq0v z%xj1a2(V(?-Oy9s)+kVvQBTyA*{eoNlu{UhK#sb77%GWR*K{KlQ1yDB8S zyBEYkDx6vlZ>;Q3bsAdZb%eDNHL$bAl$fhS>|ijuygB4JcfAH_KkfL`szfrSbikJRq0kH|h%7`;MD9}_Gd>z`eqvIw2Qf*g>9r}cG zhpuLajs+fn#!rm{ej~n6){i+%;+yfNZ#r1ZFQqpUnjBEW9+#TGt4S~uo|sTpgQfMH z@Eo0<=%cMd4)GO^$~^^%1l!s4!yxHeruiSZSDOjHaet9~ukW2Z23%v^ZjHk4kuw{` zRu|6TQR{p=3NJDo!fEu8{U?m zwKkfrQby-^B|_a`6R$LMFV>K`#r14Vptz;(+piuw>YoX1G&tldU~6H}u-X8o@bb&2 zV#3tnd=VNluN}BCW3_Ot=v02wNBZp5^{BU})gg=z%{!V=&8<;+Y1be!zxqp2atQwf z%zi?9Ygh1*Lt{c%g~{}AxY#1&p&2n18I~ST0}4;AEWl>m))C%yTJ24do2|Evc}>$6 z=7ne;WZ|B)LFg%uB>N_M^k_ps*2DWs*vZ5lhRv*K>bH8HxIyahgx179H;CEwhVjcJ zK6-{tgiV)6Mu$Anv|K+=Hrh_-Yd(84E3N5uwESLa6jQW0F@hHnGD9|;m(wIL4N1c` zo#~c-T0KPK1Oe&cTp6;minPod+v=N~zf}=BM-;3KZ|y-W4sPa*aC03#KEd*Q$PtVq z+}^cYI;#$R$3Cu!5_OYJi8;vi9sl@kHH- zZxI1S#T=rjSF$?gokfu(Ff%>ML#h2MQ{ax+?usuYFGk6uY_bggeboFicJ6Kkh)w=f zt~1UCTJA7xj95g^K9(dhVdA!Je42Cj6GZn8Yn15}Jc_VXPVXSpIS&?J%dJe&?x(sgEIF8sX2EUWmnx%xx3H0tkTeK9 z{c&r$VASA=MVSsH=RLz=NgROpe5~G`Lp$KY468BSXQ;I6c!mSeobo#w*vYBIIVAkQ z0U$v8f8oS`1C{?qHUpo3d6g*Jr$RPdcqD#!A`+1kU`0Y=pmgK%h%7!JDqMmfN zEKMVN6Z^>OtbPO*>%hMMGzWmC$Hsg#iD(Jp5r z8rOTVU=ot=NY9&|>zJGX3bMbSoWs_C*g;Cf_a{$Ge(eh9ZT`YyCXCFu3p|bTJwDJB zISi3z-Lq8XUyR6#S$ipIo_hZ1FW9k(1Hp;*m6ZM6MRmR*peviRR8(E$a^ET>@*+b9 z&~f)*oJ->daVmZYB=6!v@rOnRpt)zZl(e}PC-pMxOPF^Z7#!pa5UYbVJ@anrz1l;2 zi%Z!qT|%vrwS06}y9qqrmA0Uk z7t*ZZxB5j#mua$X1+x{{cN?$LL*IaugK&vfI84`E7iK!ccrun+VC)AN^ce9Ymyy*k zJc-AaVUiUG>YychV6m>bd)miyt~h=y!|d+DA_#CalG}E7($9OCAui}M)#nn6d|*wY z-qXNpR0|Q~j)tQft*v?-ndAO3F0$ZeY3K+i^Te3m142?CM{x7cp1*>Y zMY#i>(1(b@uJ#30NT3c-d-i zZc@@NFh3$i{=syCIM2q}-l^xqLgg4Q=eytqN+mCj(m-yuqng^}=Tp`Jaa`bsW$s+A zgr@rPN-hkd&Gz*BGTn3>(rqt-w=WOt#=~y%Yaqq>bHlOg>DcV&1DTR#9NDExOqg!P zk*>}z8IjkZb2TEEl#bC(tipOhEQVhEiM>%gR-9YD85tz`Bt5AlZUOg<1|styukBL| zk-r#!h+rkhcn>~bxB+aVFAqGZd~0t*Fbg8Xp+yF+c3JmL7z|=CuNOFevmQBwRfMyH z9WtP2`i1QuXjq6Ida7-Ox@Q_{zQ@jjYee|S#}W!bcS$B4R(?Luu8lllrJAkAlKW`1 zdg61~&%yGZRTTZ9M>bifRw#Vhu03nvKGnps*Y0;d0{rN;40ZS_V0{24*u!%xt7}#u z4iTRVD6E>d_f##}!TsMmI$ov!rs^n{vbR;^QKHn~i#kheELnL!g1tSQl9M+u6yv0$ z_mD|HaiAl~qT$VZA|MXWwwhSdvLTl~n1Q4vltSWkO3Fuu%*HE!q^gDoBg{~)*_CU zwo?xm{i+%f4b(?l9C(T~@u;N{RheH89VZyn>TK1R*M*2xM58tZ(Qm9G4MxYJH9s|P zSn%yn`=5+p5nMBUlz{^~EOv+Et>h3+ZMW(I?+FQ;X7E}LYJKD6kZzgx`c zVcQc%=fE^r0^>O>KQ1MkQ2NrZUst)^Ic+Rr)(uEhb{w@vT?DpBOJm5YEbdL_^Kfn~ zVOXU_tXDXl6jI3+-)}I9L$DrO%P3&^EC)M{KMejwVy87>fA=GJ@?9e+dJEok+3$JY z5c}(gCqcX%_1O-LgTXBNiFTbDpyI`*-FYFv1BqOz&-UeXYs=bs3`kJ6`pLOD0}>Bk z?+ax-uc$JNzi%AOE7b|#c>Np@31{g%Y3Fe4Q>Pc0M}o$ZZtiWN^Nx?M0o(#_C_O?d znTPLDkr^pIR0AxZGI<|PS?{(?5uM64o4-w+VTC&H0TQ7p$2m48q7HmeU8z)bRI0?W z2kFwqlCIX-(x49thn#;3{|jAoIuIpObkMRp;G*AK;bmu&EqnS5Js_flVKYjxH@W z34Ok-6&C=X>|Z0Q9?!4m?mm<~{oBh6KgGDyy>q@c&$d2Dl+0&5SX8+TOoprP++zdd z-ZgUBIFwZn?z0e}4~d7ZZ4sCPx|hnxY5a?SuLvao&huPzKJ)HV8NV4FbmtzqtYd3A zyGU5<9&_bvP|d3Q88}#37SVjd&l$=Ocz$35*5qaCEGEDHo;zKV&;9jVTdn88gM}|5 zfG_o#32F!~?A{uY9P|2a5sW^tnx5mbRXsa;nkVd~^(Gr$yKO6C zLAK!3Gt|J}ey?e8-+`*?Q=~2@ScNSoV|H_40j?%)UBm7m%H3^31(BH_o~jJZXrZN8 ztNdhjk;F%eMWVivi$3TJ#L2ymIL;9}_5m!&AnX7NhkH#cszxU2%_J|H(B`OCSVl~& zj%hS07onC4^?aANfpzv4()6^4CUCRrmuBB1ND*g0Twc1~!+dmaF{{f%?I>)Guvk00 zJUdJZeZ80z;(x6)qh)xlLBRcGLoCL^C9!ivn4l$@D86ggSfl{mn3pmd_TK|?cRmdy z8~{`Bm~zjr`&*a7hQn%4dRBTUEK@aV)1*AziHrP-1^$$)nyV*aLFS!PZ*8cGb-f5t zF+waH7L0srDetCq`NA@2)Fc2Qqj{X0JKYG+&DAN;jA#>h+`n)QEC6>!pdJe-tLS@N<32X%bwmqA11U^li}%658Z>@*W`pUDwh2>K~splkt{=UK`GOn_J9FrsI6CeTD)Jtml#jxa&|qL zcDw>~(=!`t)&D#XnT*k&s^Rcl3y2Zvb+VQR zMB~7RRzG@>=8qi_z5P zQEolA-*U$@3vV^u8r^W}scHA@ds&ybHy^Xkd}NHC7wUT(paOrvPCySK@nIMc2=Lz; zjCin6og;aHpz%^louwwIk9xt>Ry=xv+0L6lzbKtW1x9;`1Z4`Wk8)rWU!I91Eh2&? z64iuFk}-?T1jknaVfxPV0Ukk$7n~>S1kA7>yGRoff{%qegI`;k@0?_p)C2@rP^sOz zg}4nY$CZZ3kL?+}u;A0>QAkrsn-mMKVWkPQ>nGu5P1mHV`pLGYAZW;lo5r5!8#*8t z`NV_%2H4b61OkS)y?v_=c*~of+gkl+UmfMnyuBq1*Q5PD9Bt_NP3WUio!g2w?EMR_ zRHqt61f&)D>!)Xjn{&MeOa?qqlT7_le7)k&!>N;5HEz8p)=ub`Zn19*4nD`}S%KsT zDT$=De1q?I1LYKi6B}`xODtBDfM&RX>03IGI4=j^iep zlAj@R|F%aRk7*qXo7fIgm=z{V%Y7$(%Z@{n$~8T6h>~;DMCTWN)zse zg~+<-2gRx-9IV=9Wl}?6qCw2j_A|q@e3yJ&-R|Dn#ygaslfNprz_ly$021|q{zapq zv1uTzp#kVvn+ZMj$_@&_ypC3!llABzg$*U})t9Nk}xhLP>e4e^cBD z{rVosA$0o&pw%joQj+%ogAt_X=5lb5nb{GT`Hj1P;q_RQUk>2Efkid+*X>8Qla=0B z&UNJEY=t%0_CzxR*6&<-TV`quC!)zGvH@MKWSZ>0A^b#);CY|D8Sq8M(c~j`Zhtgx zEwb>z(Oe6Q_hF0fYd|O0ZWK^dz5{1=zL!~g!G9h_xqPHKuQ>c$t>K@b4d?}QUL!)m zQq)YLbo)D6F0sn&PoIlI5gjP#!wcXK;9Y&js+QtC?vD@%heab?8w&A9Pa`4l2^FKX z*MQ@J|03nIBLxDlepZ$<*{sK^k*Y+;W(5ScD#YFXlofhC8}!H8P&_tjfAcizP^w6H zTyXWBUL+7kPBvLMKXcB)IVsj(F35WU7$G32-RCUpI3*R9kg~C0I-gP_-O$VCQ6(Tb zrnXw8%V3e$TuR?j(<4=7XdtnUZ!Dm-*d>*Ams9dyEAx@6(fx2C(L+XwMfBI{k&iqO z*|P^R!_<-ZecQA00he>f4veQ4*fMiwlc*{tS!Q;huY$E3ggHdbfmn`fS zHSp$3t7(y`Y3lqiFL_iDMAKI#@zi)bQoqZFu7Z($W26GVK-nC8fa@$$C`6|`Hz*Nx zS8EP@FRzIwFhlL1JsYu(dUOF zlT?4A)_?g+3>Xn6aCl2?=`N+%TE1DQDb0A6mF2~|;Khd+|AN!EP0|K%A4{-1eCRONwI@n;Dx>l%NM5zu65ueCX7}yEg^k*p-9a^5v=wqYE{K3Iu zJ4H2`zurnxG&&b9HZZg{m}*@TStV?-g0XX23H6mNn}i4~2A5_)k$+v@zr9Sr+Bby9 z^z=(h;mOwrE1(l)_Q!KK>Id8f5hfj2FG-H4Gw#EBjvt#t~ZtP z|G<=+l%Vi@32Z*)nt179jx$3L^akV1`hvRt18}P2ZoXlCrTJJh{*i{v&NvcLq{f}^ zzM2shCbEFCH?YZrCGI0t2^e(VZ9SHNr$Nn`S_wFuiTRJ5kPi!BaP;TIEXB+r8zH`A zvz@B6_S=!H{T)ROpBs4hofM}&z4MA=z;-{pC+R*Eet&~Y^wOX`q5lht zL5R}Mfo}u*gr7Ylv28c4o2IKaE_E&Vs|~2HbqLtF>~9APLX&1&rO~=f+A@1!)y;$I zDu%EH;SK!WWY~x;cE9b5LETF(VPV$d`3D&RFV$zG`mvp3f8f(-{m)S-H-Q-Eu)8B+ zuF6;|i(f6?8RLaf*sP^h;VIA=>35IIlf;({cw^9&bZ;RtLtA;Kz|0 z;}5gz_Z8u3t4@6TTLF|(EITVlUrp3XzVDx*OUIqi&eMkucuK9O*EO_6{YA#9v0#(wx9;aecS-PErW*A0^u$jBby1uN zBGA}%k+W>@eI$PtBKoV^fA=Vx69Mg#MFppZ0ZU;KEC~*GF?j%p^yS$wI`UoP*eE!b zz2m!ScyL5W=iVPF(5)?`O)cB}Ss|C`R#1`Muba ze@|tq5D(txAEff-yiU9jL`T^w?vmSD?A#ElXw@I6R<*y~S12y#>8UobAfxs+W|%x< zt-Wy&YGY%M7%H%In-zeR#jo_sN- zsXG_ANUL#-*RSu4ASOy4F3~cGEg(F~R)x6#NZ(f|`b#h(*z-HHBwBWEHJv|R-l34F zpV0Bq7Ao|~^a4p`ki8iH$h}cU5|Zor=Vt+C9<@)#m5=Y8ydPT^I8WdKj!X65-3Et# zkL~V`dzwAgS^^(+pwETTu@xw!*KHY`#myjlp6sO9`zNhU_HG}|&@wEMecTI8B92;L z@OB}$b%!-@sj+rZR!h!QUnEIpPae8ZId*pY!#1mEm)LW_?%X{{J$meDbvRpr2_x9w z1l{>|*CF8LBu=imuzo4Xc{eVdlSznJ()q2E=xB_^8SC>FPt|uzd5RRfb#@n&iAm0+ zwT;!;j6)5x3asqwJ}tpehE5aB8&-<_Nj=lC6&u+rnqd?!2->yHTxwRPkdEThX;N+^ zK3CB3nT0u)URBfKA-|=|XvzTG%eo~jZ}l-4muxt9IZ*!Ox8R)1Qjl)!;V=2q!~vm4 z98;v8wZCc~aN1}@?`%67`#FPW%_d9M#+DsGAoKP1EIHve`C#t6k*a7%he;w`#lwa1 z_9@(8yi}!mat%ELvb)AK*}T7=$Z)I5;?K-+yP~yLzh6k}>aUbNuWC;4GT5FhO_rpR zb;)--sd)%8?D2d|jBBz+lTuQU()h$*NUtWoly#2bx<4y0~!f{*r}0lHG9o@MT=}C^u-ck zF!gLQOJaZACvXDpEG*E23HGNISul-(u>`b6y_1kd63X+jbxV;=yp_dad47IXTkUdC z!e2txVtn5@_Fd<@odm_omXA2m)<2au`F|g$Qd44I#0NlzZNkQ(MSb#(U2spa_z3el z-_v6X%OmH83N_5nSF4585@N{+F`m3_r`rA}U&GU=0|K6Y+5Hx?aj@NfiL4~U=({su zMgV}d=^Jon?|ec|3ULTUU@L{w+LptaIzEGi^T%vyL7>%8re<@|4|~hAhp2WRP3F&~ zwjFPSN^gO}zaY%HPese4pvy5-r!{3INlr|C&3FB!;_Jl4l|+)S>+HLRk@?ceGINU8 zd30HV`r3yYH!~F8yn0z{co%F|C(BBa8&e`AcKG9Dj;2D5W6TX_qSIw|pVaCRM*fO? zr9Hj;aDPZRbO$n@9}tjztdwZa;dqFd$zxLpgPH_e7a~UL6xU@1H6XUb!vn=LCyiP^ z<`_@V!jk=X+{gclXa3|trrIB_e*c)(bgw|VnqV>5HSqgKvtCxAgi1#K6JZ!=gd=AK zd;wnAJzXy-lRoE&wejA-(Pkt~=Yn^vDUn})DXvTt6)E#KD34v7=o$OdtVfvjYGALz zyjLek`ydtL&$-y%a8%V**_s9A^5)T0JkoR)MG8aW6Jg5fR*XA4-)v{wod1SBB4k4+ zu%gFS-NT8^NKxlIXK##I@VcX+m>3%-6bhH;P-X9f381Ep(tXuIwHG*3W(#8?BJRzOcx|br;nMX*>QGDD7eQS~i8YV> zDY93&?8Sr<%1#(fprP(d64)TZ-VT+#?)+6-)v-*qGYh7{)#Au+b(TUHvv>DGk6RdP zA=iDnH*+9Ch_n_$=C{lHcLs`2`uq086Ngn~iX*keeP3YI3~u#Zs;Ec4cYNi7I}8Zk ziC@=WT$xJtK=~EA*oN~J9Vy6a53cQ92f3Xwd}r!(vzK$+Wa<6dkx;Mr`lrVfBfI^7%J=ge2#21Yh)jzT6t)$xyM+o;ZIV*2{1=E~iByM`zJ^Ayi_fb<4hHj}3G2;kT$z zGLrA)E{p*wSC9sF?;qAei-EemRT(<;b<2v&+#WeWN< zv`}xa%pR(nKc%7ZO&@H)ve4aingVxJ`uG0Sj2E*ci9|IKddVMd&0W6syR zJ$VJ#Rx?F2$MC+}#gEGq_IpF`))qdSQ&f*fO-f3%LwRvriIGqXT-k|&9IK!MZ{eCXx)5izA^8 zPP6k!4hU<_^}U=|1Db4#m9GJiMxqaAa0X0?zk-{7BlqR-Z#}tdY*+qpAgLi(t(m*vU^&~`H4CJWfw1@19i%X)J2(Y zMzQ6zWmPNS%g?sT5E*TOQuCiY{_p`10aE^F1fcm}r}6%dkcn?XBWJiX*pp795d!7B z0n{qzg+kw=Db>M*pN>gxljq6xM>NWR*BGbTWrEtc`0b_*yU{)gQHil79x;(vDQyu|oFb_=Ln{^#Awrk-r6XlKhG zS#6tENC@@bJ?d8|&T?fx&-yoxD89uV3ih%sl^2Y;&tDt@`u?uJ3iSm7+mlKChf$Bk z1+9uu?M~Y3=b41;KgCztrBAU?I&!4OgVn9V`ybBb?3{D4E z7MiufR2A4xAE^ufS`h%3o#(=yU)It>1Gm`i=UZjo=!PfEK)`eawO9DDadN|1A}hkVY*PYW6>~c(stJ@Mm;ou1U8o4Qhx=B#G9-w>DWOfMMl8 z?s%IEg*0v@{j{hv<*gwL0m6u8+Q8LwS;&6l&p+5Zp4w+YB+fv?>jmi*!bFGXL~zZ! z3;D;us?3Dd*nG1Uhk0>M4CjSrfDbe^_chct>PjRuG`Ki8O0+peGH@F=rt06kdaNp< z0StL?|DtYG?|6S+Xw$%}z7?m?&`@vhEgDdKm!`hHzNY4R3;cM8SYpowbVw8Oy}*K_ z*Fpdw1j@>dDgE%~t#a%L?w|V(w)2 zvG)oY*{k*R7R@800={os0pZBpB)`^r*b#1=^Yp# z-23qN(^SCwkHSFnXp=msW9C!c%S*rS0m@|zQtX)Sg)yO*Q1x|+^yTx;?IWAXPjXYM zZRF^h*e=QoD!Zh zlo45lr>>^h{`-dKg7{opI4UQMXB?5_od_*7#wOk+dyUo+Nu{zP@Pi_sZXXMu$-=c= zIWbG&F}mpQ-?q0mwkD5_ya}OQ>+VIcRMh`8j8e?A!nHW;g#vqR(hO^V*$>_~yS2Qze)93UFHx{Z%dHhMktDgCXc_VS zFEkafsbhO%8>We-5Ql5}?wR}ZMZK%58FD3A!ok&FVUD@OYh5!O%+(WGn_bG4G3%r0 zZynN$p1?%wNB!($gtQ&^c1Jf>CE(*qlO2`^fmtvC0U z`&7pno-35U9$ss8u7ZZ39rpMA%kxpijlK`8AYJy8mS#OhYV9_1Rz+GntEbUIT%(k} z;(L+pd#+MQfu!2I5AR9oiQ5~QB#%sB_09eNAbTxm1$S>%Hs|Eznmlro8?w?&_r~6n z^ZBvYZREJNxjHJVHF1PtgHCeEt>J`0a|DyyQZ}#UUhL4!Rd69jtjmJp-?Vi5r#MK! zdt_>1^Me{AC5 zbV~u2CllMbA)fXN++x_bx*pc;+-cqeZYoJtoP?y=w-_zLw2Ypf`T=Y=DUC@>6=kLD z^{p=_wTev|UcbAjwIpmL3V%ITO(WSD{Ooai;DiLl$LIW8QJh>hN%FRiL)ktoCCK;)^|sKe~J~pJ+|S)pI$c2-fy4h z_TlmCkzj`CP!6UFEeSPGA4SEGQOaSXO1|0qpxC4{G)@Yowbh!MT|{-pDkGU2;ciP< zg-aD=qCnIjQ*5nLMcib0uC}_oz>3erx4(T!T+P#K7y9}zgBD9y5Of0H z|FoBPkMG{p%3%R3Ocy&!ITT`uK76Hk{3yDv@UKeB(D+4+H6gC7M9(J-wvA#P@%tIy z#TRF4c74b|O~5h@C3IoVLZaV#!9^u0VuPse704%~!>7}#qQ*$#j_{`oP!T5Yyzyq~qhvs7`^BW*3*qVDw@j zk45mO)1eMfnO{$0oTA{J>WyRb{XTrTLNZ!E-x+^ZvRo?ki>h(a+2C&8!(haSCv^R+ z`3DurEFv!HUU;2_3-(Wlb})oi)f9#3Ko4dZfK5ss}FO9PXUq@H-i zv=HM$EeKdUzx>_zJ}Z|xh@B32`^R642~BCo1uwy1ZRaGf_`iN7!%9J^FbPm<4ZOrI zBqRpXNY8Xe*q_NN(a{iGY0KoZumH~9B@_B&!wngAxm^W03<~kgcn3+)5yo^mp@h$0f8aR#9iAL8ZU;TcHI0w-o}4u#su|!rmX#|KYqKXd3Nx~!Mr!I3LOj=7di-R>thPHJ zx!tKMwe7X!j!h&Zd&T7G%`SROCp%n7bxfGb9FQW2BD)jOc;yIRcE3r5KkZ0MCL0|$ zg8TBuVgy$Lv(lo$3D}_1-2<&=uhnlukixiT`|DR*UU zz14Xo(6ZcH#BojCkrZu4%Q@>EalblU1^;o z8e6U50gGo@1f^l2IQoZnZ zRL-mP!QN?)iAwFQ&Fz>sp<-MPB-5~a-_Hos__Lk&OlCKulvGaDpJYA0w~IWxVIK8I z25edBsi=iOywvt5?-p*@XUt?yP{{bx^v?WA=ab%#(o`+`#}9PqWw-Js-)=5T!A78i z!yHd~)V=(sVZqIPg)Q7@)QgjP1qXW^>}Mmf1j8);^DS+eDG|fLA9OM#5l;0j&ZtH~ z#Ny;T*03YW24^{4lEvMb@WXyPTgd!y;RYEQe@Zq?BdSbWr|gx0Vp_3TX@kEcboJme zU+wpiFM64UKA3(E-|PUIvsr8rZPqn1vXer!nZ=^_wrJzRJ}df+TSD_aFh&5aDPQ^x zTm;@pt`^hbT88f`Go#7q? zOqYCc_W-J3#>fhYvl&S9Z?p=$xr_mtzq$Sov9^BF^Gsfd(6tQ;m1wk2c-ay55$H!B z%gX8gEklo*xwjk^J5u%3!ValmnSK{|b;$((*2Ptj)9Lh?_S%*^3RstS3IKRXUx8%N zX^39s0q`kVJr=twDc8#lx-TF8M8QDu0!=afzdATZX z#(jIb#W}6|HZFpIn?0}zz$aqPV@+b#-vDu@&w5wir}jRm_VlQdwPsKTNS870E>^kS zBX1B!TTpUUM?$aN@=4M4DdZ-co>s^4`q`Vhl-`pwhQZl~JAX9QY;529RR0<=aq#>i zcQ@zx@KIKQm`~BSuLa9`+_ITsrZ?xi7Gv}0+Ho~}#jiRg)gsa0;BasSPfg-w$Ho`q zitNX=T&r=PQVVV*PyM;wBxT9gGi!K}>^&&0g$eQ+BVTMV7g$GfI4OWXB zKGxDbSvh#i%nuw2pVv)T*a99^mV^O`%E*}3w}yt+SvGrH)-5y( z?oXYh4@>w_ua$`*syrTF+>pvTD%&9LaVMJ)h%npEA3d1_sx|5RtsK6mQn@`AO9?V& zH|@T7N+k3s6&kv(L-Y3vzwEHewr-ApTSrBbMwo$#IhN#W;ZxY)QWh8g5eD2bUL zegt5M`&F>$nJMZWS<=Aw^$5Q*4e3wkdK~PP+V+sP>f|%R3g`#MWoyWJ?QV}D{na1n zuh0HS_SdDOt)lI{H=s z59Z?9ADC{8s2Ir(3@BVumY}6|kdpEW3o7g!-D@XgRaaGKho_%-&=d?&{Kbmxs*6iI z%?~JLwi97ZY6+As6i?!KQh5FRRL!38z;qQepMy;%hjdkWw7LnpVs?fWx&_Z>QClX2 z9s%$Bq~=iXuV<*!U%#T03S2oC@j1Dn7FhO8>ul0gZKkvy*u{K3!>;1dl77XT z1<~8sP&Q=GwCx45_kQG5_QUNb9l}b^ktkNh+P;;AnZo1Aqw6h}*ye13p)7Ocbaf;e zFL7;6elvqRQ3V7F4-b#!AA`yn6DE!ig|0i8+z=#g7pF1(SRKl9JT_)X>Z)-eoO&~{ zTLKDg;Obj!P?5zy7Kq^5?;pjJYv%4Eq>gr0#tJ^*O;q#eQAU%A(nN(hPaVPU$ABW9 zym+-$1})FUBn)m2%siHx6lO1*bB{w70vNJ?*pXwL${&5Jm()dVs)l~|&eGd+hA0Qp z6S$ni46SRaIa6M3Pl?aUOW1pg?x~g|?QUBZo;dNf4#Yr11 zV4YRzeCK?Rf5Bo0W|3JM!0~xEb>IZh0BcmFNgm|(Go2wm)$Drl9qvJ=bJP6&{`aAj z`^tqZ;@|CtM2npRI>*@jx}QA;zNlv!Zd+c1D$gHR1Vt_9R%EZ-Ta1^|$_6Ink+y>K=&NKv{TM~6(sV^wjjQiIj0GIT`|wW;Q{j;R zX0w5*{)^29A8O zm`dTm6zA*3{EFY)D&=~uoO*2IB&}!1o~|@i<3Q`rLXI6@PPyqdI%fW!rb*6du)|-% z1~cj`zZCX><;AbI0VYkA1ONd%+H9&*#P|7hj@xNaG+;J4lk3(DsyF+*jmo|wmkK9t znkqtV-GrW?PVlJb8S9>{0O?in@}&;$m=?I<_TaVUlfriV!Om zhe3O4M_<)5`=x7%UA=X(C?20Xp+smc=T|Uo-}xF;#l2r3p9HN|T%a(fwh^)%x2rhZ z8!|cY?haSK>_W1w0HnTT^3b%sN;=T6`bM%K=vatrUKuVWwe&|* zSwb3!dv^9O@>B%k1PFhP)>Bq`Bx2PhE|C5F^cX38CSp($11^=g@4OvryD{|8uS$K9c_oiOZcJKU3zR)VAojeVwAty zev-n@+FFUTClYe^Sx5}O$G!08eB6P^E|W9&>G0qwstvU4YKV}IM37gf-cv3I8S4!! z+ThVB(aSP4sT5d)MsN7)af1|Vdp64m;c)2>mT+pLj!aL0#Wxz-I)c9}P$;MXV_M&c z$k>~wcLd=AA1(hl<$a=IE!T2VS^NS*OUxj@SmWdT5kO>Y=D0Ld-Y2v!SET~T-{N;Y zLlo0^ntHE1&%MZ|wAlEC!^Aj?j@Im#bUUzPfL=pkzhcdlbZ&HX z4M#LEKd>vW%7WFAuNW953!PQ}1x#86_Ei1t6pH+uyl}XS6DI047o4yA`^pUmg zj7!rw&eNCfR0o6ORIW%N(hFFQ#=m8=WI&f!Ga8iHaYagT<@p;a#*-%&bzxa23R9xe zjT*FJ9b!kWMxL5rm5iiBHjxy<(8W6fk+{PL>ELb_QHxy$$PawopJwAW`45TR4q`Df zWkj5;^y?8mH9+LM2dS^J!d6TVI#;f=wQQ>_{`!zpM0)cIX)$_i;l~^{t68QGK?ftW zoc=3ZII>gf_U+N8GLf+HbXr(th72vT(Hz72g?)`CX|_&D(RuS)u8ZMt0_DDUxkH`# z9qxo3U&1FT+#!9!RP}zG-@9B~kCSYX`;m)}A#|PaSe_e5`F<3B=@Jy@F22^11`2vM z^9ZaBu@MGUi%!aP1m;sIR3_aE8Zl1_dF4Eu>4LxanC*Grn+}eFv!b-|O7eylZ?|LL zrl7aChw}o}3rXF{pElZBg_#6{`3DLt;p}cw$DLPIlTqt;DJ`zEpPr^z=+{{8Z}TQh zW$g*c?gjvB!cz>`d^UQ!?hpDU6)Y7wV=L&u5-Ix$$%R9dpI7vwf!$o*{c;`~e_}UDWXZ!bz64nF()FF84*>@#^2LxgZ<7fP=eLa6_&d84w zzR)X3N$0$N%RByasBhMrnGyM-jhYXp;}0!P6F(N(JsuC=61(Fq^IfOLlEv?Aw^W_T zoB$l`O{E|d3NoUiqZ8a@D$XpcC~Iq#mJtfsRQav|aldiXhtlEX&Fe%&v7n;8hz2#; zPmb6B+ciU~NUT3*srWVa5=To=;}70(O{91rl4Isl)x$l3?Wt$dZH@WL%lff~VgzSl zD2s+Q9|&#@0-*SY_nR+2^g>qK62|~QKH(@WHXaE?KaR(y*C{^Tb5(OQ$OP&NfL~O$ zb~eR$94_qdWoiPH*W_04pGLfk)Vz5x?_6juJ)*jwd4!oblk zJI-ds7BT_Dca7M+l3M4Logmbk2=-=5x;vEB7kR1JOnefG-k3J0XqZ)FYaXkhP`L+~ zQQn<{Kk9PnMdT~D+*bja>X|r=LSJM#R9xeC<4aNDTq4$}RhN0TA8*I)AN7nxn>6p| z7td86>N%H+aGA)SP8>YuM+R9X${>@Z1u8A-7rKs1aB_2QyB!$0=!9Baw@#lIHh(2W zjEha$rz!Ppz)TDaQ*a$LV8ut3NSSjQA!4>xvkb1n(8PiLQ#m^@xLPG}9dxrI&vI(T z(iZilz*r`4r}(;8x;5^IZFQl`VegJdJBcV5@sx#uw(4P;12%&j(x|;Ux#zA0ez0f;fzPWU;yGO+7t zT~$GOXv$3vLIz3o-c9yVI?Lt{kKfAivoG zd>wIV+Dml11JW_uUW(VBXqo7eFL^SMg_3Rkpx>b6=a;QNygrKd27|#79w4W~E3|#B z`GD5Ar*AGuo;ag8mxg}|q|$|L2k5Cmy?D@0n_r(#<&Bra?Wg=@!hWc!?U|}oz0&~i zC02zUS4hk5_wFpkiSN;zb}UTFYBg3K<3@2pktp2$XLm7@VaeI$`)C_!gZIC%>uRj46otTXU7EA*v6LR(|f~&kug0YsDq?3^+xvPbDP33hhmip)WjLA@ zSA6(eNmP#eRGvHsrxKX-IXalHD$=LY6|hrCDk$sAYnrgSqIb^{JB~vWBv!#-6%?ZK z)*ibA<>CG<*_<8e9#@QCO@#OHs$7d7oJJ1gmY%P0)N$d^AKenZ4F`duI&u52)Sf@5 z-?ARJe&>Mve22T7@F2l83rG{lPo%y1ast(H0cD&d6O3MRFKKrMRmwgbkUo1opW&@Q z@{MRdG$h(=IMcK&N-wsA7#Z$U6CMiiC5{4CsGbQaV$|Gqr`wk>38O=4{U~fE#K<{# zC9Qg!pH|+6*LiYO+8IoAo2V_8XA7EoyleUDBe);PbVQ9`w{>W*W-fc%95+zmv>Ley zu`xJwxqR72>g8+{>$vX&f*Mu3hw2lhx`Cp1w(KMO6OTmf*-FmT>apc_?b{AwugkOc zq9(*b6-b_ASso#E`AFT4`xnTjQ?&j!pp+jBo{fD(3MX0&7{|cZPW_qWSD35oQJ$uB z|8!YZ21<|*vlG^RsIBf92RQq&b5eUJ<@=jf|1T#NObO9>_{5_7+H)C~haKI8Ms!}g zzryBnhPQ^>F=Af2k#{J&(`ChJw&JUAJ@){)hHxx4@sB6YsJi-WV&2lXMXJv??~7|-JYvxBsE141}c1ez4-_J z_8w6n#fC~_LjFv?l@WS;O_lcwZv~@k$-jN1`}jL)v1y0-%7?f1rT}D0_2B$p09h^p z+lI#V?!n(~Bu~?H+3@LF5bXl1J&pJmWY+(94I>du)IYsO6TH-I=kDY$R{gd3YdGK6 zr1`z_3Q8F+00Pro$*2A~#S@8n<34wN5Xf@0u<~OLD7V*qrbgVS$4`vqK}SMbsn-Dy zG^5j4(v>RbpWSrYF&g+xGvGNbMZ@B~>i~EV{_|F9U97kT9PF}FP;>?DEfIux8%^VF zKHT2A{OajoxelGDTn;Jz`Lbcjn>57`SPi3j0W`roIUeJ#<&t+##`E|HvLW%Vdd?sl z6@(aGiU{!^2`N#CskSosR$pWTnL0wvGXLoZBo?tgR}EOTn>Y>vP61A|>R#Qx&B|JK zkXCMP3u&Ak|4yof_x1G|EP9JNs{VD)bXCmtsxt-IdnHPK%zuWLxVQqPt^}8SW84LF z+&Af3U)_Vz+B@9#t-BGjp??()6u~=!rt)k1GwsZncDI8F++eH<*d+w7b6`I+%Wc9k zaz>yrO*{I`Yb1Z>f=FIzU1i^k-rl&qJ^8JZzehFD30-S>#E2>#f3igNFEctg`y86l@P_{9>791akuSiwi!Yo$*{?XkkOJht9er_N%_ zi)fYu`(L$1;+)C`4&5DsIH?4-Q|HCN)&*`bn4^MX->(!DUP$ZJCsSQDHZf9uG{Dft zs6$&dXHY4K@AXssYa(t3M{3!CGA(KL?)214N!fRmW=sw5yNhH;+bnR4OICmNU*MEG z>X=E>vxf5FxdZ4atR=RrMvzJn`mkSsHUX(Wk;ShpWsBIgM|D3L1^LM<#|)u&98dO` zVa67)YEY=u&4S>;cTNHe>pDHGI^oeV4-eg!3$O)dc~E$4r1Km!4xzr* z@8=CsTKZGO!&4 zKZJf<`uL3C$(gZJu#=wzq)Rj&GG5E~fKLQvsjqjZ%@WAeoC75xPEL)+EXS;RwA@Ym zgpu;Ocm4=MH!JPaPfwEGMOPARy5sz4eqhiF@r&>({3pxK+5omGSy4AO^vD$bZb!}~N+Vuckyz)*8q612+x2yv zd^V;AU6_Ta#L}!7eRZ2_EMUcwPE-sAMnfngj@&l==1ORCagv_h8oX;;*=HAnh!TC)~5FwZU-&*?-!g&2+psFM(^3l?JIOtEM{+-@RhEv#roNGtUWiF zwbO<-1+YgQPZR7}O*{=O+c3<2PD5WP_k9Xa;$y4IZ~^73D|sd9iQ}?1lZ$`V)O}HS zfx2jB8=J+{w`sdM3Cjr-UA;x0>^s9A*rA@8g{p~1M*3vw<2IE_{c@iGrcZ0C`g+&+E!C%D>IZ?8P1sRU=FrTW z;%Z$=Ac1OGDnqYYTp{zWV>Z0Wkni1K!KF~f4&?oVW^nU7JmdsG7;rh)0)tf`nlzBDF zYuGAFG0XGSeY9Do7IZ5@jh$x7nNPJ;EmY=T)b#-~$v}kFzlxM`qg*C0=(eb>bRVa^ z1XlTcYCfMg9Zc~(W8_F^h45Mgh1&Tld~dA`-QW-`@!rg}LyfT;OStY;r?ph^pJ(Ii zvC6R>$L=34yiKz&u)eW_d`+*9xjnyH`h58C~!z?Uyomd|YnfM$W_ z|5bo4)ajFz(F7PjXr!#i9(m~HfoTC{pkoI{$L4Ny&;tX>Z#~{D^X>WsRf!QzQ3`b5xRzHiu7Rsl0)SgD3phXhee4rr(TH4;JRA)fIeRqkclDNPxDW^sjc== zuCrh-NH@2vq{Qo>UJrcr>eX!`k+{8m-V%vaZg2%zV5uJk>fCxqg}Z8NXGd6D16n)V zAcE<&cArIU9UZExR|jzq-kh`1b70Wg+??qyY?(>_0?N64&b<4a!`4PbCon1Sqv;(0 vv;1!j`diWdo$>*C{A2I`kr{f;a7I;gr8GzAZiW!hiR?K<4P5Z#^~e7MlQFQS literal 0 HcmV?d00001 diff --git a/docs/en/docs/img/tutorial/extending-openapi/image04.png b/docs/en/docs/img/tutorial/extending-openapi/image04.png new file mode 100644 index 0000000000000000000000000000000000000000..394d2bb431ab5eadaa919087ce1618f996bd829e GIT binary patch literal 11180 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 None: self._debug: bool = debug @@ -120,6 +121,7 @@ class FastAPI(Starlette): 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.extra = extra self.dependency_overrides: Dict[Callable[..., Any], Callable[..., Any]] = {} @@ -174,6 +176,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) diff --git a/fastapi/openapi/docs.py b/fastapi/openapi/docs.py index fd22e4e8..1be90d18 100644 --- a/fastapi/openapi/docs.py +++ b/fastapi/openapi/docs.py @@ -4,6 +4,14 @@ 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( *, @@ -14,7 +22,11 @@ def get_swagger_ui_html( 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/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"} From acbe79da371bc7e3673c86cafeb6d07ca64712f4 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 16 Jan 2022 19:27:03 +0000 Subject: [PATCH 165/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index b33e9dcf..2ed9729d 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ✨ Enable configuring Swagger UI parameters. PR [#2568](https://github.com/tiangolo/fastapi/pull/2568) by [@jmriebold](https://github.com/jmriebold). * 📝 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). * 🔧 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 From 5c62a59e7b99a49ce25c622747e510e211f22692 Mon Sep 17 00:00:00 2001 From: jaystone776 Date: Mon, 17 Jan 2022 03:34:28 +0800 Subject: [PATCH 166/196] =?UTF-8?q?=F0=9F=8C=90=20Add=20Chinese=20translat?= =?UTF-8?q?ion=20for=20`docs\tutorial\path-operation-configuration.md`=20(?= =?UTF-8?q?#3312)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- .../tutorial/path-operation-configuration.md | 101 ++++++++++++++++++ docs/zh/mkdocs.yml | 1 + 2 files changed, 102 insertions(+) create mode 100644 docs/zh/docs/tutorial/path-operation-configuration.md 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/mkdocs.yml b/docs/zh/mkdocs.yml index a929e338..1d050fdd 100644 --- a/docs/zh/mkdocs.yml +++ b/docs/zh/mkdocs.yml @@ -78,6 +78,7 @@ 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 From 436261b3ea75a095efbf67c4d537baa588331301 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 16 Jan 2022 19:35:00 +0000 Subject: [PATCH 167/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 2ed9729d..9066792d 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🌐 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). * ✨ Enable configuring Swagger UI parameters. PR [#2568](https://github.com/tiangolo/fastapi/pull/2568) by [@jmriebold](https://github.com/jmriebold). * 📝 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). * 🔧 Enable MkDocs Material Insiders' `content.tabs.link`. PR [#4399](https://github.com/tiangolo/fastapi/pull/4399) by [@tiangolo](https://github.com/tiangolo). From 5c5b889288f6330e2a64ab74c9bf50c95e829190 Mon Sep 17 00:00:00 2001 From: MicroPanda123 Date: Sun, 16 Jan 2022 19:36:42 +0000 Subject: [PATCH 168/196] =?UTF-8?q?=F0=9F=8C=90=20Add=20Polish=20translati?= =?UTF-8?q?on=20for=20`docs/pl/docs/index.md`=20(#4245)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dawid Dutkiewicz Co-authored-by: Dima Tisnek Co-authored-by: Bart Skowron Co-authored-by: Bart Skowron --- docs/pl/docs/index.md | 306 +++++++++++++++++++++--------------------- 1 file changed, 150 insertions(+), 156 deletions(-) diff --git a/docs/pl/docs/index.md b/docs/pl/docs/index.md index 95fb7ae2..4a300ae6 100644 --- a/docs/pl/docs/index.md +++ b/docs/pl/docs/index.md @@ -1,12 +1,8 @@ - -{!../../../docs/missing-translation.md!} - -

FastAPI

- 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,29 +18,28 @@ --- -**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. - -## Sponsors +## Sponsorzy @@ -59,9 +54,9 @@ The key features are: -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._" @@ -101,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

@@ -130,7 +125,7 @@ $ pip install fastapi
-You will also need an ASGI server, for production such as Uvicorn or Hypercorn. +Na serwerze produkcyjnym będziesz także potrzebował serwera ASGI, np. Uvicorn lub Hypercorn.
@@ -142,11 +137,11 @@ $ pip install uvicorn[standard]
-## Example +## Przykład -### Create it +### Stwórz -* Create a file `main.py` with: +* Utwórz plik o nazwie `main.py` z: ```Python from typing import Optional @@ -167,9 +162,9 @@ def read_item(item_id: int, q: Optional[str] = None): ```
-Or use async def... +Albo użyj async def... -If your code uses `async` / `await`, use `async def`: +Jeżeli twój kod korzysta z `async` / `await`, użyj `async def`: ```Python hl_lines="9 14" from typing import Optional @@ -189,15 +184,15 @@ async def read_item(item_id: int, q: Optional[str] = None): return {"item_id": item_id, "q": q} ``` -**Note**: +**Przypis**: -If you don't know, check the _"In a hurry?"_ section about `async` and `await` in the docs. +Jeżeli nie znasz, sprawdź sekcję _"In a hurry?"_ o `async` i `await` w dokumentacji.
-### Run it +### Uruchom -Run the server with: +Uruchom serwer używając:
@@ -214,54 +209,53 @@ INFO: Application startup complete.
-About the command uvicorn main:app --reload... +O komendzie uvicorn main:app --reload... +Komenda `uvicorn main:app` odnosi się do: -The command `uvicorn main:app` refers to: - -* `main`: the file `main.py` (the Python "module"). -* `app`: the object created inside of `main.py` with the line `app = FastAPI()`. -* `--reload`: make the server restart after code changes. Only do this for development. +* `main`: plik `main.py` ("moduł" w Pythonie). +* `app`: obiekt stworzony w `main.py` w lini `app = FastAPI()`. +* `--reload`: spraw by serwer resetował się po każdej zmianie w kodzie. Używaj tego tylko w środowisku deweloperskim.
-### Check it +### Wypróbuj -Open your browser at http://127.0.0.1:8000/items/5?q=somequery. +Otwórz link http://127.0.0.1:8000/items/5?q=somequery w przeglądarce. -You will see the JSON response as: +Zobaczysz następującą odpowiedź JSON: ```JSON {"item_id": 5, "q": "somequery"} ``` -You already created an API that: +Właśnie stworzyłeś API które: -* Receives HTTP requests in the _paths_ `/` and `/items/{item_id}`. -* Both _paths_ take `GET` operations (also known as HTTP _methods_). -* The _path_ `/items/{item_id}` has a _path parameter_ `item_id` that should be an `int`. -* The _path_ `/items/{item_id}` has an optional `str` _query parameter_ `q`. +* Otrzymuje żądania HTTP w _ścieżce_ `/` i `/items/{item_id}`. +* Obie _ścieżki_ używają operacji `GET` (znane także jako _metody_ HTTP). +* _Ścieżka_ `/items/{item_id}` ma _parametr ścieżki_ `item_id` który powinien być obiektem typu `int`. +* _Ścieżka_ `/items/{item_id}` ma opcjonalny _parametr zapytania_ typu `str` o nazwie `q`. -### Interactive API docs +### Interaktywna dokumentacja API -Now go to http://127.0.0.1:8000/docs. +Otwórz teraz stronę http://127.0.0.1:8000/docs. -You will see the automatic interactive API documentation (provided by Swagger UI): +Zobaczysz automatyczną interaktywną dokumentację API (dostarczoną z pomocą Swagger UI): ![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) -### Alternative API docs +### Alternatywna dokumentacja API -And now, go to http://127.0.0.1:8000/redoc. +Otwórz teraz http://127.0.0.1:8000/redoc. -You will see the alternative automatic documentation (provided by ReDoc): +Zobaczysz alternatywną, lecz wciąż automatyczną dokumentację (wygenerowaną z pomocą ReDoc): ![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) -## Example upgrade +## Aktualizacja przykładu -Now modify the file `main.py` to receive a body from a `PUT` request. +Zmodyfikuj teraz plik `main.py`, aby otrzmywał treść (body) żądania `PUT`. -Declare the body using standard Python types, thanks to Pydantic. +Zadeklaruj treść żądania, używając standardowych typów w Pythonie dzięki Pydantic. ```Python hl_lines="4 9-12 25-27" from typing import Optional @@ -293,175 +287,175 @@ def update_item(item_id: int, item: Item): return {"item_name": item.name, "item_id": item_id} ``` -The server should reload automatically (because you added `--reload` to the `uvicorn` command above). +Serwer powinien przeładować się automatycznie (ponieważ dodałeś `--reload` do komendy `uvicorn` powyżej). -### Interactive API docs upgrade +### Zaktualizowana interaktywna dokumentacja API -Now go to http://127.0.0.1:8000/docs. +Wejdź teraz na http://127.0.0.1:8000/docs. -* The interactive API documentation will be automatically updated, including the new body: +* Interaktywna dokumentacja API zaktualizuje sie automatycznie, także z nową treścią żądania (body): ![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) -* Click on the button "Try it out", it allows you to fill the parameters and directly interact with the API: +* Kliknij przycisk "Try it out" (wypróbuj), pozwoli Ci to wypełnić parametry i bezpośrednio użyć API: ![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) -* Then click on the "Execute" button, the user interface will communicate with your API, send the parameters, get the results and show them on the screen: +* Kliknij potem przycisk "Execute" (wykonaj), interfejs użytkownika połączy się z API, wyśle parametry, otrzyma odpowiedź i wyświetli ją na ekranie: ![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) -### Alternative API docs upgrade +### Zaktualizowana alternatywna dokumentacja API -And now, go to http://127.0.0.1:8000/redoc. +Otwórz teraz http://127.0.0.1:8000/redoc. -* The alternative documentation will also reflect the new query parameter and body: +* Alternatywna dokumentacja również pokaże zaktualizowane parametry i treść żądania (body): ![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) -### Recap +### Podsumowanie -In summary, you declare **once** the types of parameters, body, etc. as function parameters. +Podsumowując, musiałeś zadeklarować typy parametrów, treści żądania (body) itp. tylko **raz**, i są one dostępne jako parametry funkcji. -You do that with standard modern Python types. +Robisz to tak samo jak ze standardowymi typami w Pythonie. -You don't have to learn a new syntax, the methods or classes of a specific library, etc. +Nie musisz sie uczyć żadnej nowej składni, metod lub klas ze specyficznych bibliotek itp. -Just standard **Python 3.6+**. +Po prostu standardowy **Python 3.6+**. -For example, for an `int`: +Na przykład, dla danych typu `int`: ```Python item_id: int ``` -or for a more complex `Item` model: +albo dla bardziej złożonego obiektu `Item`: ```Python item: Item ``` -...and with that single declaration you get: +...i z pojedyńczą deklaracją otrzymujesz: -* Editor support, including: - * Completion. - * Type checks. -* Validation of data: - * Automatic and clear errors when the data is invalid. - * Validation even for deeply nested JSON objects. -* Conversion of input data: coming from the network to Python data and types. Reading from: +* Wsparcie edytorów kodu, wliczając: + * Auto-uzupełnianie. + * Sprawdzanie typów. +* Walidacja danych: + * Automatyczne i przejrzyste błędy gdy dane są niepoprawne. + * Walidacja nawet dla głęboko zagnieżdżonych obiektów JSON. +* Konwersja danych wejściowych: przychodzących z sieci na Pythonowe typy. Pozwala na przetwarzanie danych: * JSON. - * Path parameters. - * Query parameters. - * Cookies. - * Headers. - * Forms. - * Files. -* Conversion of output data: converting from Python data and types to network data (as JSON): - * Convert Python types (`str`, `int`, `float`, `bool`, `list`, etc). - * `datetime` objects. - * `UUID` objects. - * Database models. - * ...and many more. -* Automatic interactive API documentation, including 2 alternative user interfaces: + * Parametrów ścieżki. + * Parametrów zapytania. + * Dane cookies. + * Dane nagłówków (headers). + * Formularze. + * Pliki. +* Konwersja danych wyjściowych: wychodzących z Pythona do sieci (jako JSON): + * Przetwarzanie Pythonowych typów (`str`, `int`, `float`, `bool`, `list`, itp). + * Obiekty `datetime`. + * Obiekty `UUID`. + * Modele baz danych. + * ...i wiele więcej. +* Automatyczne interaktywne dokumentacje API, wliczając 2 alternatywne interfejsy użytkownika: * Swagger UI. * ReDoc. --- -Coming back to the previous code example, **FastAPI** will: +Wracając do poprzedniego przykładu, **FastAPI** : -* Validate that there is an `item_id` in the path for `GET` and `PUT` requests. -* Validate that the `item_id` is of type `int` for `GET` and `PUT` requests. - * If it is not, the client will see a useful, clear error. -* Check if there is an optional query parameter named `q` (as in `http://127.0.0.1:8000/items/foo?q=somequery`) for `GET` requests. - * As the `q` parameter is declared with `= None`, it is optional. - * Without the `None` it would be required (as is the body in the case with `PUT`). -* For `PUT` requests to `/items/{item_id}`, Read the body as JSON: - * Check that it has a required attribute `name` that should be a `str`. - * Check that it has a required attribute `price` that has to be a `float`. - * Check that it has an optional attribute `is_offer`, that should be a `bool`, if present. - * All this would also work for deeply nested JSON objects. -* Convert from and to JSON automatically. -* Document everything with OpenAPI, that can be used by: - * Interactive documentation systems. - * Automatic client code generation systems, for many languages. -* Provide 2 interactive documentation web interfaces directly. +* Potwierdzi, że w ścieżce jest `item_id` dla żądań `GET` i `PUT`. +* Potwierdzi, że `item_id` jest typu `int` dla żądań `GET` i `PUT`. + * Jeżeli nie jest, odbiorca zobaczy przydatną, przejrzystą wiadomość z błędem. +* Sprawdzi czy w ścieżce jest opcjonalny parametr zapytania `q` (np. `http://127.0.0.1:8000/items/foo?q=somequery`) dla żądania `GET`. + * Jako że parametr `q` jest zadeklarowany jako `= None`, jest on opcjonalny. + * Gdyby tego `None` nie było, parametr ten byłby wymagany (tak jak treść żądania w żądaniu `PUT`). +* Dla żądania `PUT` z ścieżką `/items/{item_id}`, odczyta treść żądania jako JSON: + * Sprawdzi czy posiada wymagany atrybut `name`, który powinien być typu `str`. + * Sprawdzi czy posiada wymagany atrybut `price`, który musi być typu `float`. + * Sprawdzi czy posiada opcjonalny atrybut `is_offer`, który (jeżeli obecny) powinien być typu `bool`. + * To wszystko będzie również działać dla głęboko zagnieżdżonych obiektów JSON. +* Automatycznie konwertuje z i do JSON. +* Dokumentuje wszystko w OpenAPI, które może być używane przez: + * Interaktywne systemy dokumentacji. + * Systemy automatycznego generowania kodu klienckiego, dla wielu języków. +* Dostarczy bezpośrednio 2 interaktywne dokumentacje webowe. --- -We just scratched the surface, but you already get the idea of how it all works. +To dopiero początek, ale już masz mniej-więcej pojęcie jak to wszystko działa. -Try changing the line with: +Spróbuj zmienić linijkę: ```Python return {"item_name": item.name, "item_id": item_id} ``` -...from: +...z: ```Python ... "item_name": item.name ... ``` -...to: +...na: ```Python ... "item_price": item.price ... ``` -...and see how your editor will auto-complete the attributes and know their types: +...i zobacz jak edytor kodu automatycznie uzupełni atrybuty i będzie znał ich typy: ![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) -For a more complete example including more features, see the Tutorial - User Guide. +Dla bardziej kompletnych przykładów posiadających więcej funkcjonalności, zobacz Tutorial - User Guide. -**Spoiler alert**: the tutorial - user guide includes: +**Uwaga Spoiler**: tutorial - user guide zawiera: -* Declaration of **parameters** from other different places as: **headers**, **cookies**, **form fields** and **files**. -* How to set **validation constraints** as `maximum_length` or `regex`. -* A very powerful and easy to use **Dependency Injection** system. -* Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth. -* More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic). -* Many extra features (thanks to Starlette) as: - * **WebSockets** +* Deklaracje **parametrów** z innych miejsc takich jak: **nagłówki**, **pliki cookies**, **formularze** i **pliki**. +* Jak ustawić **ograniczenia walidacyjne** takie jak `maksymalna długość` lub `regex`. +* Potężny i łatwy w użyciu system **Dependency Injection**. +* Zabezpieczenia i autentykacja, wliczając wsparcie dla **OAuth2** z **tokenami JWT** oraz autoryzacją **HTTP Basic**. +* Bardziej zaawansowane (ale równie proste) techniki deklarowania **głęboko zagnieżdżonych modeli JSON** (dzięki Pydantic). +* Wiele dodatkowych funkcji (dzięki Starlette) takie jak: + * **WebSockety** * **GraphQL** - * extremely easy tests based on `requests` and `pytest` + * bardzo proste testy bazujące na `requests` oraz `pytest` * **CORS** - * **Cookie Sessions** - * ...and more. + * **Sesje cookie** + * ...i więcej. -## Performance +## Wydajność -Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as one of the fastest Python frameworks available, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*) +Niezależne benchmarki TechEmpower pokazują, że **FastAPI** (uruchomiony na serwerze Uvicorn) jest jednym z najszybszych dostępnych Pythonowych frameworków, zaraz po Starlette i Uvicorn (używanymi wewnątrznie przez FastAPI). (*) -To understand more about it, see the section Benchmarks. +Aby dowiedzieć się o tym więcej, zobacz sekcję Benchmarks. -## Optional Dependencies +## Opcjonalne zależności -Used by Pydantic: +Używane przez Pydantic: -* 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. From 24968937e5a788fd15f801c705da9afba35c2517 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 16 Jan 2022 19:37:21 +0000 Subject: [PATCH 169/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 9066792d..658fd425 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🌐 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). * ✨ Enable configuring Swagger UI parameters. PR [#2568](https://github.com/tiangolo/fastapi/pull/2568) by [@jmriebold](https://github.com/jmriebold). * 📝 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). From 26e94116c12f46035ea3f4949c74d566aaa89116 Mon Sep 17 00:00:00 2001 From: kty4119 <49435654+kty4119@users.noreply.github.com> Date: Mon, 17 Jan 2022 04:41:13 +0900 Subject: [PATCH 170/196] =?UTF-8?q?=F0=9F=8C=90=20Fix=20Korean=20translati?= =?UTF-8?q?on=20for=20`docs/ko/docs/index.md`=20(#4195)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- docs/ko/docs/index.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/ko/docs/index.md b/docs/ko/docs/index.md index ee3edded..d0c23690 100644 --- a/docs/ko/docs/index.md +++ b/docs/ko/docs/index.md @@ -35,7 +35,7 @@ FastAPI는 현대적이고, 빠르며(고성능), 파이썬 표준 타입 힌트 * **직관적**: 훌륭한 편집기 지원. 모든 곳에서 자동완성. 적은 디버깅 시간. * **쉬움**: 쉽게 사용하고 배우도록 설계. 적은 문서 읽기 시간. * **짧음**: 코드 중복 최소화. 각 매개변수 선언의 여러 기능. 적은 버그. -* **견고함**: 준비된 프로덕션 용 코드를 얻으세요. 자동 대화형 문서와 함께. +* **견고함**: 준비된 프로덕션 용 코드를 얻으십시오. 자동 대화형 문서와 함께. * **표준 기반**: API에 대한 (완전히 호환되는) 개방형 표준 기반: OpenAPI (이전에 Swagger로 알려졌던) 및 JSON 스키마. * 내부 개발팀의 프로덕션 애플리케이션을 빌드한 테스트에 근거한 측정 @@ -89,9 +89,9 @@ FastAPI는 현대적이고, 빠르며(고성능), 파이썬 표준 타입 힌트 --- -"_REST API를 만들기 위해 **현대적인 프레임워크**를 찾고 있다면 **FastAPI**를 확인해 보세요. [...] 빠르고, 쓰기 쉽고, 배우기도 쉽습니다 [...]_" +"_REST API를 만들기 위해 **현대적인 프레임워크**를 찾고 있다면 **FastAPI**를 확인해 보십시오. [...] 빠르고, 쓰기 쉽고, 배우기도 쉽습니다 [...]_" -"_우리 **API**를 **FastAPI**로 바꿨습니다 [...] 아마 여러분도 좋아하실 겁니다 [...]_" +"_우리 **API**를 **FastAPI**로 바꿨습니다 [...] 아마 여러분도 좋아하실 것입니다 [...]_"
Ines Montani - Matthew Honnibal - Explosion AI 설립자 - spaCy 제작자 (ref) - (ref)
@@ -193,7 +193,7 @@ async def read_item(item_id: int, q: Optional[str] = None): ### 실행하기 -서버를 실행하세요: +서버를 실행하십시오:
@@ -239,7 +239,7 @@ INFO: Application startup complete. ### 대화형 API 문서 -이제 http://127.0.0.1:8000/docs로 가보세요. +이제 http://127.0.0.1:8000/docs로 가보십시오. 자동 대화형 API 문서를 볼 수 있습니다 (Swagger UI 제공): @@ -388,7 +388,7 @@ item: Item 우리는 그저 수박 겉핡기만 했을 뿐인데 여러분은 벌써 어떻게 작동하는지 알고 있습니다. -다음 줄을 바꿔보세요: +다음 줄을 바꿔보십시오: ```Python return {"item_name": item.name, "item_id": item_id} @@ -447,7 +447,7 @@ Starlette이 사용하는: * jinja2 - 기본 템플릿 설정을 사용하려면 필요. * python-multipart - `request.form()`과 함께 "parsing"의 지원을 원하면 필요. * itsdangerous - `SessionMiddleware` 지원을 위해 필요. -* pyyaml - Starlette의 `SchemaGenerator` 지원을 위해 필요 (FastAPI와 쓸때는 필요가 없을 겁니다). +* pyyaml - Starlette의 `SchemaGenerator` 지원을 위해 필요 (FastAPI와 쓸때는 필요 없을 것입니다). * graphene - `GraphQLApp` 지원을 위해 필요. * ujson - `UJSONResponse`를 사용하려면 필요. From d23b295b96ca9a4c9ebef381a111de4435acd222 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 16 Jan 2022 19:41:49 +0000 Subject: [PATCH 171/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 658fd425..15602b1e 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🌐 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). * ✨ Enable configuring Swagger UI parameters. PR [#2568](https://github.com/tiangolo/fastapi/pull/2568) by [@jmriebold](https://github.com/jmriebold). From e1c6d7d31083e3c637c352d36b7d9f8d93508b15 Mon Sep 17 00:00:00 2001 From: jaystone776 Date: Mon, 17 Jan 2022 03:41:59 +0800 Subject: [PATCH 172/196] =?UTF-8?q?=F0=9F=8C=90=20Update=20Chinese=20trans?= =?UTF-8?q?lation=20for=20`docs/help-fastapi.md`=20(#3847)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/zh/docs/help-fastapi.md | 164 ++++++++++++++++++++++------------- 1 file changed, 103 insertions(+), 61 deletions(-) diff --git a/docs/zh/docs/help-fastapi.md b/docs/zh/docs/help-fastapi.md index 99e37b7c..6f3f5159 100644 --- a/docs/zh/docs/help-fastapi.md +++ b/docs/zh/docs/help-fastapi.md @@ -1,107 +1,149 @@ -# 帮助 FastAPI - 获取帮助 +# 帮助 FastAPI 与求助 -你喜欢 **FastAPI** 吗? +您喜欢 **FastAPI** 吗? -您愿意去帮助 FastAPI,帮助其他用户以及作者吗? +想帮助 FastAPI?其它用户?还有项目作者? -或者你想要获得有关 **FastAPI** 的帮助? +或要求助怎么使用 **FastAPI**? -下面是一些非常简单的方式去提供帮助(有些只需单击一两次链接)。 +以下几种帮助的方式都非常简单(有些只需要点击一两下鼠标)。 -以及几种获取帮助的途径。 +求助的渠道也很多。 -## 在 GitHub 上 Star **FastAPI** +## 订阅新闻邮件 -你可以在 GitHub 上 "star" FastAPI(点击右上角的 star 按钮):https://github.com/tiangolo/fastapi。 +您可以订阅 [**FastAPI 和它的小伙伴** 新闻邮件](/newsletter/){.internal-link target=_blank}(不会经常收到) -通过添加 star,其他用户将会更容易发现 FastAPI,并了解已经有许多人认为它有用。 +* FastAPI 及其小伙伴的新闻 🚀 +* 指南 📝 +* 功能 ✨ +* 破坏性更改 🚨 +* 开发技巧 ✅ -## Watch GitHub 仓库的版本发布 +## 在推特上关注 FastAPI -你可以在 GitHub 上 "watch" FastAPI(点击右上角的 watch 按钮):https://github.com/tiangolo/fastapi。 +在 **Twitter** 上关注 @fastapi 获取 **FastAPI** 的最新消息。🐦 -这时你可以选择 "Releases only" 选项。 +## 在 GitHub 上为 **FastAPI** 加星 -之后,只要有 **FastAPI** 的新版本(包含缺陷修复和新功能)发布,你都会(通过电子邮件)收到通知。 +您可以在 GitHub 上 **Star** FastAPI(只要点击右上角的星星就可以了): https://github.com/tiangolo/fastapi。⭐️ -## 加入聊天室 +**Star** 以后,其它用户就能更容易找到 FastAPI,并了解到已经有其他用户在使用它了。 -加入 Gitter 上的聊天室:https://gitter.im/tiangolo/fastapi。 +## 关注 GitHub 资源库的版本发布 -在这里你可以快速提问、帮助他人、分享想法等。 +您还可以在 GitHub 上 **Watch** FastAPI,(点击右上角的 **Watch** 按钮)https://github.com/tiangolo/fastapi。👀 -## 与作者联系 +您可以选择只关注发布(**Releases only**)。 -你可以联系 我 (Sebastián Ramírez / `tiangolo`) - FastAPI 的作者。 +这样,您就可以(在电子邮件里)接收到 **FastAPI** 新版发布的通知,及时了解 bug 修复与新功能。 -你可以: +## 联系作者 -* 在 **GitHub** 上关注我。 - * 查看我创建的其他的可能对你有帮助的开源项目。 - * 关注我以了解我创建的新开源项目。 -* 在 **Twitter** 上关注我。 - * 告诉我你是如何使用 FastAPI 的(我很乐意听到)。 - * 提出问题。 -* 在 **Linkedin** 上联系我。 - * 与我交流。 - * 认可我的技能或推荐我 :) -* 在 **Medium** 上阅读我写的文章(或关注我)。 - * 阅读我创建的其他想法,文章和工具。 - * 关注我以了解我发布的新内容。 +您可以联系项目作者,就是我(Sebastián Ramírez / `tiangolo`)。 -## 发布和 **FastAPI** 有关的推特 +您可以: - 发布和 **FastAPI** 有关的推特 让我和其他人知道你为什么喜欢它。 +* 在 **GitHub** 上关注我 + * 了解其它我创建的开源项目,或许对您会有帮助 + * 关注我什么时候创建新的开源项目 +* 在 **Twitter** 上关注我 + * 告诉我您使用 FastAPI(我非常乐意听到这种消息) + * 接收我发布公告或新工具的消息 + * 您还可以关注@fastapi on Twitter,这是个独立的账号 +* 在**领英**上联系我 + * 接收我发布公告或新工具的消息(虽然我用 Twitter 比较多) +* 阅读我在 **Dev.to****Medium** 上的文章,或关注我 + * 阅读我的其它想法、文章,了解我创建的工具 + * 关注我,这样就可以随时看到我发布的新文章 -## 告诉我你正在如何使用 **FastAPI** +## Tweet about **FastAPI** -我很乐意听到有关 **FastAPI** 被如何使用、你喜欢它的哪一点、被投入使用的项目/公司等等信息。 +Tweet about **FastAPI** 让我和大家知道您为什么喜欢 FastAPI。🎉 -你可以通过以下平台让我知道: - -* **Twitter**。 -* **Linkedin**。 -* **Medium**。 +知道有人使用 **FastAPI**,我会很开心,我也想知道您为什么喜欢 FastAPI,以及您在什么项目/哪些公司使用 FastAPI,等等。 ## 为 FastAPI 投票 -* 在 Slant 上为 **FastAPI** 投票。 +* 在 Slant 上为 **FastAPI** 投票 +* 在 AlternativeTo 上为 **FastAPI** 投票 -## 帮助他人解决 GitHub 的 issues +## 在 GitHub 上帮助其他人解决问题 -你可以查看 已有的 issues 并尝试帮助其他人。 +您可以查看现有 issues,并尝试帮助其他人解决问题,说不定您能解决这些问题呢。🤓 -## Watch GitHub 仓库 +如果帮助很多人解决了问题,您就有可能成为 [FastAPI 的官方专家](fastapi-people.md#experts){.internal-link target=_blank}。🎉 -你可以在 GitHub 上 "watch" FastAPI(点击右上角的 "watch" 按钮):https://github.com/tiangolo/fastapi。 +## 监听 GitHub 资源库 -如果你选择的是 "Watching" 而不是 "Releases only" 选项,你会在其他人创建了新的 issue 时收到通知。 +您可以在 GitHub 上「监听」FastAPI(点击右上角的 "watch" 按钮): https://github.com/tiangolo/fastapi. 👀 -然后你可以尝试帮助他们解决这些 issue。 +如果您选择 "Watching" 而不是 "Releases only",有人创建新 Issue 时,您会接收到通知。 -## 创建 issue +然后您就可以尝试并帮助他们解决问题。 -你可以在 GitHub 仓库中 创建一个新 issue 用来: +## 创建 Issue -* 报告 bug 或问题。 -* 提议新的特性。 -* 提问。 +您可以在 GitHub 资源库中创建 Issue,例如: -## 创建 Pull Request +* 提出**问题**或**意见** +* 提出新**特性**建议 -你可以 创建一个 Pull Request 用来: +**注意**:如果您创建 Issue,我会要求您也要帮助别的用户。😉 -* 纠正你在文档中发现的错别字。 -* 添加新的文档内容。 -* 修复已有的 bug 或问题。 -* 添加新的特性。 +## 创建 PR + +您可以创建 PR 为源代码做[贡献](contributing.md){.internal-link target=_blank},例如: + +* 修改文档错别字 +* 编辑这个文件,分享 FastAPI 的文章、视频、博客,不论是您自己的,还是您看到的都成 + * 注意,添加的链接要放在对应区块的开头 +* [翻译文档](contributing.md#translations){.internal-link target=_blank} + * 审阅别人翻译的文档 +* 添加新的文档内容 +* 修复现有问题/Bug +* 添加新功能 + +## 加入聊天 + +快加入 👥 Discord 聊天服务器 👥 和 FastAPI 社区里的小伙伴一起哈皮吧。 + +!!! tip "提示" + + 如有问题,请在 GitHub Issues 里提问,在这里更容易得到 [FastAPI 专家](fastapi-people.md#experts){.internal-link target=_blank}的帮助。 + + 聊天室仅供闲聊。 + +我们之前还使用过 Gitter chat,但它不支持频道等高级功能,聊天也比较麻烦,所以现在推荐使用 Discord。 + +### 别在聊天室里提问 + +注意,聊天室更倾向于“闲聊”,经常有人会提出一些笼统得让人难以回答的问题,所以在这里提问一般没人回答。 + +GitHub Issues 里提供了模板,指引您提出正确的问题,有利于获得优质的回答,甚至可能解决您还没有想到的问题。而且就算答疑解惑要耗费不少时间,我还是会尽量在 GitHub 里回答问题。但在聊天室里,我就没功夫这么做了。😅 + +聊天室里的聊天内容也不如 GitHub 里好搜索,聊天里的问答很容易就找不到了。只有在 GitHub Issues 里的问答才能帮助您成为 [FastAPI 专家](fastapi-people.md#experts){.internal-link target=_blank},在 GitHub Issues 中为您带来更多关注。 + +另一方面,聊天室里有成千上万的用户,在这里,您有很大可能遇到聊得来的人。😄 ## 赞助作者 -你还可以通过 GitHub sponsors 在经济上支持作者(我)。 +您还可以通过 GitHub 赞助商资助本项目的作者(就是我)。 -这样你可以给我买杯咖啡☕️以示谢意😄。 +给我买杯咖啡 ☕️ 以示感谢 😄 + +当然您也可以成为 FastAPI 的金牌或银牌赞助商。🏅🎉 + +## 赞助 FastAPI 使用的工具 + +如您在本文档中所见,FastAPI 站在巨人的肩膀上,它们分别是 Starlette 和 Pydantic。 + +您还可以赞助: + +* Samuel Colvin (Pydantic) +* Encode (Starlette, Uvicorn) --- -感谢! +谢谢!🚀 + From 93e4a19e3526369b2a9a0c5ef71101833faa2987 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 16 Jan 2022 19:42:33 +0000 Subject: [PATCH 173/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 15602b1e..6ce21a8a 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🌐 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). From 9e2f5c67b603d73a77c420b629e2ee4e7378de1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 16 Jan 2022 21:08:04 +0100 Subject: [PATCH 174/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 6ce21a8a..090a344a 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,13 +2,25 @@ ## Latest Changes +### 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). -* ✨ Enable configuring Swagger UI parameters. PR [#2568](https://github.com/tiangolo/fastapi/pull/2568) by [@jmriebold](https://github.com/jmriebold). -* 📝 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). + +### 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 From f0388915a8b1cd9f3ae2259bace234ac6249c51a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 16 Jan 2022 21:09:10 +0100 Subject: [PATCH 175/196] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.72?= =?UTF-8?q?.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 3 +++ fastapi/__init__.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 090a344a..2dfb47c1 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,9 @@ ## Latest Changes + +## 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). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index 5b735aed..d83fe6fb 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.71.0" +__version__ = "0.72.0" from starlette import status as status From a75d0801241dc59590d928c48da7665856a52963 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 23 Jan 2022 15:56:14 +0100 Subject: [PATCH 176/196] =?UTF-8?q?=F0=9F=94=A7=20Add=20sponsor=20Dropbase?= =?UTF-8?q?=20(#4465)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + docs/en/data/sponsors.yml | 3 + docs/en/data/sponsors_badge.yml | 1 + docs/en/docs/img/sponsors/dropbase-banner.svg | 117 +++++++++++++++++ docs/en/docs/img/sponsors/dropbase.svg | 124 ++++++++++++++++++ docs/en/overrides/main.html | 6 + 6 files changed, 252 insertions(+) create mode 100644 docs/en/docs/img/sponsors/dropbase-banner.svg create mode 100644 docs/en/docs/img/sponsors/dropbase.svg diff --git a/README.md b/README.md index 53de38bd..c9c69d3e 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ The key features are: + diff --git a/docs/en/data/sponsors.yml b/docs/en/data/sponsors.yml index baa2e440..b98e68b6 100644 --- a/docs/en/data/sponsors.yml +++ b/docs/en/data/sponsors.yml @@ -5,6 +5,9 @@ gold: - 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://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 diff --git a/docs/en/data/sponsors_badge.yml b/docs/en/data/sponsors_badge.yml index 75974872..0c4e716d 100644 --- a/docs/en/data/sponsors_badge.yml +++ b/docs/en/data/sponsors_badge.yml @@ -7,3 +7,4 @@ logins: - koaning - deepset-ai - cryptapi + - DropbaseHQ diff --git a/docs/en/docs/img/sponsors/dropbase-banner.svg b/docs/en/docs/img/sponsors/dropbase-banner.svg new file mode 100644 index 00000000..d65abf1d --- /dev/null +++ b/docs/en/docs/img/sponsors/dropbase-banner.svg @@ -0,0 +1,117 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/en/docs/img/sponsors/dropbase.svg b/docs/en/docs/img/sponsors/dropbase.svg new file mode 100644 index 00000000..d0defb4d --- /dev/null +++ b/docs/en/docs/img/sponsors/dropbase.svg @@ -0,0 +1,124 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/en/overrides/main.html b/docs/en/overrides/main.html index 70b0253b..0f452b51 100644 --- a/docs/en/overrides/main.html +++ b/docs/en/overrides/main.html @@ -46,6 +46,12 @@
+
{% endblock %} From 347e391271e09244c3d95ac46dd5493a9b472ee4 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 23 Jan 2022 14:56:44 +0000 Subject: [PATCH 177/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 2dfb47c1..41fa8493 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🔧 Add sponsor Dropbase. PR [#4465](https://github.com/tiangolo/fastapi/pull/4465) by [@tiangolo](https://github.com/tiangolo). ## 0.72.0 From ca5d57ea799028d771101bd711ca87a301dd45d8 Mon Sep 17 00:00:00 2001 From: Mark Date: Sun, 23 Jan 2022 23:54:59 +0800 Subject: [PATCH 178/196] =?UTF-8?q?=E2=9C=A8=20Allow=20hiding=20from=20Ope?= =?UTF-8?q?nAPI=20(and=20Swagger=20UI)=20`Query`,=20`Cookie`,=20`Header`,?= =?UTF-8?q?=20and=20`Path`=20parameters=20(#3144)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- .../tutorial/query-params-str-validations.md | 16 ++ .../tutorial014.py | 15 ++ .../tutorial014_py310.py | 11 + fastapi/openapi/utils.py | 2 + fastapi/param_functions.py | 8 + fastapi/params.py | 10 + tests/test_param_include_in_schema.py | 239 ++++++++++++++++++ .../test_tutorial014.py | 82 ++++++ .../test_tutorial014_py310.py | 91 +++++++ 9 files changed, 474 insertions(+) create mode 100644 docs_src/query_params_str_validations/tutorial014.py create mode 100644 docs_src/query_params_str_validations/tutorial014_py310.py create mode 100644 tests/test_param_include_in_schema.py create mode 100644 tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py create mode 100644 tests/test_tutorial/test_query_params_str_validations/test_tutorial014_py310.py diff --git a/docs/en/docs/tutorial/query-params-str-validations.md b/docs/en/docs/tutorial/query-params-str-validations.md index fcac1a4e..ee62b971 100644 --- a/docs/en/docs/tutorial/query-params-str-validations.md +++ b/docs/en/docs/tutorial/query-params-str-validations.md @@ -387,6 +387,22 @@ 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_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/fastapi/openapi/utils.py b/fastapi/openapi/utils.py index 0e73e21b..aff76b15 100644 --- a/fastapi/openapi/utils.py +++ b/fastapi/openapi/utils.py @@ -92,6 +92,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, 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/tests/test_param_include_in_schema.py b/tests/test_param_include_in_schema.py new file mode 100644 index 00000000..4eaac72d --- /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": {"type": "string"}, + }, + "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_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..98ae5a68 --- /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": {"type": "string"}, + }, + "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..33f3d5f7 --- /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": {"type": "string"}, + }, + "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"} From 85518bc58b131ddc8d7f251c9349f473d194a9b2 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 23 Jan 2022 15:55:36 +0000 Subject: [PATCH 179/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 41fa8493..afd109d8 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ✨ 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). * 🔧 Add sponsor Dropbase. PR [#4465](https://github.com/tiangolo/fastapi/pull/4465) by [@tiangolo](https://github.com/tiangolo). ## 0.72.0 From 3de0fb82bf17fa4179caa38c3126786a7d99cf67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 23 Jan 2022 17:13:49 +0100 Subject: [PATCH 180/196] =?UTF-8?q?=F0=9F=90=9B=20Fix=20docs=20dependencie?= =?UTF-8?q?s=20cache,=20to=20get=20the=20latest=20Material=20for=20MkDocs?= =?UTF-8?q?=20(#4466)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build-docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index eba5fc57..ccf96448 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -20,7 +20,7 @@ jobs: id: cache with: path: ${{ env.pythonLocation }} - key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-docs + key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-docs-v2 - name: Install Flit if: steps.cache.outputs.cache-hit != 'true' run: python3.7 -m pip install flit From 699b5ef84198a352a332beea9953fe1db33315b6 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 23 Jan 2022 16:14:28 +0000 Subject: [PATCH 181/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index afd109d8..997fb752 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🐛 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). * ✨ 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). * 🔧 Add sponsor Dropbase. PR [#4465](https://github.com/tiangolo/fastapi/pull/4465) by [@tiangolo](https://github.com/tiangolo). From d4608a00cf4855021dfb1a780556e24dedc94b14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 23 Jan 2022 17:32:04 +0100 Subject: [PATCH 182/196] =?UTF-8?q?=F0=9F=90=9B=20Prefer=20custom=20encode?= =?UTF-8?q?r=20over=20defaults=20if=20specified=20in=20`jsonable=5Fencoder?= =?UTF-8?q?`=20(#4467)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Vivek Sunder --- fastapi/encoders.py | 18 +++++++++--------- tests/test_jsonable_encoder.py | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/fastapi/encoders.py b/fastapi/encoders.py index 3f599c9f..4b7ffe31 100644 --- a/fastapi/encoders.py +++ b/fastapi/encoders.py @@ -34,9 +34,17 @@ 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: + 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, dict)): @@ -118,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/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" From f4963f05bf4295e02cce1a28386712a5e6776fc4 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 23 Jan 2022 16:32:35 +0000 Subject: [PATCH 183/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 997fb752..49747539 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🐛 Prefer custom encoder over defaults if specified in `jsonable_encoder`. PR [#4467](https://github.com/tiangolo/fastapi/pull/4467) by [@tiangolo](https://github.com/tiangolo). * 🐛 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). * ✨ 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). * 🔧 Add sponsor Dropbase. PR [#4465](https://github.com/tiangolo/fastapi/pull/4465) by [@tiangolo](https://github.com/tiangolo). From 94ca8c1e290a9c587608d001d7b1ca76bd570ec8 Mon Sep 17 00:00:00 2001 From: Vivek Sunder Date: Sun, 23 Jan 2022 11:34:18 -0500 Subject: [PATCH 184/196] =?UTF-8?q?=F0=9F=90=9B=20Prefer=20custom=20encode?= =?UTF-8?q?r=20over=20defaults=20if=20specified=20in=20`jsonable=5Fencoder?= =?UTF-8?q?`=20(#2061)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez From 6215fdd39e54422561d51d7e6159c219053d41cb Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 23 Jan 2022 16:34:52 +0000 Subject: [PATCH 185/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 49747539..468d450a 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🐛 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). * 🐛 Prefer custom encoder over defaults if specified in `jsonable_encoder`. PR [#4467](https://github.com/tiangolo/fastapi/pull/4467) by [@tiangolo](https://github.com/tiangolo). * 🐛 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). * ✨ 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). From 0f8349fcb7c57921d28e78296a7dc8d0504459e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 23 Jan 2022 18:03:42 +0100 Subject: [PATCH 186/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 468d450a..54017ceb 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -3,7 +3,7 @@ ## Latest Changes * 🐛 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). -* 🐛 Prefer custom encoder over defaults if specified in `jsonable_encoder`. PR [#4467](https://github.com/tiangolo/fastapi/pull/4467) by [@tiangolo](https://github.com/tiangolo). + * 💚 Duplicate PR to trigger CI. PR [#4467](https://github.com/tiangolo/fastapi/pull/4467) by [@tiangolo](https://github.com/tiangolo). * 🐛 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). * ✨ 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). * 🔧 Add sponsor Dropbase. PR [#4465](https://github.com/tiangolo/fastapi/pull/4465) by [@tiangolo](https://github.com/tiangolo). From 569afb4378c80e0bff5dc4a45f26d012e498eda6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 23 Jan 2022 18:43:04 +0100 Subject: [PATCH 187/196] =?UTF-8?q?=E2=9C=A8=20Add=20support=20for=20tags?= =?UTF-8?q?=20with=20Enums=20(#4468)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tutorial/path-operation-configuration.md | 12 ++++ .../tutorial002b.py | 20 +++++++ fastapi/applications.py | 23 ++++---- fastapi/routing.py | 32 +++++------ .../test_tutorial002b.py | 56 +++++++++++++++++++ 5 files changed, 116 insertions(+), 27 deletions(-) create mode 100644 docs_src/path_operation_configuration/tutorial002b.py create mode 100644 tests/test_tutorial/test_path_operation_configurations/test_tutorial002b.py diff --git a/docs/en/docs/tutorial/path-operation-configuration.md b/docs/en/docs/tutorial/path-operation-configuration.md index 1ff448e7..884a762e 100644 --- a/docs/en/docs/tutorial/path-operation-configuration.md +++ b/docs/en/docs/tutorial/path-operation-configuration.md @@ -64,6 +64,18 @@ They will be added to the OpenAPI schema and used by the automatic documentation +### 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`: 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/fastapi/applications.py b/fastapi/applications.py index d71d4190..dbfd76fb 100644 --- a/fastapi/applications.py +++ b/fastapi/applications.py @@ -1,3 +1,4 @@ +from enum import Enum from typing import Any, Callable, Coroutine, Dict, List, Optional, Sequence, Type, Union from fastapi import routing @@ -219,7 +220,7 @@ class FastAPI(Starlette): *, response_model: Optional[Type[Any]] = None, status_code: Optional[int] = None, - tags: Optional[List[str]] = None, + tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[Depends]] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -273,7 +274,7 @@ class FastAPI(Starlette): *, response_model: Optional[Type[Any]] = None, status_code: Optional[int] = None, - tags: Optional[List[str]] = None, + tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[Depends]] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -342,7 +343,7 @@ 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, @@ -368,7 +369,7 @@ class FastAPI(Starlette): *, response_model: Optional[Type[Any]] = None, status_code: Optional[int] = None, - tags: Optional[List[str]] = None, + tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[Depends]] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -419,7 +420,7 @@ class FastAPI(Starlette): *, response_model: Optional[Type[Any]] = None, status_code: Optional[int] = None, - tags: Optional[List[str]] = None, + tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[Depends]] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -470,7 +471,7 @@ class FastAPI(Starlette): *, response_model: Optional[Type[Any]] = None, status_code: Optional[int] = None, - tags: Optional[List[str]] = None, + tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[Depends]] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -521,7 +522,7 @@ class FastAPI(Starlette): *, response_model: Optional[Type[Any]] = None, status_code: Optional[int] = None, - tags: Optional[List[str]] = None, + tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[Depends]] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -572,7 +573,7 @@ class FastAPI(Starlette): *, response_model: Optional[Type[Any]] = None, status_code: Optional[int] = None, - tags: Optional[List[str]] = None, + tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[Depends]] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -623,7 +624,7 @@ class FastAPI(Starlette): *, response_model: Optional[Type[Any]] = None, status_code: Optional[int] = None, - tags: Optional[List[str]] = None, + tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[Depends]] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -674,7 +675,7 @@ class FastAPI(Starlette): *, response_model: Optional[Type[Any]] = None, status_code: Optional[int] = None, - tags: Optional[List[str]] = None, + tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[Depends]] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -725,7 +726,7 @@ class FastAPI(Starlette): *, response_model: Optional[Type[Any]] = None, status_code: Optional[int] = None, - tags: Optional[List[str]] = None, + tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[Depends]] = None, summary: Optional[str] = None, description: Optional[str] = None, diff --git a/fastapi/routing.py b/fastapi/routing.py index 63ad7296..f6d5370d 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -1,9 +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, @@ -305,7 +305,7 @@ class APIRoute(routing.Route): *, response_model: Optional[Type[Any]] = None, status_code: Optional[int] = None, - tags: Optional[List[str]] = None, + tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[params.Depends]] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -330,7 +330,7 @@ class APIRoute(routing.Route): openapi_extra: Optional[Dict[str, Any]] = None, ) -> None: # normalise enums e.g. http.HTTPStatus - if isinstance(status_code, enum.IntEnum): + if isinstance(status_code, IntEnum): status_code = int(status_code) self.path = path self.endpoint = endpoint @@ -438,7 +438,7 @@ class APIRouter(routing.Router): 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, @@ -466,7 +466,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 @@ -483,7 +483,7 @@ class APIRouter(routing.Router): *, response_model: Optional[Type[Any]] = None, status_code: Optional[int] = None, - tags: Optional[List[str]] = None, + tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[params.Depends]] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -557,7 +557,7 @@ class APIRouter(routing.Router): *, response_model: Optional[Type[Any]] = None, status_code: Optional[int] = None, - tags: Optional[List[str]] = None, + tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[params.Depends]] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -634,7 +634,7 @@ 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, @@ -738,7 +738,7 @@ class APIRouter(routing.Router): *, response_model: Optional[Type[Any]] = None, status_code: Optional[int] = None, - tags: Optional[List[str]] = None, + tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[params.Depends]] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -790,7 +790,7 @@ class APIRouter(routing.Router): *, response_model: Optional[Type[Any]] = None, status_code: Optional[int] = None, - tags: Optional[List[str]] = None, + tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[params.Depends]] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -842,7 +842,7 @@ class APIRouter(routing.Router): *, response_model: Optional[Type[Any]] = None, status_code: Optional[int] = None, - tags: Optional[List[str]] = None, + tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[params.Depends]] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -894,7 +894,7 @@ class APIRouter(routing.Router): *, response_model: Optional[Type[Any]] = None, status_code: Optional[int] = None, - tags: Optional[List[str]] = None, + tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[params.Depends]] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -946,7 +946,7 @@ class APIRouter(routing.Router): *, response_model: Optional[Type[Any]] = None, status_code: Optional[int] = None, - tags: Optional[List[str]] = None, + tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[params.Depends]] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -998,7 +998,7 @@ class APIRouter(routing.Router): *, response_model: Optional[Type[Any]] = None, status_code: Optional[int] = None, - tags: Optional[List[str]] = None, + tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[params.Depends]] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -1050,7 +1050,7 @@ class APIRouter(routing.Router): *, response_model: Optional[Type[Any]] = None, status_code: Optional[int] = None, - tags: Optional[List[str]] = None, + tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[params.Depends]] = None, summary: Optional[str] = None, description: Optional[str] = None, @@ -1102,7 +1102,7 @@ class APIRouter(routing.Router): *, response_model: Optional[Type[Any]] = None, status_code: Optional[int] = None, - tags: Optional[List[str]] = None, + tags: Optional[List[Union[str, Enum]]] = None, dependencies: Optional[Sequence[params.Depends]] = None, summary: Optional[str] = None, description: Optional[str] = None, 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"] From 59b1f353b3fe77cf801242e3d120372ad8519710 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 23 Jan 2022 17:43:36 +0000 Subject: [PATCH 188/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 54017ceb..7026d0eb 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ✨ Add support for tags with Enums. PR [#4468](https://github.com/tiangolo/fastapi/pull/4468) by [@tiangolo](https://github.com/tiangolo). * 🐛 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). * 🐛 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). From 1bf55200a90b04229f665cd2ee83edde91e936e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 23 Jan 2022 20:14:13 +0100 Subject: [PATCH 189/196] =?UTF-8?q?=E2=9C=A8=20Add=20support=20for=20decla?= =?UTF-8?q?ring=20`UploadFile`=20parameters=20without=20explicit=20`File()?= =?UTF-8?q?`=20(#4469)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/tutorial/request-files.md | 47 +++- docs_src/request_files/tutorial001.py | 2 +- docs_src/request_files/tutorial001_02.py | 21 ++ .../request_files/tutorial001_02_py310.py | 19 ++ docs_src/request_files/tutorial001_03.py | 15 ++ docs_src/request_files/tutorial002.py | 2 +- docs_src/request_files/tutorial002_py39.py | 2 +- docs_src/request_files/tutorial003.py | 37 +++ docs_src/request_files/tutorial003_py39.py | 35 +++ fastapi/datastructures.py | 6 +- fastapi/dependencies/utils.py | 28 +-- .../test_request_files/test_tutorial001_02.py | 157 ++++++++++++ .../test_tutorial001_02_py310.py | 169 +++++++++++++ .../test_request_files/test_tutorial001_03.py | 159 +++++++++++++ .../test_request_files/test_tutorial003.py | 194 +++++++++++++++ .../test_tutorial003_py39.py | 223 ++++++++++++++++++ 16 files changed, 1086 insertions(+), 30 deletions(-) create mode 100644 docs_src/request_files/tutorial001_02.py create mode 100644 docs_src/request_files/tutorial001_02_py310.py create mode 100644 docs_src/request_files/tutorial001_03.py create mode 100644 docs_src/request_files/tutorial003.py create mode 100644 docs_src/request_files/tutorial003_py39.py create mode 100644 tests/test_tutorial/test_request_files/test_tutorial001_02.py create mode 100644 tests/test_tutorial/test_request_files/test_tutorial001_02_py310.py create mode 100644 tests/test_tutorial/test_request_files/test_tutorial001_03.py create mode 100644 tests/test_tutorial/test_request_files/test_tutorial003.py create mode 100644 tests/test_tutorial/test_request_files/test_tutorial003_py39.py diff --git a/docs/en/docs/tutorial/request-files.md b/docs/en/docs/tutorial/request-files.md index b7257c7e..ed2c8b6a 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,7 +41,7 @@ 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`: @@ -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. * 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,7 +114,31 @@ 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: + +=== "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` to set additional parameters in `File()`, for example 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. @@ -140,6 +165,22 @@ You will receive, as declared, a `list` of `bytes` or `UploadFile`s. **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). 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 index 26cd5676..2779618b 100644 --- a/docs_src/request_files/tutorial002_py39.py +++ b/docs_src/request_files/tutorial002_py39.py @@ -10,7 +10,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/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/fastapi/datastructures.py b/fastapi/datastructures.py index b1317128..b20a25ab 100644 --- a/fastapi/datastructures.py +++ b/fastapi/datastructures.py @@ -1,4 +1,4 @@ -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 @@ -20,6 +20,10 @@ 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: """ diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index 35ba44aa..d4028d06 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -390,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 @@ -701,25 +703,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: @@ -729,9 +712,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 @@ -740,7 +722,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/tests/test_tutorial/test_request_files/test_tutorial001_02.py b/tests/test_tutorial/test_request_files/test_tutorial001_02.py new file mode 100644 index 00000000..e852a1b3 --- /dev/null +++ b/tests/test_tutorial/test_request_files/test_tutorial001_02.py @@ -0,0 +1,157 @@ +from fastapi.testclient import TestClient + +from docs_src.request_files.tutorial001_02 import app + +client = TestClient(app) + +openapi_schema = { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/files/": { + "post": { + "summary": "Create File", + "operationId": "create_file_files__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_create_file_files__post" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/uploadfile/": { + "post": { + "summary": "Create Upload File", + "operationId": "create_upload_file_uploadfile__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "Body_create_file_files__post": { + "title": "Body_create_file_files__post", + "type": "object", + "properties": { + "file": {"title": "File", "type": "string", "format": "binary"} + }, + }, + "Body_create_upload_file_uploadfile__post": { + "title": "Body_create_upload_file_uploadfile__post", + "type": "object", + "properties": { + "file": {"title": "File", "type": "string", "format": "binary"} + }, + }, + "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": {"type": "string"}, + }, + "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_post_form_no_body(): + response = client.post("/files/") + assert response.status_code == 200, response.text + assert response.json() == {"message": "No file sent"} + + +def test_post_uploadfile_no_body(): + response = client.post("/uploadfile/") + assert response.status_code == 200, response.text + assert response.json() == {"message": "No upload file sent"} + + +def test_post_file(tmp_path): + path = tmp_path / "test.txt" + path.write_bytes(b"") + + client = TestClient(app) + with path.open("rb") as file: + response = client.post("/files/", files={"file": file}) + assert response.status_code == 200, response.text + assert response.json() == {"file_size": 14} + + +def test_post_upload_file(tmp_path): + path = tmp_path / "test.txt" + path.write_bytes(b"") + + client = TestClient(app) + with path.open("rb") as file: + response = client.post("/uploadfile/", files={"file": file}) + assert response.status_code == 200, response.text + assert response.json() == {"filename": "test.txt"} diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_02_py310.py b/tests/test_tutorial/test_request_files/test_tutorial001_02_py310.py new file mode 100644 index 00000000..62e9f98d --- /dev/null +++ b/tests/test_tutorial/test_request_files/test_tutorial001_02_py310.py @@ -0,0 +1,169 @@ +from pathlib import Path + +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": { + "/files/": { + "post": { + "summary": "Create File", + "operationId": "create_file_files__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_create_file_files__post" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/uploadfile/": { + "post": { + "summary": "Create Upload File", + "operationId": "create_upload_file_uploadfile__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "Body_create_file_files__post": { + "title": "Body_create_file_files__post", + "type": "object", + "properties": { + "file": {"title": "File", "type": "string", "format": "binary"} + }, + }, + "Body_create_upload_file_uploadfile__post": { + "title": "Body_create_upload_file_uploadfile__post", + "type": "object", + "properties": { + "file": {"title": "File", "type": "string", "format": "binary"} + }, + }, + "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": {"type": "string"}, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, +} + + +@pytest.fixture(name="client") +def get_client(): + from docs_src.request_files.tutorial001_02_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_post_form_no_body(client: TestClient): + response = client.post("/files/") + assert response.status_code == 200, response.text + assert response.json() == {"message": "No file sent"} + + +@needs_py310 +def test_post_uploadfile_no_body(client: TestClient): + response = client.post("/uploadfile/") + assert response.status_code == 200, response.text + assert response.json() == {"message": "No upload file sent"} + + +@needs_py310 +def test_post_file(tmp_path: Path, client: TestClient): + path = tmp_path / "test.txt" + path.write_bytes(b"") + + with path.open("rb") as file: + response = client.post("/files/", files={"file": file}) + assert response.status_code == 200, response.text + assert response.json() == {"file_size": 14} + + +@needs_py310 +def test_post_upload_file(tmp_path: Path, client: TestClient): + path = tmp_path / "test.txt" + path.write_bytes(b"") + + with path.open("rb") as file: + response = client.post("/uploadfile/", files={"file": file}) + assert response.status_code == 200, response.text + assert response.json() == {"filename": "test.txt"} diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_03.py b/tests/test_tutorial/test_request_files/test_tutorial001_03.py new file mode 100644 index 00000000..ec7509ea --- /dev/null +++ b/tests/test_tutorial/test_request_files/test_tutorial001_03.py @@ -0,0 +1,159 @@ +from fastapi.testclient import TestClient + +from docs_src.request_files.tutorial001_03 import app + +client = TestClient(app) + +openapi_schema = { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/files/": { + "post": { + "summary": "Create File", + "operationId": "create_file_files__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_create_file_files__post" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/uploadfile/": { + "post": { + "summary": "Create Upload File", + "operationId": "create_upload_file_uploadfile__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__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_create_file_files__post": { + "title": "Body_create_file_files__post", + "required": ["file"], + "type": "object", + "properties": { + "file": { + "title": "File", + "type": "string", + "description": "A file read as bytes", + "format": "binary", + } + }, + }, + "Body_create_upload_file_uploadfile__post": { + "title": "Body_create_upload_file_uploadfile__post", + "required": ["file"], + "type": "object", + "properties": { + "file": { + "title": "File", + "type": "string", + "description": "A file read as UploadFile", + "format": "binary", + } + }, + }, + "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": {"type": "string"}, + }, + "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_post_file(tmp_path): + path = tmp_path / "test.txt" + path.write_bytes(b"") + + client = TestClient(app) + with path.open("rb") as file: + response = client.post("/files/", files={"file": file}) + assert response.status_code == 200, response.text + assert response.json() == {"file_size": 14} + + +def test_post_upload_file(tmp_path): + path = tmp_path / "test.txt" + path.write_bytes(b"") + + client = TestClient(app) + with path.open("rb") as file: + response = client.post("/uploadfile/", files={"file": file}) + assert response.status_code == 200, response.text + assert response.json() == {"filename": "test.txt"} diff --git a/tests/test_tutorial/test_request_files/test_tutorial003.py b/tests/test_tutorial/test_request_files/test_tutorial003.py new file mode 100644 index 00000000..943b235a --- /dev/null +++ b/tests/test_tutorial/test_request_files/test_tutorial003.py @@ -0,0 +1,194 @@ +from fastapi.testclient import TestClient + +from docs_src.request_files.tutorial003 import app + +client = TestClient(app) + +openapi_schema = { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/files/": { + "post": { + "summary": "Create Files", + "operationId": "create_files_files__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_create_files_files__post" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/uploadfiles/": { + "post": { + "summary": "Create Upload Files", + "operationId": "create_upload_files_uploadfiles__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_create_upload_files_uploadfiles__post" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/": { + "get": { + "summary": "Main", + "operationId": "main__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + }, + }, + "components": { + "schemas": { + "Body_create_files_files__post": { + "title": "Body_create_files_files__post", + "required": ["files"], + "type": "object", + "properties": { + "files": { + "title": "Files", + "type": "array", + "items": {"type": "string", "format": "binary"}, + "description": "Multiple files as bytes", + } + }, + }, + "Body_create_upload_files_uploadfiles__post": { + "title": "Body_create_upload_files_uploadfiles__post", + "required": ["files"], + "type": "object", + "properties": { + "files": { + "title": "Files", + "type": "array", + "items": {"type": "string", "format": "binary"}, + "description": "Multiple files as UploadFile", + } + }, + }, + "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": {"type": "string"}, + }, + "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_post_files(tmp_path): + path = tmp_path / "test.txt" + path.write_bytes(b"") + path2 = tmp_path / "test2.txt" + path2.write_bytes(b"") + + client = TestClient(app) + with path.open("rb") as file, path2.open("rb") as file2: + response = client.post( + "/files/", + files=( + ("files", ("test.txt", file)), + ("files", ("test2.txt", file2)), + ), + ) + assert response.status_code == 200, response.text + assert response.json() == {"file_sizes": [14, 15]} + + +def test_post_upload_file(tmp_path): + path = tmp_path / "test.txt" + path.write_bytes(b"") + path2 = tmp_path / "test2.txt" + path2.write_bytes(b"") + + client = TestClient(app) + with path.open("rb") as file, path2.open("rb") as file2: + response = client.post( + "/uploadfiles/", + files=( + ("files", ("test.txt", file)), + ("files", ("test2.txt", file2)), + ), + ) + assert response.status_code == 200, response.text + assert response.json() == {"filenames": ["test.txt", "test2.txt"]} + + +def test_get_root(): + client = TestClient(app) + response = client.get("/") + assert response.status_code == 200, response.text + assert b"") + path2 = tmp_path / "test2.txt" + path2.write_bytes(b"") + + client = TestClient(app) + with path.open("rb") as file, path2.open("rb") as file2: + response = client.post( + "/files/", + files=( + ("files", ("test.txt", file)), + ("files", ("test2.txt", file2)), + ), + ) + assert response.status_code == 200, response.text + assert response.json() == {"file_sizes": [14, 15]} + + +@needs_py39 +def test_post_upload_file(tmp_path, app: FastAPI): + path = tmp_path / "test.txt" + path.write_bytes(b"") + path2 = tmp_path / "test2.txt" + path2.write_bytes(b"") + + client = TestClient(app) + with path.open("rb") as file, path2.open("rb") as file2: + response = client.post( + "/uploadfiles/", + files=( + ("files", ("test.txt", file)), + ("files", ("test2.txt", file2)), + ), + ) + assert response.status_code == 200, response.text + assert response.json() == {"filenames": ["test.txt", "test2.txt"]} + + +@needs_py39 +def test_get_root(app: FastAPI): + client = TestClient(app) + response = client.get("/") + assert response.status_code == 200, response.text + assert b" Date: Sun, 23 Jan 2022 19:14:47 +0000 Subject: [PATCH 190/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 7026d0eb..b7593ee3 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* ✨ Add support for declaring `UploadFile` parameters without explicit `File()`. PR [#4469](https://github.com/tiangolo/fastapi/pull/4469) by [@tiangolo](https://github.com/tiangolo). * ✨ Add support for tags with Enums. PR [#4468](https://github.com/tiangolo/fastapi/pull/4468) by [@tiangolo](https://github.com/tiangolo). * 🐛 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). From f8d4d040155a58ecfdbc2ed58f0739b07e417516 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 23 Jan 2022 22:30:35 +0100 Subject: [PATCH 191/196] =?UTF-8?q?=F0=9F=93=9D=20Tweak=20and=20improve=20?= =?UTF-8?q?docs=20for=20Request=20Files=20(#4470)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/tutorial/request-files.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/en/docs/tutorial/request-files.md b/docs/en/docs/tutorial/request-files.md index ed2c8b6a..3ca471a9 100644 --- a/docs/en/docs/tutorial/request-files.md +++ b/docs/en/docs/tutorial/request-files.md @@ -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,7 +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. +* 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. @@ -116,7 +116,7 @@ The way HTML forms (`
`) sends the data to the server normally uses ## Optional File Upload -You can make a file optional by using standard type annotations: +You can make a file optional by using standard type annotations and setting a default value of `None`: === "Python 3.6 and above" @@ -132,7 +132,7 @@ You can make a file optional by using standard type annotations: ## `UploadFile` with Additional Metadata -You can also use `File()` with `UploadFile` to set additional parameters in `File()`, for example 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!} @@ -183,4 +183,4 @@ And the same way as before, you can use `File()` to set additional parameters, e ## 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. From dba9ea81208078bdd91fbfff4fdbfe203dcc303f Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 23 Jan 2022 21:31:08 +0000 Subject: [PATCH 192/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index b7593ee3..84908dda 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 📝 Tweak and improve docs for Request Files. PR [#4470](https://github.com/tiangolo/fastapi/pull/4470) by [@tiangolo](https://github.com/tiangolo). * ✨ Add support for declaring `UploadFile` parameters without explicit `File()`. PR [#4469](https://github.com/tiangolo/fastapi/pull/4469) by [@tiangolo](https://github.com/tiangolo). * ✨ Add support for tags with Enums. PR [#4468](https://github.com/tiangolo/fastapi/pull/4468) by [@tiangolo](https://github.com/tiangolo). * 🐛 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). From a698908ed65d887a17245a580ecbf3bf3c848406 Mon Sep 17 00:00:00 2001 From: Victor Benichoux Date: Sun, 23 Jan 2022 23:13:55 +0100 Subject: [PATCH 193/196] =?UTF-8?q?=F0=9F=90=9B=20Fix=20bug=20preventing?= =?UTF-8?q?=20to=20use=20OpenAPI=20when=20using=20tuples=20(#3874)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- fastapi/openapi/models.py | 2 +- tests/test_tuples.py | 267 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 268 insertions(+), 1 deletion(-) create mode 100644 tests/test_tuples.py diff --git a/fastapi/openapi/models.py b/fastapi/openapi/models.py index 361c7500..9c6598d2 100644 --- a/fastapi/openapi/models.py +++ b/fastapi/openapi/models.py @@ -123,7 +123,7 @@ class Schema(BaseModel): oneOf: Optional[List["Schema"]] = None anyOf: Optional[List["Schema"]] = None not_: Optional["Schema"] = Field(None, alias="not") - items: Optional["Schema"] = None + items: Optional[Union["Schema", List["Schema"]]] = None properties: Optional[Dict[str, "Schema"]] = None additionalProperties: Optional[Union["Schema", Reference, bool]] = None description: Optional[str] = None diff --git a/tests/test_tuples.py b/tests/test_tuples.py new file mode 100644 index 00000000..4cd5ee3a --- /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": {"type": "string"}, + }, + "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 From af18d5c49fde32e79e4dfd7a82819a2c642c6c17 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 23 Jan 2022 22:14:28 +0000 Subject: [PATCH 194/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 84908dda..f9f3aabd 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,7 @@ ## Latest Changes +* 🐛 Fix bug preventing to use OpenAPI when using tuples. PR [#3874](https://github.com/tiangolo/fastapi/pull/3874) by [@victorbenichoux](https://github.com/victorbenichoux). * 📝 Tweak and improve docs for Request Files. PR [#4470](https://github.com/tiangolo/fastapi/pull/4470) by [@tiangolo](https://github.com/tiangolo). * ✨ Add support for declaring `UploadFile` parameters without explicit `File()`. PR [#4469](https://github.com/tiangolo/fastapi/pull/4469) by [@tiangolo](https://github.com/tiangolo). * ✨ Add support for tags with Enums. PR [#4468](https://github.com/tiangolo/fastapi/pull/4468) by [@tiangolo](https://github.com/tiangolo). From cbe8d552c1eee5b48f8ab0eab6b517e98ae8523b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 23 Jan 2022 23:37:48 +0100 Subject: [PATCH 195/196] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index f9f3aabd..e75d4670 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,14 +2,25 @@ ## Latest Changes -* 🐛 Fix bug preventing to use OpenAPI when using tuples. PR [#3874](https://github.com/tiangolo/fastapi/pull/3874) by [@victorbenichoux](https://github.com/victorbenichoux). +### 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). -* ✨ Add support for declaring `UploadFile` parameters without explicit `File()`. PR [#4469](https://github.com/tiangolo/fastapi/pull/4469) by [@tiangolo](https://github.com/tiangolo). -* ✨ Add support for tags with Enums. PR [#4468](https://github.com/tiangolo/fastapi/pull/4468) 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). -* ✨ 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). * 🔧 Add sponsor Dropbase. PR [#4465](https://github.com/tiangolo/fastapi/pull/4465) by [@tiangolo](https://github.com/tiangolo). ## 0.72.0 From 291180bf2d8c39e84860c2426b1d58b6c80f6fef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 23 Jan 2022 23:38:51 +0100 Subject: [PATCH 196/196] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.73?= =?UTF-8?q?.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 ++ fastapi/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index e75d4670..68b75e70 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,8 @@ ## Latest Changes +## 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). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index d83fe6fb..8718788f 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.72.0" +__version__ = "0.73.0" from starlette import status as status