+
+### Tipleri ekle
+
+Önceki sürümden sadece bir satırı değiştirelim.
+
+Tam olarak bu parçayı, işlevin parametrelerini değiştireceğiz:
+
+```Python
+ first_name, last_name
+```
+
+ve bu hale getireceğiz:
+
+```Python
+ first_name: str, last_name: str
+```
+
+Bu kadar.
+
+İşte bunlar "tip belirteçleri":
+
+```Python hl_lines="1"
+{!../../../docs_src/python_types/tutorial002.py!}
+```
+
+Bu, aşağıdaki gibi varsayılan değerleri bildirmekle aynı şey değildir:
+
+```Python
+ first_name="john", last_name="doe"
+```
+
+Bu tamamen farklı birşey
+
+İki nokta üst üste (`:`) kullanıyoruz , eşittir (`=`) değil.
+
+Normalde tip belirteçleri eklemek, kod üzerinde olacakları değiştirmez.
+
+Şimdi programı sıfırdan birdaha yazdığınızı hayal edin.
+
+Aynı noktada, `Ctrl+Space` ile otomatik tamamlamayı tetiklediniz ve şunu görüyorsunuz:
+
+
+
+Aradığınızı bulana kadar seçenekleri kaydırabilirsiniz:
+
+
+
+## Daha fazla motivasyon
+
+Bu fonksiyon, zaten tür belirteçlerine sahip:
+
+```Python hl_lines="1"
+{!../../../docs_src/python_types/tutorial003.py!}
+```
+
+Editör değişkenlerin tiplerini bildiğinden, yalnızca otomatik tamamlama değil, hata kontrolleri de sağlar:
+
+
+
+Artık `age` değişkenini `str(age)` olarak kullanmanız gerektiğini biliyorsunuz:
+
+```Python hl_lines="2"
+{!../../../docs_src/python_types/tutorial004.py!}
+```
+
+## Tip bildirme
+
+Az önce tip belirteçlerinin en çok kullanıldığı yeri gördünüz.
+
+ **FastAPI**ile çalışırken tip belirteçlerini en çok kullanacağımız yer yine fonksiyonlardır.
+
+### Basit tipler
+
+Yalnızca `str` değil, tüm standart Python tiplerinin bildirebilirsiniz.
+
+Örneğin şunları kullanabilirsiniz:
+
+* `int`
+* `float`
+* `bool`
+* `bytes`
+
+```Python hl_lines="1"
+{!../../../docs_src/python_types/tutorial005.py!}
+```
+
+### Tip parametreleri ile Generic tipler
+
+"dict", "list", "set" ve "tuple" gibi diğer değerleri içerebilen bazı veri yapıları vardır. Ve dahili değerlerinin de tip belirtecleri olabilir.
+
+Bu tipleri ve dahili tpileri bildirmek için standart Python modülünü "typing" kullanabilirsiniz.
+
+Bu tür tip belirteçlerini desteklemek için özel olarak mevcuttur.
+
+#### `List`
+
+Örneğin `str` değerlerden oluşan bir `list` tanımlayalım.
+
+From `typing`, import `List` (büyük harf olan `L` ile):
+
+```Python hl_lines="1"
+{!../../../docs_src/python_types/tutorial006.py!}
+```
+
+Değişkenin tipini yine iki nokta üstüste (`:`) ile belirleyin.
+
+tip olarak `List` kullanın.
+
+Liste, bazı dahili tipleri içeren bir tür olduğundan, bunları köşeli parantez içine alırsınız:
+
+```Python hl_lines="4"
+{!../../../docs_src/python_types/tutorial006.py!}
+```
+
+!!! ipucu
+ Köşeli parantez içindeki bu dahili tiplere "tip parametreleri" denir.
+
+ Bu durumda `str`, `List`e iletilen tür parametresidir.
+
+Bunun anlamı şudur: "`items` değişkeni bir `list`tir ve bu listedeki öğelerin her biri bir `str`dir".
+
+Bunu yaparak, düzenleyicinizin listedeki öğeleri işlerken bile destek sağlamasını sağlayabilirsiniz:
+
+
+
+Tip belirteçleri olmadan, bunu başarmak neredeyse imkansızdır.
+
+`item` değişkeninin `items` listesindeki öğelerden biri olduğuna dikkat edin.
+
+Ve yine, editör bunun bir `str` olduğunu biliyor ve bunun için destek sağlıyor.
+
+#### `Tuple` ve `Set`
+
+`Tuple` ve `set`lerin tiplerini bildirmek için de aynısını yapıyoruz:
+
+```Python hl_lines="1 4"
+{!../../../docs_src/python_types/tutorial007.py!}
+```
+
+Bu şu anlama geliyor:
+
+* `items_t` değişkeni sırasıyla `int`, `int`, ve `str` tiplerinden oluşan bir `tuple` türündedir .
+* `items_s` ise her öğesi `bytes` türünde olan bir `set` örneğidir.
+
+#### `Dict`
+
+Bir `dict` tanımlamak için virgülle ayrılmış iki parametre verebilirsiniz.
+
+İlk tip parametresi `dict` değerinin `key` değeri içindir.
+
+İkinci parametre ise `dict` değerinin `value` değeri içindir:
+
+```Python hl_lines="1 4"
+{!../../../docs_src/python_types/tutorial008.py!}
+```
+
+Bu şu anlama gelir:
+
+* `prices` değişkeni `dict` tipindedir:
+ * `dict` değişkeninin `key` değeri `str` tipindedir (herbir item'ın "name" değeri).
+ * `dict` değişkeninin `value` değeri `float` tipindedir (lherbir item'ın "price" değeri).
+
+#### `Optional`
+
+`Optional` bir değişkenin `str`gibi bir tipi olabileceğini ama isteğe bağlı olarak tipinin `None` olabileceğini belirtir:
+
+```Python hl_lines="1 4"
+{!../../../docs_src/python_types/tutorial009.py!}
+```
+
+`str` yerine `Optional[str]` kullanmak editorün bu değerin her zaman `str` tipinde değil bazen `None` tipinde de olabileceğini belirtir ve hataları tespit etmemizde yardımcı olur.
+
+#### Generic tipler
+
+Köşeli parantez içinde tip parametreleri alan bu türler, örneğin:
+
+* `List`
+* `Tuple`
+* `Set`
+* `Dict`
+* `Optional`
+* ...and others.
+
+**Generic types** yada **Generics** olarak adlandırılır.
+
+### Tip olarak Sınıflar
+
+Bir değişkenin tipini bir sınıf ile bildirebilirsiniz.
+
+Diyelim ki `name` değerine sahip `Person` sınıfınız var:
+
+```Python hl_lines="1-3"
+{!../../../docs_src/python_types/tutorial010.py!}
+```
+
+Sonra bir değişkeni 'Person' tipinde tanımlayabilirsiniz:
+
+```Python hl_lines="6"
+{!../../../docs_src/python_types/tutorial010.py!}
+```
+
+Ve yine bütün editör desteğini alırsınız:
+
+
+
+## Pydantic modelleri
+
+Pydantic veri doğrulaması yapmak için bir Python kütüphanesidir.
+
+Verilerin "biçimini" niteliklere sahip sınıflar olarak düzenlersiniz.
+
+Ve her niteliğin bir türü vardır.
+
+Sınıfın bazı değerlerle bir örneğini oluşturursunuz ve değerleri doğrular, bunları uygun türe dönüştürür ve size tüm verileri içeren bir nesne verir.
+
+Ve ortaya çıkan nesne üzerindeki bütün editör desteğini alırsınız.
+
+Resmi Pydantic dokümanlarından alınmıştır:
+
+```Python
+{!../../../docs_src/python_types/tutorial011.py!}
+```
+
+!!! info
+ Daha fazla şey öğrenmek için Pydantic'i takip edin.
+
+**FastAPI** tamamen Pydantic'e dayanmaktadır.
+
+Daha fazlasini görmek için [Tutorial - User Guide](tutorial/index.md){.internal-link target=_blank}.
+
+## **FastAPI** tip belirteçleri
+
+**FastAPI** birkaç şey yapmak için bu tür tip belirteçlerinden faydalanır.
+
+**FastAPI** ile parametre tiplerini bildirirsiniz ve şunları elde edersiniz:
+
+* **Editor desteği**.
+* **Tip kontrolü**.
+
+...ve **FastAPI** aynı belirteçleri şunlar için de kullanıyor:
+
+* **Gereksinimleri tanımlama**: request path parameters, query parameters, headers, bodies, dependencies, ve benzeri gereksinimlerden
+* **Verileri çevirme**: Gönderilen veri tipinden istenilen veri tipine çevirme.
+* **Verileri doğrulama**: Her gönderilen verinin:
+ * doğrulanması ve geçersiz olduğunda **otomatik hata** oluşturma.
+* OpenAPI kullanarak apinizi **Belgeleyin** :
+ * bu daha sonra otomatik etkileşimli dokümantasyon kullanıcı arayüzü tarafından kullanılır.
+
+Bütün bunlar kulağa soyut gelebilir. Merak etme. Tüm bunları çalışırken göreceksiniz. [Tutorial - User Guide](tutorial/index.md){.internal-link target=_blank}.
+
+Önemli olan, standart Python türlerini tek bir yerde kullanarak (daha fazla sınıf, dekoratör vb. eklemek yerine), **FastAPI**'nin bizim için işi yapmasını sağlamak.
+
+!!! info
+ Tüm öğreticiyi zaten okuduysanız ve türler hakkında daha fazla bilgi için geri döndüyseniz, iyi bir kaynak: the "cheat sheet" from `mypy`.
diff --git a/docs/tr/mkdocs.yml b/docs/tr/mkdocs.yml
index 6fe2b0b62..5ef8fd6a7 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
+
+!!! 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 4973d170e..01cf8d5e0 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 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 01cf8d5e0..6242a88fd 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
diff --git a/docs/en/data/sponsors.yml b/docs/en/data/sponsors.yml
index 4949e6c56..baa2e440d 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 0496c6279..759748728 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 000000000..29cd772da
--- /dev/null
+++ b/docs/en/docs/img/sponsors/cryptapi-banner.svg
@@ -0,0 +1,1375 @@
+
+
diff --git a/docs/en/docs/img/sponsors/cryptapi.svg b/docs/en/docs/img/sponsors/cryptapi.svg
new file mode 100644
index 000000000..db4e09347
--- /dev/null
+++ b/docs/en/docs/img/sponsors/cryptapi.svg
@@ -0,0 +1,1216 @@
+
+
diff --git a/docs/en/overrides/main.html b/docs/en/overrides/main.html
index aa381faa3..70b0253bd 100644
--- a/docs/en/overrides/main.html
+++ b/docs/en/overrides/main.html
@@ -40,6 +40,12 @@
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 0bf1a9713..0cd0e2f9f 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
+
+!!! 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 0cd0e2f9f..dd2d6a7ac 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
+
+### 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 219f41b81..ea4af852e 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 0024/1881] =?UTF-8?q?=F0=9F=8C=90=20Add=20Korean=20transla?=
=?UTF-8?q?tion=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 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 dd2d6a7ac..124e7b1d3 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
@@ -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 6002ce075..69aeb6712 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 6b3fd8fb5..1f38a0c5c 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 a4484147f..13de4c8ea 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 5bacf1666..fa38cfc48 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 757d7bdbc..7d8675060 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 f18afaea6..81441b41e 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 9aa2300c4..221cdfffb 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 7747e3e1b..663fff15b 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 cf0b5a92f..fe10facfb 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 097edb68b..51531228d 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 a2cbe45d5..7a69c5474 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 995f0b972..a00bd3212 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 4eced04ca..72fc74894 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 57570153f..8e294416c 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 0d606331d..1ff448e76 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 5da69a21b..31bf91a0e 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 8deccda0b..fcac1a4e0 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 4401745ac..eec55502a 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 68ea654c2..b7257c7eb 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 e2b89ca9e..c751a9256 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 71f9a3918..c69df51dc 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 a41db2b67..13e867216 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 f88cf23c0..09557f1ac 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 2aa747847..505b223b1 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 e8ebb29c8..9dc2f64c6 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 9c9a8270c..7e2ae84d0 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 5bdd2c54a..b3716f508 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 a4bc41154..06b015a1d 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 ff16e1d78..c42f92b8a 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 d70d2b3c3..0dc34a14f 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 e6d01fbde..18afbd547 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 39fd8a211..327fe862a 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 1d4d30913..5ffb71877 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 3c1351a12..724dc2725 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 f202f306d..f36ecf49d 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 6e17c287e..eb2597de7 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 d9c3dad4c..5afe0a665 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 f6ed7f5b9..40ba6d522 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 d0de8cc0e..a116d90a3 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 a929e3388..33d40129a 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 000000000..e69de29bb
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 83cc7d255..d186b8ecb 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 000000000..e69de29bb
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 000000000..d44ab9e7c
--- /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 000000000..d186b8ecb
--- /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 000000000..626af1358
--- /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 000000000..b71a52b62
--- /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 000000000..8928b72b8
--- /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 000000000..a936f28fd
--- /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 000000000..60cfd9610
--- /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 000000000..01e02a050
--- /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 000000000..b08d397b3
--- /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 000000000..2c4eb824c
--- /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 000000000..9ddbda3f7
--- /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 000000000..77321300e
--- /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 000000000..97b213b16
--- /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 000000000..d89be35d4
--- /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 000000000..71340e9fd
--- /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 000000000..af523a74e
--- /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 000000000..194f2dc23
--- /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 000000000..931d92f88
--- /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 000000000..ab0b63db1
--- /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 000000000..19985ec7a
--- /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 000000000..009628453
--- /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 000000000..504551883
--- /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 000000000..c87cda3c8
--- /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 000000000..61898178e
--- /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 000000000..665ac56e5
--- /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 000000000..0c7d32fbb
--- /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 000000000..854a7a5a4
--- /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 000000000..59c1e5082
--- /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 000000000..d275d7f84
--- /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 000000000..5d5388b56
--- /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 000000000..349841496
--- /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 000000000..ab85bd5ae
--- /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 000000000..d0b004631
--- /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 000000000..662af9b1b
--- /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 000000000..23c048ab9
--- /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 000000000..10d3771c1
--- /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 000000000..5245bb0f6
--- /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 000000000..5e1d7e0ef
--- /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 000000000..5baf13628
--- /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 000000000..4a33481b7
--- /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 000000000..669386ae6
--- /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 000000000..5b8ed7de3
--- /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 000000000..065439acc
--- /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 000000000..28cacde4d
--- /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 000000000..9da2a0a0f
--- /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 000000000..b28463346
--- /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 000000000..98ab5a807
--- /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 000000000..2dac2c13c
--- /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 000000000..359766527
--- /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 66ea442ea..1316d9237 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 000000000..da078fdf5
--- /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 000000000..5c04d8bac
--- /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 01df041b8..2df537d86 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 000000000..9a8af5432
--- /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 000000000..766d9fb0b
--- /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 29bcb4a22..269a1a253 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 000000000..3d94afe2c
--- /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 000000000..446198b5c
--- /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 ac95cc4bb..de83be836 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 000000000..4cb8bdd43
--- /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 000000000..bf6005b95
--- /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 c1b809887..0f62c3814 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 000000000..b176631d8
--- /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 000000000..5ef320405
--- /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 000000000..b940a0949
--- /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 000000000..486b67caf
--- /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 000000000..ea96c7964
--- /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 9fb1043bb..a393385b0 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 000000000..e52539ead
--- /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 000000000..6bb437841
--- /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 000000000..8464c6ff0
--- /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 000000000..9f1a05bc0
--- /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 000000000..7f173880f
--- /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 000000000..af79e2df0
--- /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 000000000..bedb58ce8
--- /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 000000000..1eec66d90
--- /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 000000000..8ec4338df
--- /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 000000000..be2a44c31
--- /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 000000000..f0dbfe08f
--- /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 000000000..eeefddfa7
--- /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 000000000..fa3139d5a
--- /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 000000000..335858a40
--- /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 000000000..518b779f7
--- /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 000000000..14ef4cb69
--- /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 000000000..06bb02442
--- /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 000000000..e84c116f1
--- /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 000000000..c35800858
--- /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 000000000..c3d992e62
--- /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 000000000..38ba764d6
--- /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 000000000..1900133d9
--- /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 000000000..26cd56769
--- /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 000000000..59efecde4
--- /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 000000000..37b866864
--- /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 000000000..29ab9c9d2
--- /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 000000000..fc9693e3c
--- /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 000000000..a3e21b434
--- /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 000000000..07ccbbf41
--- /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 000000000..acb3035b9
--- /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 000000000..9ccec324e
--- /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 000000000..77ceedd60
--- /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 000000000..4f8f8304e
--- /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 000000000..cf4c99dc0
--- /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 000000000..6f29c1a5c
--- /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 000000000..3c2018f54
--- /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 000000000..af935e997
--- /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 000000000..797d56d04
--- /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 000000000..c6a095d2c
--- /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 000000000..d45c08ce6
--- /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 000000000..e69de29bb
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 000000000..5de88ec3a
--- /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 000000000..679acdb5c
--- /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 000000000..45a8b9f69
--- /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 000000000..a9856d0b6
--- /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 000000000..62d8ab4aa
--- /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 000000000..aea2e3f10
--- /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 000000000..e69de29bb
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 000000000..c60c3356f
--- /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 000000000..e69de29bb
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 000000000..5de88ec3a
--- /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 000000000..679acdb5c
--- /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 000000000..45a8b9f69
--- /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 000000000..a9856d0b6
--- /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 000000000..62d8ab4aa
--- /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 000000000..a19f1cdfe
--- /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 000000000..e69de29bb
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 000000000..c60c3356f
--- /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 e6148831b..fa399c492 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 000000000..6937c8fab
--- /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 000000000..e292b5346
--- /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 000000000..d7a525ea7
--- /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 000000000..85ba41ce6
--- /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 000000000..f896f7bf5
--- /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 000000000..17ca29ce5
--- /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 000000000..ca1d8c585
--- /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 000000000..f2b184c4f
--- /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 000000000..587a328da
--- /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 000000000..a7991170e
--- /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 000000000..f66a36a99
--- /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 000000000..3d4c1d07d
--- /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 000000000..185bc3a37
--- /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 000000000..7f4f5b9be
--- /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 000000000..3bb5a99f1
--- /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 000000000..f5ee17428
--- /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 000000000..1f617da70
--- /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 000000000..ffdf05081
--- /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 000000000..1986d27d0
--- /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 000000000..66b24017e
--- /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 000000000..8894ee1b5
--- /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 000000000..b10e70af7
--- /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 000000000..a9cbce02a
--- /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 000000000..bbdf25cd9
--- /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"