]> git.ipfire.org Git - thirdparty/fastapi/fastapi.git/commitdiff
✨ Add docs and tests for Python 3.9 and Python 3.10 (#3712)
authorSebastián Ramírez <tiangolo@gmail.com>
Fri, 7 Jan 2022 14:11:31 +0000 (15:11 +0100)
committerGitHub <noreply@github.com>
Fri, 7 Jan 2022 14:11:31 +0000 (15:11 +0100)
Co-authored-by: Thomas Grainger <tagrain@gmail.com>
219 files changed:
.github/workflows/test.yml
docs/az/mkdocs.yml
docs/de/mkdocs.yml
docs/en/docs/python-types.md
docs/en/docs/tutorial/background-tasks.md
docs/en/docs/tutorial/body-fields.md
docs/en/docs/tutorial/body-multiple-params.md
docs/en/docs/tutorial/body-nested-models.md
docs/en/docs/tutorial/body-updates.md
docs/en/docs/tutorial/body.md
docs/en/docs/tutorial/cookie-params.md
docs/en/docs/tutorial/dependencies/classes-as-dependencies.md
docs/en/docs/tutorial/dependencies/index.md
docs/en/docs/tutorial/dependencies/sub-dependencies.md
docs/en/docs/tutorial/encoder.md
docs/en/docs/tutorial/extra-data-types.md
docs/en/docs/tutorial/extra-models.md
docs/en/docs/tutorial/header-params.md
docs/en/docs/tutorial/path-operation-configuration.md
docs/en/docs/tutorial/path-params-numeric-validations.md
docs/en/docs/tutorial/query-params-str-validations.md
docs/en/docs/tutorial/query-params.md
docs/en/docs/tutorial/request-files.md
docs/en/docs/tutorial/response-model.md
docs/en/docs/tutorial/schema-extra-example.md
docs/en/docs/tutorial/security/get-current-user.md
docs/en/docs/tutorial/security/oauth2-jwt.md
docs/en/docs/tutorial/security/simple-oauth2.md
docs/en/docs/tutorial/sql-databases.md
docs/en/docs/tutorial/testing.md
docs/en/mkdocs.yml
docs/es/mkdocs.yml
docs/fr/mkdocs.yml
docs/id/mkdocs.yml
docs/it/mkdocs.yml
docs/ja/mkdocs.yml
docs/ko/mkdocs.yml
docs/pl/mkdocs.yml
docs/pt/mkdocs.yml
docs/ru/mkdocs.yml
docs/sq/mkdocs.yml
docs/tr/mkdocs.yml
docs/uk/mkdocs.yml
docs/zh/mkdocs.yml
docs_src/app_testing/app_b/__init__.py [new file with mode: 0644]
docs_src/app_testing/app_b/main.py [moved from docs_src/app_testing/main_b.py with 100% similarity]
docs_src/app_testing/app_b/test_main.py [moved from docs_src/app_testing/test_main_b.py with 98% similarity]
docs_src/app_testing/app_b_py310/__init__.py [new file with mode: 0644]
docs_src/app_testing/app_b_py310/main.py [new file with mode: 0644]
docs_src/app_testing/app_b_py310/test_main.py [new file with mode: 0644]
docs_src/background_tasks/tutorial002_py310.py [new file with mode: 0644]
docs_src/body/tutorial001_py310.py [new file with mode: 0644]
docs_src/body/tutorial002_py310.py [new file with mode: 0644]
docs_src/body/tutorial003_py310.py [new file with mode: 0644]
docs_src/body/tutorial004_py310.py [new file with mode: 0644]
docs_src/body_fields/tutorial001_py310.py [new file with mode: 0644]
docs_src/body_multiple_params/tutorial001_py310.py [new file with mode: 0644]
docs_src/body_multiple_params/tutorial002_py310.py [new file with mode: 0644]
docs_src/body_multiple_params/tutorial003_py310.py [new file with mode: 0644]
docs_src/body_multiple_params/tutorial004_py310.py [new file with mode: 0644]
docs_src/body_multiple_params/tutorial005_py310.py [new file with mode: 0644]
docs_src/body_nested_models/tutorial001_py310.py [new file with mode: 0644]
docs_src/body_nested_models/tutorial002_py310.py [new file with mode: 0644]
docs_src/body_nested_models/tutorial002_py39.py [new file with mode: 0644]
docs_src/body_nested_models/tutorial003_py310.py [new file with mode: 0644]
docs_src/body_nested_models/tutorial003_py39.py [new file with mode: 0644]
docs_src/body_nested_models/tutorial004_py310.py [new file with mode: 0644]
docs_src/body_nested_models/tutorial004_py39.py [new file with mode: 0644]
docs_src/body_nested_models/tutorial005_py310.py [new file with mode: 0644]
docs_src/body_nested_models/tutorial005_py39.py [new file with mode: 0644]
docs_src/body_nested_models/tutorial006_py310.py [new file with mode: 0644]
docs_src/body_nested_models/tutorial006_py39.py [new file with mode: 0644]
docs_src/body_nested_models/tutorial007_py310.py [new file with mode: 0644]
docs_src/body_nested_models/tutorial007_py39.py [new file with mode: 0644]
docs_src/body_nested_models/tutorial008_py39.py [new file with mode: 0644]
docs_src/body_nested_models/tutorial009_py39.py [new file with mode: 0644]
docs_src/body_updates/tutorial001_py310.py [new file with mode: 0644]
docs_src/body_updates/tutorial001_py39.py [new file with mode: 0644]
docs_src/body_updates/tutorial002_py310.py [new file with mode: 0644]
docs_src/body_updates/tutorial002_py39.py [new file with mode: 0644]
docs_src/cookie_params/tutorial001_py310.py [new file with mode: 0644]
docs_src/dependencies/tutorial001_py310.py [new file with mode: 0644]
docs_src/dependencies/tutorial002_py310.py [new file with mode: 0644]
docs_src/dependencies/tutorial003_py310.py [new file with mode: 0644]
docs_src/dependencies/tutorial004_py310.py [new file with mode: 0644]
docs_src/dependencies/tutorial005_py310.py [new file with mode: 0644]
docs_src/encoder/tutorial001_py310.py [new file with mode: 0644]
docs_src/extra_data_types/tutorial001_py310.py [new file with mode: 0644]
docs_src/extra_models/tutorial001_py310.py [new file with mode: 0644]
docs_src/extra_models/tutorial002_py310.py [new file with mode: 0644]
docs_src/extra_models/tutorial003_py310.py [new file with mode: 0644]
docs_src/extra_models/tutorial004_py39.py [new file with mode: 0644]
docs_src/extra_models/tutorial005_py39.py [new file with mode: 0644]
docs_src/header_params/tutorial001_py310.py [new file with mode: 0644]
docs_src/header_params/tutorial002_py310.py [new file with mode: 0644]
docs_src/header_params/tutorial003_py310.py [new file with mode: 0644]
docs_src/header_params/tutorial003_py39.py [new file with mode: 0644]
docs_src/path_operation_configuration/tutorial001.py
docs_src/path_operation_configuration/tutorial001_py310.py [new file with mode: 0644]
docs_src/path_operation_configuration/tutorial001_py39.py [new file with mode: 0644]
docs_src/path_operation_configuration/tutorial002.py
docs_src/path_operation_configuration/tutorial002_py310.py [new file with mode: 0644]
docs_src/path_operation_configuration/tutorial002_py39.py [new file with mode: 0644]
docs_src/path_operation_configuration/tutorial003.py
docs_src/path_operation_configuration/tutorial003_py310.py [new file with mode: 0644]
docs_src/path_operation_configuration/tutorial003_py39.py [new file with mode: 0644]
docs_src/path_operation_configuration/tutorial004.py
docs_src/path_operation_configuration/tutorial004_py310.py [new file with mode: 0644]
docs_src/path_operation_configuration/tutorial004_py39.py [new file with mode: 0644]
docs_src/path_operation_configuration/tutorial005.py
docs_src/path_operation_configuration/tutorial005_py310.py [new file with mode: 0644]
docs_src/path_operation_configuration/tutorial005_py39.py [new file with mode: 0644]
docs_src/path_params_numeric_validations/tutorial001_py310.py [new file with mode: 0644]
docs_src/python_types/tutorial006_py39.py [new file with mode: 0644]
docs_src/python_types/tutorial007_py39.py [new file with mode: 0644]
docs_src/python_types/tutorial008.py
docs_src/python_types/tutorial008b.py [new file with mode: 0644]
docs_src/python_types/tutorial008b_py310.py [new file with mode: 0644]
docs_src/python_types/tutorial009_py310.py [new file with mode: 0644]
docs_src/python_types/tutorial009b.py [new file with mode: 0644]
docs_src/python_types/tutorial011_py310.py [new file with mode: 0644]
docs_src/python_types/tutorial011_py39.py [new file with mode: 0644]
docs_src/query_params/tutorial002_py310.py [new file with mode: 0644]
docs_src/query_params/tutorial003_py310.py [new file with mode: 0644]
docs_src/query_params/tutorial004_py310.py [new file with mode: 0644]
docs_src/query_params/tutorial006_py310.py [new file with mode: 0644]
docs_src/query_params/tutorial006b.py [new file with mode: 0644]
docs_src/query_params_str_validations/tutorial001_py310.py [new file with mode: 0644]
docs_src/query_params_str_validations/tutorial002_py310.py [new file with mode: 0644]
docs_src/query_params_str_validations/tutorial003_py310.py [new file with mode: 0644]
docs_src/query_params_str_validations/tutorial004_py310.py [new file with mode: 0644]
docs_src/query_params_str_validations/tutorial007_py310.py [new file with mode: 0644]
docs_src/query_params_str_validations/tutorial008_py310.py [new file with mode: 0644]
docs_src/query_params_str_validations/tutorial009_py310.py [new file with mode: 0644]
docs_src/query_params_str_validations/tutorial010_py310.py [new file with mode: 0644]
docs_src/query_params_str_validations/tutorial011_py310.py [new file with mode: 0644]
docs_src/query_params_str_validations/tutorial011_py39.py [new file with mode: 0644]
docs_src/query_params_str_validations/tutorial012_py39.py [new file with mode: 0644]
docs_src/request_files/tutorial002_py39.py [new file with mode: 0644]
docs_src/response_model/tutorial001_py310.py [new file with mode: 0644]
docs_src/response_model/tutorial001_py39.py [new file with mode: 0644]
docs_src/response_model/tutorial002_py310.py [new file with mode: 0644]
docs_src/response_model/tutorial003_py310.py [new file with mode: 0644]
docs_src/response_model/tutorial004_py310.py [new file with mode: 0644]
docs_src/response_model/tutorial004_py39.py [new file with mode: 0644]
docs_src/response_model/tutorial005_py310.py [new file with mode: 0644]
docs_src/response_model/tutorial006_py310.py [new file with mode: 0644]
docs_src/schema_extra_example/tutorial001_py310.py [new file with mode: 0644]
docs_src/schema_extra_example/tutorial002_py310.py [new file with mode: 0644]
docs_src/schema_extra_example/tutorial003_py310.py [new file with mode: 0644]
docs_src/schema_extra_example/tutorial004_py310.py [new file with mode: 0644]
docs_src/security/tutorial002_py310.py [new file with mode: 0644]
docs_src/security/tutorial003_py310.py [new file with mode: 0644]
docs_src/security/tutorial004_py310.py [new file with mode: 0644]
docs_src/security/tutorial005_py310.py [new file with mode: 0644]
docs_src/security/tutorial005_py39.py [new file with mode: 0644]
docs_src/sql_databases/sql_app_py310/__init__.py [new file with mode: 0644]
docs_src/sql_databases/sql_app_py310/alt_main.py [new file with mode: 0644]
docs_src/sql_databases/sql_app_py310/crud.py [new file with mode: 0644]
docs_src/sql_databases/sql_app_py310/database.py [new file with mode: 0644]
docs_src/sql_databases/sql_app_py310/main.py [new file with mode: 0644]
docs_src/sql_databases/sql_app_py310/models.py [new file with mode: 0644]
docs_src/sql_databases/sql_app_py310/schemas.py [new file with mode: 0644]
docs_src/sql_databases/sql_app_py310/tests/__init__.py [new file with mode: 0644]
docs_src/sql_databases/sql_app_py310/tests/test_sql_app.py [new file with mode: 0644]
docs_src/sql_databases/sql_app_py39/__init__.py [new file with mode: 0644]
docs_src/sql_databases/sql_app_py39/alt_main.py [new file with mode: 0644]
docs_src/sql_databases/sql_app_py39/crud.py [new file with mode: 0644]
docs_src/sql_databases/sql_app_py39/database.py [new file with mode: 0644]
docs_src/sql_databases/sql_app_py39/main.py [new file with mode: 0644]
docs_src/sql_databases/sql_app_py39/models.py [new file with mode: 0644]
docs_src/sql_databases/sql_app_py39/schemas.py [new file with mode: 0644]
docs_src/sql_databases/sql_app_py39/tests/__init__.py [new file with mode: 0644]
docs_src/sql_databases/sql_app_py39/tests/test_sql_app.py [new file with mode: 0644]
pyproject.toml
tests/test_tutorial/test_background_tasks/test_tutorial002_py310.py [new file with mode: 0644]
tests/test_tutorial/test_body/test_tutorial001_py310.py [new file with mode: 0644]
tests/test_tutorial/test_body_fields/test_tutorial001_py310.py [new file with mode: 0644]
tests/test_tutorial/test_body_multiple_params/test_tutorial001_py310.py [new file with mode: 0644]
tests/test_tutorial/test_body_multiple_params/test_tutorial003_py310.py [new file with mode: 0644]
tests/test_tutorial/test_body_nested_models/test_tutorial009_py39.py [new file with mode: 0644]
tests/test_tutorial/test_body_updates/test_tutorial001_py310.py [new file with mode: 0644]
tests/test_tutorial/test_body_updates/test_tutorial001_py39.py [new file with mode: 0644]
tests/test_tutorial/test_cookie_params/test_tutorial001_py310.py [new file with mode: 0644]
tests/test_tutorial/test_dependencies/test_tutorial001_py310.py [new file with mode: 0644]
tests/test_tutorial/test_dependencies/test_tutorial004_py310.py [new file with mode: 0644]
tests/test_tutorial/test_extra_data_types/test_tutorial001_py310.py [new file with mode: 0644]
tests/test_tutorial/test_extra_models/test_tutorial003_py310.py [new file with mode: 0644]
tests/test_tutorial/test_extra_models/test_tutorial004_py39.py [new file with mode: 0644]
tests/test_tutorial/test_extra_models/test_tutorial005_py39.py [new file with mode: 0644]
tests/test_tutorial/test_header_params/test_tutorial001_py310.py [new file with mode: 0644]
tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py310.py [new file with mode: 0644]
tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py39.py [new file with mode: 0644]
tests/test_tutorial/test_query_params/test_tutorial006_py310.py [new file with mode: 0644]
tests/test_tutorial/test_query_params_str_validations/test_tutorial001_py310.py [new file with mode: 0644]
tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py310.py [new file with mode: 0644]
tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py39.py [new file with mode: 0644]
tests/test_tutorial/test_query_params_str_validations/test_tutorial012_py39.py [new file with mode: 0644]
tests/test_tutorial/test_request_files/test_tutorial002_py39.py [new file with mode: 0644]
tests/test_tutorial/test_response_model/test_tutorial003_py310.py [new file with mode: 0644]
tests/test_tutorial/test_response_model/test_tutorial004_py310.py [new file with mode: 0644]
tests/test_tutorial/test_response_model/test_tutorial004_py39.py [new file with mode: 0644]
tests/test_tutorial/test_response_model/test_tutorial005_py310.py [new file with mode: 0644]
tests/test_tutorial/test_response_model/test_tutorial006_py310.py [new file with mode: 0644]
tests/test_tutorial/test_schema_extra_example/test_tutorial004_py310.py [new file with mode: 0644]
tests/test_tutorial/test_security/test_tutorial003_py310.py [new file with mode: 0644]
tests/test_tutorial/test_security/test_tutorial005_py310.py [new file with mode: 0644]
tests/test_tutorial/test_security/test_tutorial005_py39.py [new file with mode: 0644]
tests/test_tutorial/test_sql_databases/test_sql_databases.py
tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py310.py [new file with mode: 0644]
tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py39.py [new file with mode: 0644]
tests/test_tutorial/test_sql_databases/test_sql_databases_py310.py [new file with mode: 0644]
tests/test_tutorial/test_sql_databases/test_sql_databases_py39.py [new file with mode: 0644]
tests/test_tutorial/test_sql_databases/test_testing_databases.py
tests/test_tutorial/test_sql_databases/test_testing_databases_py310.py [new file with mode: 0644]
tests/test_tutorial/test_sql_databases/test_testing_databases_py39.py [new file with mode: 0644]
tests/test_tutorial/test_testing/test_main_b.py
tests/test_tutorial/test_testing/test_main_b_py310.py [new file with mode: 0644]
tests/utils.py

index 7ab0f1230775f5c1bed360f6211ad552600315e0..21ea7c1a8d0bd23ac301e20cc1bed09725a6a5c9 100644 (file)
@@ -12,7 +12,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python-version: [3.6, 3.7, 3.8, 3.9]
+        python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"]
       fail-fast: false
 
     steps:
index 66220f63eea088b1ff864ca003d4a729fda3a6ef..dbff7b26ab9349202585e4df0d9003d3d87db215 100644 (file)
@@ -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
index 360fa8c4a4c22d81795659aeb862116e9cd4ec0e..8f29ef316af967797548feaf421db1112d44f564 100644 (file)
@@ -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
index 9bbf955b97d27d7686a7583d355da011d5ef8d06..76d442855e85e580dba3b831faf96db32a40b07a 100644 (file)
@@ -148,37 +148,62 @@ You can use, for example:
 
 There are some data structures that can contain other values, like `dict`, `list`, `set` and `tuple`. And the internal values can have their own type too.
 
-To declare those types and the internal types, you can use the standard Python module `typing`.
+These types that have internal types are called "**generic**" types. And it's possible to declare them, even with their internal types.
 
-It exists specifically to support these type hints.
+To declare those types and the internal types, you can use the standard Python module `typing`. It exists specifically to support these type hints.
 
-#### `List`
+#### Newer versions of Python
+
+The syntax using `typing` is **compatible** with all versions, from Python 3.6 to the latest ones, including Python 3.9, Python 3.10, etc.
+
+As Python advances, **newer versions** come with improved support for these type annotations and in many cases you won't even need to import and use the `typing` module to declare the type annotations.
+
+If you can chose a more recent version of Python for your project, you will be able to take advantage of that extra simplicity. See some examples below.
+
+#### List
 
 For example, let's define a variable to be a `list` of `str`.
 
-From `typing`, import `List` (with a capital `L`):
+=== "Python 3.6 and above"
 
-```Python hl_lines="1"
-{!../../../docs_src/python_types/tutorial006.py!}
-```
+    From `typing`, import `List` (with a capital `L`):
 
-Declare the variable, with the same colon (`:`) syntax.
+    ``` Python hl_lines="1"
+    {!> ../../../docs_src/python_types/tutorial006.py!}
+    ```
 
-As the type, put the `List`.
+    Declare the variable, with the same colon (`:`) syntax.
 
-As the list is a type that contains some internal types, you put them in square brackets:
+    As the type, put the `List` that you imported from `typing`.
 
-```Python hl_lines="4"
-{!../../../docs_src/python_types/tutorial006.py!}
-```
+    As the list is a type that contains some internal types, you put them in square brackets:
 
-!!! tip
+    ```Python hl_lines="4"
+    {!> ../../../docs_src/python_types/tutorial006.py!}
+    ```
+
+=== "Python 3.9 and above"
+
+    Declare the variable, with the same colon (`:`) syntax.
+
+    As the type, put `list`.
+
+    As the list is a type that contains some internal types, you put them in square brackets:
+
+    ```Python hl_lines="1"
+    {!> ../../../docs_src/python_types/tutorial006_py39.py!}
+    ```
+
+!!! info
     Those internal types in the square brackets are called "type parameters".
 
-    In this case, `str` is the type parameter passed to `List`.
+    In this case, `str` is the type parameter passed to `List` (or `list` in Python 3.9 and above).
 
 That means: "the variable `items` is a `list`, and each of the items in this list is a `str`".
 
+!!! tip
+    If you use Python 3.9 or above, you don't have to import `List` from `typing`, you can use the same regular `list` type instead.
+
 By doing that, your editor can provide support even while processing items from the list:
 
 <img src="/img/python-types/image05.png">
@@ -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 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 <abbr title='also called "bitwise or operator", but that meaning is not relevant here'>vertical bar (`|`)</abbr>.
 
-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`:
+=== "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 <abbr title='also called "bitwise or operator", but that meaning is not relevant here'>vertical bar (`|`)</abbr> 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 <a href="https://pydantic-docs.helpmanual.io/" class="external-link" target="_blank">Pydantic, check its docs</a>.
index 6002ce0759a545df6251bd1ead495a78f857d3e4..69aeb6712c70a65a1e0a7c05212976d75f65af6e 100644 (file)
@@ -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.
 
index 6b3fd8fb571a6fdeaf3546dc518066d9da7c6e1c..1f38a0c5c566fa6f332e5ddc0c861765ce872c85 100644 (file)
@@ -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.
 
index a4484147f50c7bfcb2cdab775aa5a5b3bc6ccb4f..13de4c8eab531ac76d7d6fa384d0a9f09217c418 100644 (file)
@@ -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,13 +87,19 @@ 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!}
+    ```
 
-In this case, **FastAPI** will expect a body like:
+=== "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
 {
@@ -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:
 
index 5bacf166644cc7271cc00dc946e14d85a20cea85..fa38cfc4883f508050746a080f5beef48e4f015a 100644 (file)
@@ -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 <a href="https://pydantic
 
 For example, as in the `Image` model we have a `url` field, we can declare it to be instead of a `str`, a Pydantic's `HttpUrl`:
 
-```Python hl_lines="4  10"
-{!../../../docs_src/body_nested_models/tutorial005.py!}
-```
+=== "Python 3.6 and above"
+
+    ```Python hl_lines="4  10"
+    {!> ../../../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,12 +300,26 @@ If the top level value of the JSON body you expect is a JSON `array` (a Python `
 images: List[Image]
 ```
 
-as in:
+or in Python 3.9 and above:
 
-```Python hl_lines="15"
-{!../../../docs_src/body_nested_models/tutorial008.py!}
+```Python
+images: list[Image]
 ```
 
+as in:
+
+=== "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
 
 And you get 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.
index 757d7bdbcfd6c127b1286a5147935480a2011fda..7d8675060f3f8daa2026011bde1898b0b1727c1b 100644 (file)
@@ -6,9 +6,23 @@ To update an item you can use the <a href="https://developer.mozilla.org/en-US/d
 
 You can use the `jsonable_encoder` to convert the input data to data that can be stored as JSON (e.g. with a NoSQL database). For example, converting `datetime` to `str`.
 
-```Python hl_lines="30-35"
-{!../../../docs_src/body_updates/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+    ```Python hl_lines="30-35"
+    {!> ../../../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.
index f18afaea6960142ad4b002d13dd2a66ddb809030..81441b41eb2aa2f831e2be146f81308dce13fdf5 100644 (file)
@@ -19,9 +19,17 @@ To declare a **request** body, you use <a href="https://pydantic-docs.helpmanual
 
 First, you need to import `BaseModel` from `pydantic`:
 
-```Python hl_lines="4"
-{!../../../docs_src/body/tutorial001.py!}
-```
+=== "Python 3.6 and above"
+
+    ```Python hl_lines="4"
+    {!> ../../../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 <a href="https://www.jetbrains.co
 
 Inside of the function, you can access all the attributes of the model object directly:
 
-```Python hl_lines="21"
-{!../../../docs_src/body/tutorial002.py!}
-```
+=== "Python 3.6 and above"
+
+    ```Python hl_lines="21"
+    {!> ../../../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:
 
index 9aa2300c4b29a3bf6bf076bb27565777e66bbfed..221cdfffba754f4fce6d7093df641518f07374ca 100644 (file)
@@ -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.
index 7747e3e1b454bd0e9d8b336db76bd3f5c7b0d81f..663fff15bf3f5cf849bfc891dae80ab91fa08744 100644 (file)
@@ -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.
 
index cf0b5a92fa0832c1b99b86acd3e9dbf3e7515d25..fe10facfbc36540857c7d834f1b1f25c7e12868d 100644 (file)
@@ -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.
 
index 097edb68b1d682af2d9a0c353f7366234a6c2e3a..51531228da54d263abc02eb45f78d3beafa1156a 100644 (file)
@@ -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`.
index a2cbe45d5533a5482418bd0c65c84095c7a886cf..7a69c5474a31842bc9a4d5d1e76e21151baa7ec3 100644 (file)
@@ -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`.
 
index 995f0b972f9ebcdf19843398eadc19f403fe5761..a00bd32122509b7b1def8551ceff0c2621a2302d 100644 (file)
@@ -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!}
+    ```
index 4eced04ca98b91ef7a1633bb15dc7cf615ca6a61..72fc74894ceb1eba6bc76e44105118ce85b59a16 100644 (file)
@@ -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 <a href="https://docs.python.org/3
 !!! note
     When defining a <a href="https://pydantic-docs.helpmanual.io/usage/types/#unions" class="external-link" target="_blank">`Union`</a>, 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
 
index 57570153ffd1a151e75381c79c232fb24b0d8f3c..8e294416c689da5a2520fa1402e9828099242ec8 100644 (file)
@@ -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:
 
index 0d606331db2f999b747d36d52eb048e69ef73bed..1ff448e768d5013bf8921cd3f842dae96ab67f9d 100644 (file)
@@ -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 <a href="https://en.wikipedia.org/wiki/Markdown" class="external-link" target="_blank">Markdown</a> 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.
index 5da69a21b2ef31a8068a8dcd0b7e9cfe79ff4dec..31bf91a0ea6e6abb716e5f73353f9923ac863bc0 100644 (file)
@@ -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.
index 8deccda0bd615bb7ddfd6c14930dafdfe924a64e..fcac1a4e0e91d6af368f61b0aa45cab759674f97 100644 (file)
@@ -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"
+
+    ```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]`, 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.
+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 <abbr title="A regular expression, regex or regexp is a sequence of characters that define a search pattern for strings.">regular expression</abbr> 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:
 
index 4401745ac9dc3bec989530fd90cdac8247d3d3a2..eec55502a3d16a194af386ed161d5ed7a881715d 100644 (file)
@@ -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:
 
index 68ea654c29d4d6d027a1d6c17e95cbb530f7edb9..b7257c7eb686602e59fbf8774893b7b1f1ea31a7 100644 (file)
@@ -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.
 
index e2b89ca9e6614b7bf9b0c14f9df848a10a6972bf..c751a9256be90b3a84950a07a0a68c53f18db11c 100644 (file)
@@ -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
 
index 71f9a39180d78e0a20d5a2536923a1fc4e2542ef..c69df51dc9e0e489c183611608ae66e715167d70 100644 (file)
@@ -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 <a href="https://pydantic-docs.helpmanual.io/usage/schema/#schema-customization" class="external-link" target="_blank">Pydantic's docs: Schema customization</a>:
 
-```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
 
index a41db2b67d137d41658fe0f4dcb4af21ad3382e0..13e867216be310562a5d77cfda973e8d40f42fdd 100644 (file)
@@ -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
 
index f88cf23c0e5add57d04dd4591cabecdf3e2c1f0c..09557f1ac5fac7375af4a7c869d71f1720924971 100644 (file)
@@ -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`
 
index 2aa747847ad54cdcc7c0f597cc6bf69e3932cd22..505b223b1613a964f7a07ad2db3d53d4db2101c8 100644 (file)
@@ -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.
index e8ebb29c8c7763e020d4b1dbfdfe8ef448acfb6f..9dc2f64c6e67f0ab2f43df11f06702a938ac82e9 100644 (file)
@@ -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 <a href="https://pydantic-docs.helpmanual.io/#config" class="external-link"
 
 In the `Config` class, set the attribute `orm_mode = True`.
 
-```Python hl_lines="15  19-20  31  36-37"
-{!../../../docs_src/sql_databases/sql_app/schemas.py!}
-```
+=== "Python 3.6 and above"
+
+    ```Python hl_lines="15  19-20  31  36-37"
+    {!> ../../../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 <a href="https://docs.celeryprojec
 
 * `sql_app/schemas.py`:
 
-```Python
-{!../../../docs_src/sql_databases/sql_app/schemas.py!}
-```
+=== "Python 3.6 and above"
+
+    ```Python
+    {!> ../../../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 <a href="https://docs.celeryprojec
 
 * `sql_app/main.py`:
 
-```Python
-{!../../../docs_src/sql_databases/sql_app/main.py!}
-```
+=== "Python 3.6 and above"
+
+    ```Python
+    {!> ../../../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.
index 9c9a8270cc3d237a4b6e74ba1823c433c8c5004a..7e2ae84d006a9ece9e1a13800c8443253d5260b4 100644 (file)
@@ -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`.
index 5bdd2c54a8ab3a3298fba1cb08502485ed5b8bea..b3716f508004eba7cad8ecf2d075e41ee20b930a 100644 (file)
@@ -20,7 +20,6 @@ theme:
   features:
   - search.suggest
   - search.highlight
-  - content.tabs.link
   icon:
     repo: fontawesome/brands/github-alt
   logo: img/icon-white.svg
index a4bc41154f6de2d5e89933bdab58250202dc0e35..06b015a1d4595efda0050541b66a21fb1d773957 100644 (file)
@@ -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
index ff16e1d78fd24403874972690a0bce204d172ec8..c42f92b8aafa6cf12fc89fd4f1c4fe51e3aeec9d 100644 (file)
@@ -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
index d70d2b3c3d03dc3d10833b6d55698735785e4e6f..0dc34a14f7515b94a480b8af5eed107d398c06a0 100644 (file)
@@ -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
index e6d01fbdecac97f5b6092ce588c72508c97d19cd..18afbd54731fa47829e7d6a7c653e3b33e1191b9 100644 (file)
@@ -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
index 39fd8a2110a817a295cb627907d01b05ecdf820d..327fe862ae512e4b174c880b457e4f8d9166c65f 100644 (file)
@@ -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
index 1d4d30913c3653b2ada7ea15463c2fa4041658e4..5ffb718770410e132fbf3ab73d23bcba6c407f2b 100644 (file)
@@ -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
index 3c1351a127ae24a62b43c49d0917bb2210a27f01..724dc27257ad329eeb21ee980ec8d93c90a1033e 100644 (file)
@@ -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
index f202f306d62b274bb570f2b795a6a452146f2cae..f36ecf49d8448c5280badc2201bf29fd45b8e1ef 100644 (file)
@@ -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
index 6e17c287e6551627699705a2fa1bb0747d034f2f..eb2597de776766aa77d7a1b5fb2e245101147d7b 100644 (file)
@@ -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
index d9c3dad4cce333dda513b660c481683bf3a5b859..5afe0a66525b17775a85f0a6dad370e17efd1555 100644 (file)
@@ -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
index f6ed7f5b9c3361a9c80b922c02aead8a31184e11..40ba6d522dbd56b43a30772ec9f3360d8152d537 100644 (file)
@@ -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
index d0de8cc0ef621620a74a95e391fe19724854eca1..a116d90a37f73dcf31991a24fc1c7ed5260c8fa0 100644 (file)
@@ -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
index a929e3388e4d5f8514428360221944478971d0b3..33d40129a42c68f14c83cdd42f993a308a91d0cd 100644 (file)
@@ -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 (file)
index 0000000..e69de29
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 83cc7d2552110e4c134a4b8d140d8f97dac8639d..d186b8ecbacfa431a40af18918037ead306a2c8e 100644 (file)
@@ -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 (file)
index 0000000..e69de29
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 (file)
index 0000000..d44ab9e
--- /dev/null
@@ -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 (file)
index 0000000..d186b8e
--- /dev/null
@@ -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 (file)
index 0000000..626af13
--- /dev/null
@@ -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 (file)
index 0000000..b71a52b
--- /dev/null
@@ -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 (file)
index 0000000..8928b72
--- /dev/null
@@ -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 (file)
index 0000000..a936f28
--- /dev/null
@@ -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 (file)
index 0000000..60cfd96
--- /dev/null
@@ -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 (file)
index 0000000..01e02a0
--- /dev/null
@@ -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 (file)
index 0000000..b08d397
--- /dev/null
@@ -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 (file)
index 0000000..2c4eb82
--- /dev/null
@@ -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 (file)
index 0000000..9ddbda3
--- /dev/null
@@ -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 (file)
index 0000000..7732130
--- /dev/null
@@ -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 (file)
index 0000000..97b213b
--- /dev/null
@@ -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 (file)
index 0000000..d89be35
--- /dev/null
@@ -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 (file)
index 0000000..71340e9
--- /dev/null
@@ -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 (file)
index 0000000..af523a7
--- /dev/null
@@ -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 (file)
index 0000000..194f2dc
--- /dev/null
@@ -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 (file)
index 0000000..931d92f
--- /dev/null
@@ -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 (file)
index 0000000..ab0b63d
--- /dev/null
@@ -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 (file)
index 0000000..19985ec
--- /dev/null
@@ -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 (file)
index 0000000..0096284
--- /dev/null
@@ -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 (file)
index 0000000..5045518
--- /dev/null
@@ -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 (file)
index 0000000..c87cda3
--- /dev/null
@@ -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 (file)
index 0000000..6189817
--- /dev/null
@@ -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 (file)
index 0000000..665ac56
--- /dev/null
@@ -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 (file)
index 0000000..0c7d32f
--- /dev/null
@@ -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 (file)
index 0000000..854a7a5
--- /dev/null
@@ -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 (file)
index 0000000..59c1e50
--- /dev/null
@@ -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 (file)
index 0000000..d275d7f
--- /dev/null
@@ -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 (file)
index 0000000..5d5388b
--- /dev/null
@@ -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 (file)
index 0000000..3498414
--- /dev/null
@@ -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 (file)
index 0000000..ab85bd5
--- /dev/null
@@ -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 (file)
index 0000000..d0b0046
--- /dev/null
@@ -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 (file)
index 0000000..662af9b
--- /dev/null
@@ -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 (file)
index 0000000..23c048a
--- /dev/null
@@ -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 (file)
index 0000000..10d3771
--- /dev/null
@@ -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 (file)
index 0000000..5245bb0
--- /dev/null
@@ -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 (file)
index 0000000..5e1d7e0
--- /dev/null
@@ -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 (file)
index 0000000..5baf136
--- /dev/null
@@ -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 (file)
index 0000000..4a33481
--- /dev/null
@@ -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 (file)
index 0000000..669386a
--- /dev/null
@@ -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 (file)
index 0000000..5b8ed7d
--- /dev/null
@@ -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 (file)
index 0000000..065439a
--- /dev/null
@@ -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 (file)
index 0000000..28cacde
--- /dev/null
@@ -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 (file)
index 0000000..9da2a0a
--- /dev/null
@@ -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 (file)
index 0000000..b284633
--- /dev/null
@@ -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 (file)
index 0000000..98ab5a8
--- /dev/null
@@ -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 (file)
index 0000000..2dac2c1
--- /dev/null
@@ -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 (file)
index 0000000..3597665
--- /dev/null
@@ -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}
index 66ea442ea9c9888d6bd84144a99dd5b4492eda7a..1316d9237eb1fd4b9cc8ad38499f9afb0b93ae6e 100644 (file)
@@ -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 (file)
index 0000000..da078fd
--- /dev/null
@@ -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 (file)
index 0000000..5c04d8b
--- /dev/null
@@ -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
index 01df041b83a61094e0912bb0dbc8d705d5a053a5..2df537d8647494f3e0b2823c1f755a3f1b133e01 100644 (file)
@@ -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 (file)
index 0000000..9a8af54
--- /dev/null
@@ -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 (file)
index 0000000..766d9fb
--- /dev/null
@@ -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"}]
index 29bcb4a223f669414b2030fe080a5a89ae2f19d5..269a1a253d7d9ba4b0883589828988f5bfd5a5cd 100644 (file)
@@ -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 (file)
index 0000000..3d94afe
--- /dev/null
@@ -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 (file)
index 0000000..446198b
--- /dev/null
@@ -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
index ac95cc4bb943f093172ae0c077ae504f2bdbde73..de83be836ba0e400871ff9c0643aafa1a2cbbcda 100644 (file)
@@ -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 (file)
index 0000000..4cb8bdd
--- /dev/null
@@ -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 (file)
index 0000000..bf6005b
--- /dev/null
@@ -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
index c1b809887261a0c022ea86222e8e1a1aa7494bba..0f62c38146413dc6f4a31f5642550dc11cfc208d 100644 (file)
@@ -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 (file)
index 0000000..b176631
--- /dev/null
@@ -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 (file)
index 0000000..5ef3204
--- /dev/null
@@ -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 (file)
index 0000000..b940a09
--- /dev/null
@@ -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 (file)
index 0000000..486b67c
--- /dev/null
@@ -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 (file)
index 0000000..ea96c79
--- /dev/null
@@ -0,0 +1,2 @@
+def process_items(items_t: tuple[int, int, str], items_s: set[bytes]):
+    return items_t, items_s
index 9fb1043bb88b55290ed103929f9974bede98562a..a393385b0510927ea45a45307c591f7a7994e7a4 100644 (file)
@@ -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 (file)
index 0000000..e52539e
--- /dev/null
@@ -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 (file)
index 0000000..6bb4378
--- /dev/null
@@ -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 (file)
index 0000000..8464c6f
--- /dev/null
@@ -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 (file)
index 0000000..9f1a05b
--- /dev/null
@@ -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 (file)
index 0000000..7f17388
--- /dev/null
@@ -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 (file)
index 0000000..af79e2d
--- /dev/null
@@ -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 (file)
index 0000000..bedb58c
--- /dev/null
@@ -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 (file)
index 0000000..1eec66d
--- /dev/null
@@ -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 (file)
index 0000000..8ec4338
--- /dev/null
@@ -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 (file)
index 0000000..be2a44c
--- /dev/null
@@ -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 (file)
index 0000000..f0dbfe0
--- /dev/null
@@ -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 (file)
index 0000000..eeefddf
--- /dev/null
@@ -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 (file)
index 0000000..fa3139d
--- /dev/null
@@ -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 (file)
index 0000000..335858a
--- /dev/null
@@ -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 (file)
index 0000000..518b779
--- /dev/null
@@ -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 (file)
index 0000000..14ef4cb
--- /dev/null
@@ -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 (file)
index 0000000..06bb024
--- /dev/null
@@ -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 (file)
index 0000000..e84c116
--- /dev/null
@@ -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 (file)
index 0000000..c358008
--- /dev/null
@@ -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 (file)
index 0000000..c3d992e
--- /dev/null
@@ -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 (file)
index 0000000..38ba764
--- /dev/null
@@ -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 (file)
index 0000000..1900133
--- /dev/null
@@ -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 (file)
index 0000000..26cd567
--- /dev/null
@@ -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 = """
+<body>
+<form action="/files/" enctype="multipart/form-data" method="post">
+<input name="files" type="file" multiple>
+<input type="submit">
+</form>
+<form action="/uploadfiles/" enctype="multipart/form-data" method="post">
+<input name="files" type="file" multiple>
+<input type="submit">
+</form>
+</body>
+    """
+    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 (file)
index 0000000..59efecd
--- /dev/null
@@ -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 (file)
index 0000000..37b8668
--- /dev/null
@@ -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 (file)
index 0000000..29ab9c9
--- /dev/null
@@ -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 (file)
index 0000000..fc9693e
--- /dev/null
@@ -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 (file)
index 0000000..a3e21b4
--- /dev/null
@@ -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 (file)
index 0000000..07ccbbf
--- /dev/null
@@ -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 (file)
index 0000000..acb3035
--- /dev/null
@@ -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 (file)
index 0000000..9ccec32
--- /dev/null
@@ -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 (file)
index 0000000..77ceedd
--- /dev/null
@@ -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 (file)
index 0000000..4f8f830
--- /dev/null
@@ -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 (file)
index 0000000..cf4c99d
--- /dev/null
@@ -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 (file)
index 0000000..6f29c1a
--- /dev/null
@@ -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 (file)
index 0000000..3c2018f
--- /dev/null
@@ -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 (file)
index 0000000..af935e9
--- /dev/null
@@ -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 (file)
index 0000000..797d56d
--- /dev/null
@@ -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 (file)
index 0000000..c6a095d
--- /dev/null
@@ -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 (file)
index 0000000..d45c08c
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
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 (file)
index 0000000..5de88ec
--- /dev/null
@@ -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 (file)
index 0000000..679acdb
--- /dev/null
@@ -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 (file)
index 0000000..45a8b9f
--- /dev/null
@@ -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 (file)
index 0000000..a9856d0
--- /dev/null
@@ -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 (file)
index 0000000..62d8ab4
--- /dev/null
@@ -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 (file)
index 0000000..aea2e3f
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
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 (file)
index 0000000..c60c335
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
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 (file)
index 0000000..5de88ec
--- /dev/null
@@ -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 (file)
index 0000000..679acdb
--- /dev/null
@@ -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 (file)
index 0000000..45a8b9f
--- /dev/null
@@ -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 (file)
index 0000000..a9856d0
--- /dev/null
@@ -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 (file)
index 0000000..62d8ab4
--- /dev/null
@@ -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 (file)
index 0000000..a19f1cd
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
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 (file)
index 0000000..c60c335
--- /dev/null
@@ -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
index e6148831b9598d3e6d500c63f0dc605e58562b9f..fa399c49216ce65f8daa944e24350be80d22d022 100644 (file)
@@ -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 (file)
index 0000000..6937c8f
--- /dev/null
@@ -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 (file)
index 0000000..e292b53
--- /dev/null
@@ -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 (file)
index 0000000..d7a525e
--- /dev/null
@@ -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 (file)
index 0000000..85ba41c
--- /dev/null
@@ -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 (file)
index 0000000..f896f7b
--- /dev/null
@@ -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 (file)
index 0000000..17ca29c
--- /dev/null
@@ -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 (file)
index 0000000..ca1d8c5
--- /dev/null
@@ -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 (file)
index 0000000..f2b184c
--- /dev/null
@@ -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 (file)
index 0000000..587a328
--- /dev/null
@@ -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 (file)
index 0000000..a799117
--- /dev/null
@@ -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 (file)
index 0000000..f66a36a
--- /dev/null
@@ -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 (file)
index 0000000..3d4c1d0
--- /dev/null
@@ -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 (file)
index 0000000..185bc3a
--- /dev/null
@@ -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 (file)
index 0000000..7f4f5b9
--- /dev/null
@@ -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 (file)
index 0000000..3bb5a99
--- /dev/null
@@ -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 (file)
index 0000000..f5ee174
--- /dev/null
@@ -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 (file)
index 0000000..1f617da
--- /dev/null
@@ -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 (file)
index 0000000..ffdf050
--- /dev/null
@@ -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 (file)
index 0000000..1986d27
--- /dev/null
@@ -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 (file)
index 0000000..66b2401
--- /dev/null
@@ -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 (file)
index 0000000..8894ee1
--- /dev/null
@@ -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 (file)
index 0000000..b10e70a
--- /dev/null
@@ -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 (file)
index 0000000..a9cbce0
--- /dev/null
@@ -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 (file)
index 0000000..bbdf25c
--- /dev/null
@@ -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"<file content>")
+    path2 = tmp_path / "test2.txt"
+    path2.write_bytes(b"<file content2>")
+
+    client = TestClient(app)
+    with path.open("rb") as file, path2.open("rb") as file2:
+        response = client.post(
+            "/files/",
+            files=(
+                ("files", ("test.txt", file)),
+                ("files", ("test2.txt", file2)),
+            ),
+        )
+    assert response.status_code == 200, response.text
+    assert response.json() == {"file_sizes": [14, 15]}
+
+
+@needs_py39
+def test_post_upload_file(tmp_path, app: FastAPI):
+    path = tmp_path / "test.txt"
+    path.write_bytes(b"<file content>")
+    path2 = tmp_path / "test2.txt"
+    path2.write_bytes(b"<file content2>")
+
+    client = TestClient(app)
+    with path.open("rb") as file, path2.open("rb") as file2:
+        response = client.post(
+            "/uploadfiles/",
+            files=(
+                ("files", ("test.txt", file)),
+                ("files", ("test2.txt", file2)),
+            ),
+        )
+    assert response.status_code == 200, response.text
+    assert response.json() == {"filenames": ["test.txt", "test2.txt"]}
+
+
+@needs_py39
+def test_get_root(app: FastAPI):
+    client = TestClient(app)
+    response = client.get("/")
+    assert response.status_code == 200, response.text
+    assert b"<form" in response.content
diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_py310.py b/tests/test_tutorial/test_response_model/test_tutorial003_py310.py
new file mode 100644 (file)
index 0000000..ffba116
--- /dev/null
@@ -0,0 +1,129 @@
+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": {
+        "/user/": {
+            "post": {
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {
+                            "application/json": {
+                                "schema": {"$ref": "#/components/schemas/UserOut"}
+                            }
+                        },
+                    },
+                    "422": {
+                        "description": "Validation Error",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/HTTPValidationError"
+                                }
+                            }
+                        },
+                    },
+                },
+                "summary": "Create User",
+                "operationId": "create_user_user__post",
+                "requestBody": {
+                    "content": {
+                        "application/json": {
+                            "schema": {"$ref": "#/components/schemas/UserIn"}
+                        }
+                    },
+                    "required": True,
+                },
+            }
+        }
+    },
+    "components": {
+        "schemas": {
+            "UserOut": {
+                "title": "UserOut",
+                "required": ["username", "email"],
+                "type": "object",
+                "properties": {
+                    "username": {"title": "Username", "type": "string"},
+                    "email": {"title": "Email", "type": "string", "format": "email"},
+                    "full_name": {"title": "Full Name", "type": "string"},
+                },
+            },
+            "UserIn": {
+                "title": "UserIn",
+                "required": ["username", "password", "email"],
+                "type": "object",
+                "properties": {
+                    "username": {"title": "Username", "type": "string"},
+                    "password": {"title": "Password", "type": "string"},
+                    "email": {"title": "Email", "type": "string", "format": "email"},
+                    "full_name": {"title": "Full Name", "type": "string"},
+                },
+            },
+            "ValidationError": {
+                "title": "ValidationError",
+                "required": ["loc", "msg", "type"],
+                "type": "object",
+                "properties": {
+                    "loc": {
+                        "title": "Location",
+                        "type": "array",
+                        "items": {"type": "string"},
+                    },
+                    "msg": {"title": "Message", "type": "string"},
+                    "type": {"title": "Error Type", "type": "string"},
+                },
+            },
+            "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.response_model.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_post_user(client: TestClient):
+    response = client.post(
+        "/user/",
+        json={
+            "username": "foo",
+            "password": "fighter",
+            "email": "foo@example.com",
+            "full_name": "Grave Dohl",
+        },
+    )
+    assert response.status_code == 200, response.text
+    assert response.json() == {
+        "username": "foo",
+        "email": "foo@example.com",
+        "full_name": "Grave Dohl",
+    }
diff --git a/tests/test_tutorial/test_response_model/test_tutorial004_py310.py b/tests/test_tutorial/test_response_model/test_tutorial004_py310.py
new file mode 100644 (file)
index 0000000..f1508a0
--- /dev/null
@@ -0,0 +1,133 @@
+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",
+                    }
+                ],
+            }
+        }
+    },
+    "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", "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.response_model.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(
+    "url,data",
+    [
+        ("/items/foo", {"name": "Foo", "price": 50.2}),
+        (
+            "/items/bar",
+            {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
+        ),
+        (
+            "/items/baz",
+            {
+                "name": "Baz",
+                "description": None,
+                "price": 50.2,
+                "tax": 10.5,
+                "tags": [],
+            },
+        ),
+    ],
+)
+def test_get(url, data, client: TestClient):
+    response = client.get(url)
+    assert response.status_code == 200, response.text
+    assert response.json() == data
diff --git a/tests/test_tutorial/test_response_model/test_tutorial004_py39.py b/tests/test_tutorial/test_response_model/test_tutorial004_py39.py
new file mode 100644 (file)
index 0000000..e5d9c8b
--- /dev/null
@@ -0,0 +1,133 @@
+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",
+                    }
+                ],
+            }
+        }
+    },
+    "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", "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.response_model.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
+@pytest.mark.parametrize(
+    "url,data",
+    [
+        ("/items/foo", {"name": "Foo", "price": 50.2}),
+        (
+            "/items/bar",
+            {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
+        ),
+        (
+            "/items/baz",
+            {
+                "name": "Baz",
+                "description": None,
+                "price": 50.2,
+                "tax": 10.5,
+                "tags": [],
+            },
+        ),
+    ],
+)
+def test_get(url, data, client: TestClient):
+    response = client.get(url)
+    assert response.status_code == 200, response.text
+    assert response.json() == data
diff --git a/tests/test_tutorial/test_response_model/test_tutorial005_py310.py b/tests/test_tutorial/test_response_model/test_tutorial005_py310.py
new file mode 100644 (file)
index 0000000..6d7366f
--- /dev/null
@@ -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/{item_id}/name": {
+            "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 Name",
+                "operationId": "read_item_name_items__item_id__name_get",
+                "parameters": [
+                    {
+                        "required": True,
+                        "schema": {"title": "Item Id", "type": "string"},
+                        "name": "item_id",
+                        "in": "path",
+                    }
+                ],
+            }
+        },
+        "/items/{item_id}/public": {
+            "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 Public Data",
+                "operationId": "read_item_public_data_items__item_id__public_get",
+                "parameters": [
+                    {
+                        "required": True,
+                        "schema": {"title": "Item Id", "type": "string"},
+                        "name": "item_id",
+                        "in": "path",
+                    }
+                ],
+            }
+        },
+    },
+    "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", "default": 10.5},
+                },
+            },
+            "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.response_model.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_read_item_name(client: TestClient):
+    response = client.get("/items/bar/name")
+    assert response.status_code == 200, response.text
+    assert response.json() == {"name": "Bar", "description": "The Bar fighters"}
+
+
+@needs_py310
+def test_read_item_public_data(client: TestClient):
+    response = client.get("/items/bar/public")
+    assert response.status_code == 200, response.text
+    assert response.json() == {
+        "name": "Bar",
+        "description": "The Bar fighters",
+        "price": 62,
+    }
diff --git a/tests/test_tutorial/test_response_model/test_tutorial006_py310.py b/tests/test_tutorial/test_response_model/test_tutorial006_py310.py
new file mode 100644 (file)
index 0000000..a3d8d20
--- /dev/null
@@ -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/{item_id}/name": {
+            "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 Name",
+                "operationId": "read_item_name_items__item_id__name_get",
+                "parameters": [
+                    {
+                        "required": True,
+                        "schema": {"title": "Item Id", "type": "string"},
+                        "name": "item_id",
+                        "in": "path",
+                    }
+                ],
+            }
+        },
+        "/items/{item_id}/public": {
+            "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 Public Data",
+                "operationId": "read_item_public_data_items__item_id__public_get",
+                "parameters": [
+                    {
+                        "required": True,
+                        "schema": {"title": "Item Id", "type": "string"},
+                        "name": "item_id",
+                        "in": "path",
+                    }
+                ],
+            }
+        },
+    },
+    "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", "default": 10.5},
+                },
+            },
+            "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.response_model.tutorial006_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_read_item_name(client: TestClient):
+    response = client.get("/items/bar/name")
+    assert response.status_code == 200, response.text
+    assert response.json() == {"name": "Bar", "description": "The Bar fighters"}
+
+
+@needs_py310
+def test_read_item_public_data(client: TestClient):
+    response = client.get("/items/bar/public")
+    assert response.status_code == 200, response.text
+    assert response.json() == {
+        "name": "Bar",
+        "description": "The Bar fighters",
+        "price": 62,
+    }
diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial004_py310.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial004_py310.py
new file mode 100644 (file)
index 0000000..4f9a2ff
--- /dev/null
@@ -0,0 +1,143 @@
+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": {
+                "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/Item"},
+                            "examples": {
+                                "normal": {
+                                    "summary": "A normal example",
+                                    "description": "A **normal** item works correctly.",
+                                    "value": {
+                                        "name": "Foo",
+                                        "description": "A very nice Item",
+                                        "price": 35.4,
+                                        "tax": 3.2,
+                                    },
+                                },
+                                "converted": {
+                                    "summary": "An example with converted data",
+                                    "description": "FastAPI can convert price `strings` to actual `numbers` automatically",
+                                    "value": {"name": "Bar", "price": "35.4"},
+                                },
+                                "invalid": {
+                                    "summary": "Invalid data is rejected with an error",
+                                    "value": {
+                                        "name": "Baz",
+                                        "price": "thirty five point four",
+                                    },
+                                },
+                            },
+                        }
+                    },
+                    "required": True,
+                },
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {"application/json": {"schema": {}}},
+                    },
+                    "422": {
+                        "description": "Validation Error",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/HTTPValidationError"
+                                }
+                            }
+                        },
+                    },
+                },
+            }
+        }
+    },
+    "components": {
+        "schemas": {
+            "HTTPValidationError": {
+                "title": "HTTPValidationError",
+                "type": "object",
+                "properties": {
+                    "detail": {
+                        "title": "Detail",
+                        "type": "array",
+                        "items": {"$ref": "#/components/schemas/ValidationError"},
+                    }
+                },
+            },
+            "Item": {
+                "title": "Item",
+                "required": ["name", "price"],
+                "type": "object",
+                "properties": {
+                    "name": {"title": "Name", "type": "string"},
+                    "description": {"title": "Description", "type": "string"},
+                    "price": {"title": "Price", "type": "number"},
+                    "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"},
+                },
+            },
+        }
+    },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+    from docs_src.schema_extra_example.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
+
+
+# Test required and embedded body parameters with no bodies sent
+@needs_py310
+def test_post_body_example(client: TestClient):
+    response = client.put(
+        "/items/5",
+        json={
+            "name": "Foo",
+            "description": "A very nice Item",
+            "price": 35.4,
+            "tax": 3.2,
+        },
+    )
+    assert response.status_code == 200
diff --git a/tests/test_tutorial/test_security/test_tutorial003_py310.py b/tests/test_tutorial/test_security/test_tutorial003_py310.py
new file mode 100644 (file)
index 0000000..e621bcd
--- /dev/null
@@ -0,0 +1,192 @@
+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": {
+        "/token": {
+            "post": {
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {"application/json": {"schema": {}}},
+                    },
+                    "422": {
+                        "description": "Validation Error",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/HTTPValidationError"
+                                }
+                            }
+                        },
+                    },
+                },
+                "summary": "Login",
+                "operationId": "login_token_post",
+                "requestBody": {
+                    "content": {
+                        "application/x-www-form-urlencoded": {
+                            "schema": {
+                                "$ref": "#/components/schemas/Body_login_token_post"
+                            }
+                        }
+                    },
+                    "required": True,
+                },
+            }
+        },
+        "/users/me": {
+            "get": {
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {"application/json": {"schema": {}}},
+                    }
+                },
+                "summary": "Read Users Me",
+                "operationId": "read_users_me_users_me_get",
+                "security": [{"OAuth2PasswordBearer": []}],
+            }
+        },
+    },
+    "components": {
+        "schemas": {
+            "Body_login_token_post": {
+                "title": "Body_login_token_post",
+                "required": ["username", "password"],
+                "type": "object",
+                "properties": {
+                    "grant_type": {
+                        "title": "Grant Type",
+                        "pattern": "password",
+                        "type": "string",
+                    },
+                    "username": {"title": "Username", "type": "string"},
+                    "password": {"title": "Password", "type": "string"},
+                    "scope": {"title": "Scope", "type": "string", "default": ""},
+                    "client_id": {"title": "Client Id", "type": "string"},
+                    "client_secret": {"title": "Client Secret", "type": "string"},
+                },
+            },
+            "ValidationError": {
+                "title": "ValidationError",
+                "required": ["loc", "msg", "type"],
+                "type": "object",
+                "properties": {
+                    "loc": {
+                        "title": "Location",
+                        "type": "array",
+                        "items": {"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"},
+                    }
+                },
+            },
+        },
+        "securitySchemes": {
+            "OAuth2PasswordBearer": {
+                "type": "oauth2",
+                "flows": {"password": {"scopes": {}, "tokenUrl": "token"}},
+            }
+        },
+    },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+    from docs_src.security.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_login(client: TestClient):
+    response = client.post("/token", data={"username": "johndoe", "password": "secret"})
+    assert response.status_code == 200, response.text
+    assert response.json() == {"access_token": "johndoe", "token_type": "bearer"}
+
+
+@needs_py310
+def test_login_incorrect_password(client: TestClient):
+    response = client.post(
+        "/token", data={"username": "johndoe", "password": "incorrect"}
+    )
+    assert response.status_code == 400, response.text
+    assert response.json() == {"detail": "Incorrect username or password"}
+
+
+@needs_py310
+def test_login_incorrect_username(client: TestClient):
+    response = client.post("/token", data={"username": "foo", "password": "secret"})
+    assert response.status_code == 400, response.text
+    assert response.json() == {"detail": "Incorrect username or password"}
+
+
+@needs_py310
+def test_no_token(client: TestClient):
+    response = client.get("/users/me")
+    assert response.status_code == 401, response.text
+    assert response.json() == {"detail": "Not authenticated"}
+    assert response.headers["WWW-Authenticate"] == "Bearer"
+
+
+@needs_py310
+def test_token(client: TestClient):
+    response = client.get("/users/me", headers={"Authorization": "Bearer johndoe"})
+    assert response.status_code == 200, response.text
+    assert response.json() == {
+        "username": "johndoe",
+        "full_name": "John Doe",
+        "email": "johndoe@example.com",
+        "hashed_password": "fakehashedsecret",
+        "disabled": False,
+    }
+
+
+@needs_py310
+def test_incorrect_token(client: TestClient):
+    response = client.get("/users/me", headers={"Authorization": "Bearer nonexistent"})
+    assert response.status_code == 401, response.text
+    assert response.json() == {"detail": "Invalid authentication credentials"}
+    assert response.headers["WWW-Authenticate"] == "Bearer"
+
+
+@needs_py310
+def test_incorrect_token_type(client: TestClient):
+    response = client.get(
+        "/users/me", headers={"Authorization": "Notexistent testtoken"}
+    )
+    assert response.status_code == 401, response.text
+    assert response.json() == {"detail": "Not authenticated"}
+    assert response.headers["WWW-Authenticate"] == "Bearer"
+
+
+@needs_py310
+def test_inactive_user(client: TestClient):
+    response = client.get("/users/me", headers={"Authorization": "Bearer alice"})
+    assert response.status_code == 400, response.text
+    assert response.json() == {"detail": "Inactive user"}
diff --git a/tests/test_tutorial/test_security/test_tutorial005_py310.py b/tests/test_tutorial/test_security/test_tutorial005_py310.py
new file mode 100644 (file)
index 0000000..0c9372e
--- /dev/null
@@ -0,0 +1,375 @@
+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": {
+        "/token": {
+            "post": {
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {
+                            "application/json": {
+                                "schema": {"$ref": "#/components/schemas/Token"}
+                            }
+                        },
+                    },
+                    "422": {
+                        "description": "Validation Error",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/HTTPValidationError"
+                                }
+                            }
+                        },
+                    },
+                },
+                "summary": "Login For Access Token",
+                "operationId": "login_for_access_token_token_post",
+                "requestBody": {
+                    "content": {
+                        "application/x-www-form-urlencoded": {
+                            "schema": {
+                                "$ref": "#/components/schemas/Body_login_for_access_token_token_post"
+                            }
+                        }
+                    },
+                    "required": True,
+                },
+            }
+        },
+        "/users/me/": {
+            "get": {
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {
+                            "application/json": {
+                                "schema": {"$ref": "#/components/schemas/User"}
+                            }
+                        },
+                    }
+                },
+                "summary": "Read Users Me",
+                "operationId": "read_users_me_users_me__get",
+                "security": [{"OAuth2PasswordBearer": ["me"]}],
+            }
+        },
+        "/users/me/items/": {
+            "get": {
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {"application/json": {"schema": {}}},
+                    }
+                },
+                "summary": "Read Own Items",
+                "operationId": "read_own_items_users_me_items__get",
+                "security": [{"OAuth2PasswordBearer": ["items", "me"]}],
+            }
+        },
+        "/status/": {
+            "get": {
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {"application/json": {"schema": {}}},
+                    }
+                },
+                "summary": "Read System Status",
+                "operationId": "read_system_status_status__get",
+                "security": [{"OAuth2PasswordBearer": []}],
+            }
+        },
+    },
+    "components": {
+        "schemas": {
+            "User": {
+                "title": "User",
+                "required": ["username"],
+                "type": "object",
+                "properties": {
+                    "username": {"title": "Username", "type": "string"},
+                    "email": {"title": "Email", "type": "string"},
+                    "full_name": {"title": "Full Name", "type": "string"},
+                    "disabled": {"title": "Disabled", "type": "boolean"},
+                },
+            },
+            "Token": {
+                "title": "Token",
+                "required": ["access_token", "token_type"],
+                "type": "object",
+                "properties": {
+                    "access_token": {"title": "Access Token", "type": "string"},
+                    "token_type": {"title": "Token Type", "type": "string"},
+                },
+            },
+            "Body_login_for_access_token_token_post": {
+                "title": "Body_login_for_access_token_token_post",
+                "required": ["username", "password"],
+                "type": "object",
+                "properties": {
+                    "grant_type": {
+                        "title": "Grant Type",
+                        "pattern": "password",
+                        "type": "string",
+                    },
+                    "username": {"title": "Username", "type": "string"},
+                    "password": {"title": "Password", "type": "string"},
+                    "scope": {"title": "Scope", "type": "string", "default": ""},
+                    "client_id": {"title": "Client Id", "type": "string"},
+                    "client_secret": {"title": "Client Secret", "type": "string"},
+                },
+            },
+            "ValidationError": {
+                "title": "ValidationError",
+                "required": ["loc", "msg", "type"],
+                "type": "object",
+                "properties": {
+                    "loc": {
+                        "title": "Location",
+                        "type": "array",
+                        "items": {"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"},
+                    }
+                },
+            },
+        },
+        "securitySchemes": {
+            "OAuth2PasswordBearer": {
+                "type": "oauth2",
+                "flows": {
+                    "password": {
+                        "scopes": {
+                            "me": "Read information about the current user.",
+                            "items": "Read items.",
+                        },
+                        "tokenUrl": "token",
+                    }
+                },
+            }
+        },
+    },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+    from docs_src.security.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
+
+
+def get_access_token(
+    *, username="johndoe", password="secret", scope=None, client: TestClient
+):
+    data = {"username": username, "password": password}
+    if scope:
+        data["scope"] = scope
+    response = client.post("/token", data=data)
+    content = response.json()
+    access_token = content.get("access_token")
+    return access_token
+
+
+@needs_py310
+def test_login(client: TestClient):
+    response = client.post("/token", data={"username": "johndoe", "password": "secret"})
+    assert response.status_code == 200, response.text
+    content = response.json()
+    assert "access_token" in content
+    assert content["token_type"] == "bearer"
+
+
+@needs_py310
+def test_login_incorrect_password(client: TestClient):
+    response = client.post(
+        "/token", data={"username": "johndoe", "password": "incorrect"}
+    )
+    assert response.status_code == 400, response.text
+    assert response.json() == {"detail": "Incorrect username or password"}
+
+
+@needs_py310
+def test_login_incorrect_username(client: TestClient):
+    response = client.post("/token", data={"username": "foo", "password": "secret"})
+    assert response.status_code == 400, response.text
+    assert response.json() == {"detail": "Incorrect username or password"}
+
+
+@needs_py310
+def test_no_token(client: TestClient):
+    response = client.get("/users/me")
+    assert response.status_code == 401, response.text
+    assert response.json() == {"detail": "Not authenticated"}
+    assert response.headers["WWW-Authenticate"] == "Bearer"
+
+
+@needs_py310
+def test_token(client: TestClient):
+    access_token = get_access_token(scope="me", client=client)
+    response = client.get(
+        "/users/me", headers={"Authorization": f"Bearer {access_token}"}
+    )
+    assert response.status_code == 200, response.text
+    assert response.json() == {
+        "username": "johndoe",
+        "full_name": "John Doe",
+        "email": "johndoe@example.com",
+        "disabled": False,
+    }
+
+
+@needs_py310
+def test_incorrect_token(client: TestClient):
+    response = client.get("/users/me", headers={"Authorization": "Bearer nonexistent"})
+    assert response.status_code == 401, response.text
+    assert response.json() == {"detail": "Could not validate credentials"}
+    assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
+
+
+@needs_py310
+def test_incorrect_token_type(client: TestClient):
+    response = client.get(
+        "/users/me", headers={"Authorization": "Notexistent testtoken"}
+    )
+    assert response.status_code == 401, response.text
+    assert response.json() == {"detail": "Not authenticated"}
+    assert response.headers["WWW-Authenticate"] == "Bearer"
+
+
+@needs_py310
+def test_verify_password():
+    from docs_src.security.tutorial005_py310 import fake_users_db, verify_password
+
+    assert verify_password("secret", fake_users_db["johndoe"]["hashed_password"])
+
+
+@needs_py310
+def test_get_password_hash():
+    from docs_src.security.tutorial005_py310 import get_password_hash
+
+    assert get_password_hash("secretalice")
+
+
+@needs_py310
+def test_create_access_token():
+    from docs_src.security.tutorial005_py310 import create_access_token
+
+    access_token = create_access_token(data={"data": "foo"})
+    assert access_token
+
+
+@needs_py310
+def test_token_no_sub(client: TestClient):
+    response = client.get(
+        "/users/me",
+        headers={
+            "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoiZm9vIn0.9ynBhuYb4e6aW3oJr_K_TBgwcMTDpRToQIE25L57rOE"
+        },
+    )
+    assert response.status_code == 401, response.text
+    assert response.json() == {"detail": "Could not validate credentials"}
+    assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
+
+
+@needs_py310
+def test_token_no_username(client: TestClient):
+    response = client.get(
+        "/users/me",
+        headers={
+            "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmb28ifQ.NnExK_dlNAYyzACrXtXDrcWOgGY2JuPbI4eDaHdfK5Y"
+        },
+    )
+    assert response.status_code == 401, response.text
+    assert response.json() == {"detail": "Could not validate credentials"}
+    assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
+
+
+@needs_py310
+def test_token_no_scope(client: TestClient):
+    access_token = get_access_token(client=client)
+    response = client.get(
+        "/users/me", headers={"Authorization": f"Bearer {access_token}"}
+    )
+    assert response.status_code == 401, response.text
+    assert response.json() == {"detail": "Not enough permissions"}
+    assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
+
+
+@needs_py310
+def test_token_inexistent_user(client: TestClient):
+    response = client.get(
+        "/users/me",
+        headers={
+            "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VybmFtZTpib2IifQ.HcfCW67Uda-0gz54ZWTqmtgJnZeNem0Q757eTa9EZuw"
+        },
+    )
+    assert response.status_code == 401, response.text
+    assert response.json() == {"detail": "Could not validate credentials"}
+    assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
+
+
+@needs_py310
+def test_token_inactive_user(client: TestClient):
+    access_token = get_access_token(
+        username="alice", password="secretalice", scope="me", client=client
+    )
+    response = client.get(
+        "/users/me", headers={"Authorization": f"Bearer {access_token}"}
+    )
+    assert response.status_code == 400, response.text
+    assert response.json() == {"detail": "Inactive user"}
+
+
+@needs_py310
+def test_read_items(client: TestClient):
+    access_token = get_access_token(scope="me items", client=client)
+    response = client.get(
+        "/users/me/items/", headers={"Authorization": f"Bearer {access_token}"}
+    )
+    assert response.status_code == 200, response.text
+    assert response.json() == [{"item_id": "Foo", "owner": "johndoe"}]
+
+
+@needs_py310
+def test_read_system_status(client: TestClient):
+    access_token = get_access_token(client=client)
+    response = client.get(
+        "/status/", headers={"Authorization": f"Bearer {access_token}"}
+    )
+    assert response.status_code == 200, response.text
+    assert response.json() == {"status": "ok"}
+
+
+@needs_py310
+def test_read_system_status_no_token(client: TestClient):
+    response = client.get("/status/")
+    assert response.status_code == 401, response.text
+    assert response.json() == {"detail": "Not authenticated"}
+    assert response.headers["WWW-Authenticate"] == "Bearer"
diff --git a/tests/test_tutorial/test_security/test_tutorial005_py39.py b/tests/test_tutorial/test_security/test_tutorial005_py39.py
new file mode 100644 (file)
index 0000000..099ab25
--- /dev/null
@@ -0,0 +1,375 @@
+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": {
+        "/token": {
+            "post": {
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {
+                            "application/json": {
+                                "schema": {"$ref": "#/components/schemas/Token"}
+                            }
+                        },
+                    },
+                    "422": {
+                        "description": "Validation Error",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/HTTPValidationError"
+                                }
+                            }
+                        },
+                    },
+                },
+                "summary": "Login For Access Token",
+                "operationId": "login_for_access_token_token_post",
+                "requestBody": {
+                    "content": {
+                        "application/x-www-form-urlencoded": {
+                            "schema": {
+                                "$ref": "#/components/schemas/Body_login_for_access_token_token_post"
+                            }
+                        }
+                    },
+                    "required": True,
+                },
+            }
+        },
+        "/users/me/": {
+            "get": {
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {
+                            "application/json": {
+                                "schema": {"$ref": "#/components/schemas/User"}
+                            }
+                        },
+                    }
+                },
+                "summary": "Read Users Me",
+                "operationId": "read_users_me_users_me__get",
+                "security": [{"OAuth2PasswordBearer": ["me"]}],
+            }
+        },
+        "/users/me/items/": {
+            "get": {
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {"application/json": {"schema": {}}},
+                    }
+                },
+                "summary": "Read Own Items",
+                "operationId": "read_own_items_users_me_items__get",
+                "security": [{"OAuth2PasswordBearer": ["items", "me"]}],
+            }
+        },
+        "/status/": {
+            "get": {
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {"application/json": {"schema": {}}},
+                    }
+                },
+                "summary": "Read System Status",
+                "operationId": "read_system_status_status__get",
+                "security": [{"OAuth2PasswordBearer": []}],
+            }
+        },
+    },
+    "components": {
+        "schemas": {
+            "User": {
+                "title": "User",
+                "required": ["username"],
+                "type": "object",
+                "properties": {
+                    "username": {"title": "Username", "type": "string"},
+                    "email": {"title": "Email", "type": "string"},
+                    "full_name": {"title": "Full Name", "type": "string"},
+                    "disabled": {"title": "Disabled", "type": "boolean"},
+                },
+            },
+            "Token": {
+                "title": "Token",
+                "required": ["access_token", "token_type"],
+                "type": "object",
+                "properties": {
+                    "access_token": {"title": "Access Token", "type": "string"},
+                    "token_type": {"title": "Token Type", "type": "string"},
+                },
+            },
+            "Body_login_for_access_token_token_post": {
+                "title": "Body_login_for_access_token_token_post",
+                "required": ["username", "password"],
+                "type": "object",
+                "properties": {
+                    "grant_type": {
+                        "title": "Grant Type",
+                        "pattern": "password",
+                        "type": "string",
+                    },
+                    "username": {"title": "Username", "type": "string"},
+                    "password": {"title": "Password", "type": "string"},
+                    "scope": {"title": "Scope", "type": "string", "default": ""},
+                    "client_id": {"title": "Client Id", "type": "string"},
+                    "client_secret": {"title": "Client Secret", "type": "string"},
+                },
+            },
+            "ValidationError": {
+                "title": "ValidationError",
+                "required": ["loc", "msg", "type"],
+                "type": "object",
+                "properties": {
+                    "loc": {
+                        "title": "Location",
+                        "type": "array",
+                        "items": {"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"},
+                    }
+                },
+            },
+        },
+        "securitySchemes": {
+            "OAuth2PasswordBearer": {
+                "type": "oauth2",
+                "flows": {
+                    "password": {
+                        "scopes": {
+                            "me": "Read information about the current user.",
+                            "items": "Read items.",
+                        },
+                        "tokenUrl": "token",
+                    }
+                },
+            }
+        },
+    },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+    from docs_src.security.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
+
+
+def get_access_token(
+    *, username="johndoe", password="secret", scope=None, client: TestClient
+):
+    data = {"username": username, "password": password}
+    if scope:
+        data["scope"] = scope
+    response = client.post("/token", data=data)
+    content = response.json()
+    access_token = content.get("access_token")
+    return access_token
+
+
+@needs_py39
+def test_login(client: TestClient):
+    response = client.post("/token", data={"username": "johndoe", "password": "secret"})
+    assert response.status_code == 200, response.text
+    content = response.json()
+    assert "access_token" in content
+    assert content["token_type"] == "bearer"
+
+
+@needs_py39
+def test_login_incorrect_password(client: TestClient):
+    response = client.post(
+        "/token", data={"username": "johndoe", "password": "incorrect"}
+    )
+    assert response.status_code == 400, response.text
+    assert response.json() == {"detail": "Incorrect username or password"}
+
+
+@needs_py39
+def test_login_incorrect_username(client: TestClient):
+    response = client.post("/token", data={"username": "foo", "password": "secret"})
+    assert response.status_code == 400, response.text
+    assert response.json() == {"detail": "Incorrect username or password"}
+
+
+@needs_py39
+def test_no_token(client: TestClient):
+    response = client.get("/users/me")
+    assert response.status_code == 401, response.text
+    assert response.json() == {"detail": "Not authenticated"}
+    assert response.headers["WWW-Authenticate"] == "Bearer"
+
+
+@needs_py39
+def test_token(client: TestClient):
+    access_token = get_access_token(scope="me", client=client)
+    response = client.get(
+        "/users/me", headers={"Authorization": f"Bearer {access_token}"}
+    )
+    assert response.status_code == 200, response.text
+    assert response.json() == {
+        "username": "johndoe",
+        "full_name": "John Doe",
+        "email": "johndoe@example.com",
+        "disabled": False,
+    }
+
+
+@needs_py39
+def test_incorrect_token(client: TestClient):
+    response = client.get("/users/me", headers={"Authorization": "Bearer nonexistent"})
+    assert response.status_code == 401, response.text
+    assert response.json() == {"detail": "Could not validate credentials"}
+    assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
+
+
+@needs_py39
+def test_incorrect_token_type(client: TestClient):
+    response = client.get(
+        "/users/me", headers={"Authorization": "Notexistent testtoken"}
+    )
+    assert response.status_code == 401, response.text
+    assert response.json() == {"detail": "Not authenticated"}
+    assert response.headers["WWW-Authenticate"] == "Bearer"
+
+
+@needs_py39
+def test_verify_password():
+    from docs_src.security.tutorial005_py39 import fake_users_db, verify_password
+
+    assert verify_password("secret", fake_users_db["johndoe"]["hashed_password"])
+
+
+@needs_py39
+def test_get_password_hash():
+    from docs_src.security.tutorial005_py39 import get_password_hash
+
+    assert get_password_hash("secretalice")
+
+
+@needs_py39
+def test_create_access_token():
+    from docs_src.security.tutorial005_py39 import create_access_token
+
+    access_token = create_access_token(data={"data": "foo"})
+    assert access_token
+
+
+@needs_py39
+def test_token_no_sub(client: TestClient):
+    response = client.get(
+        "/users/me",
+        headers={
+            "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoiZm9vIn0.9ynBhuYb4e6aW3oJr_K_TBgwcMTDpRToQIE25L57rOE"
+        },
+    )
+    assert response.status_code == 401, response.text
+    assert response.json() == {"detail": "Could not validate credentials"}
+    assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
+
+
+@needs_py39
+def test_token_no_username(client: TestClient):
+    response = client.get(
+        "/users/me",
+        headers={
+            "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmb28ifQ.NnExK_dlNAYyzACrXtXDrcWOgGY2JuPbI4eDaHdfK5Y"
+        },
+    )
+    assert response.status_code == 401, response.text
+    assert response.json() == {"detail": "Could not validate credentials"}
+    assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
+
+
+@needs_py39
+def test_token_no_scope(client: TestClient):
+    access_token = get_access_token(client=client)
+    response = client.get(
+        "/users/me", headers={"Authorization": f"Bearer {access_token}"}
+    )
+    assert response.status_code == 401, response.text
+    assert response.json() == {"detail": "Not enough permissions"}
+    assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
+
+
+@needs_py39
+def test_token_inexistent_user(client: TestClient):
+    response = client.get(
+        "/users/me",
+        headers={
+            "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VybmFtZTpib2IifQ.HcfCW67Uda-0gz54ZWTqmtgJnZeNem0Q757eTa9EZuw"
+        },
+    )
+    assert response.status_code == 401, response.text
+    assert response.json() == {"detail": "Could not validate credentials"}
+    assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"'
+
+
+@needs_py39
+def test_token_inactive_user(client: TestClient):
+    access_token = get_access_token(
+        username="alice", password="secretalice", scope="me", client=client
+    )
+    response = client.get(
+        "/users/me", headers={"Authorization": f"Bearer {access_token}"}
+    )
+    assert response.status_code == 400, response.text
+    assert response.json() == {"detail": "Inactive user"}
+
+
+@needs_py39
+def test_read_items(client: TestClient):
+    access_token = get_access_token(scope="me items", client=client)
+    response = client.get(
+        "/users/me/items/", headers={"Authorization": f"Bearer {access_token}"}
+    )
+    assert response.status_code == 200, response.text
+    assert response.json() == [{"item_id": "Foo", "owner": "johndoe"}]
+
+
+@needs_py39
+def test_read_system_status(client: TestClient):
+    access_token = get_access_token(client=client)
+    response = client.get(
+        "/status/", headers={"Authorization": f"Bearer {access_token}"}
+    )
+    assert response.status_code == 200, response.text
+    assert response.json() == {"status": "ok"}
+
+
+@needs_py39
+def test_read_system_status_no_token(client: TestClient):
+    response = client.get("/status/")
+    assert response.status_code == 401, response.text
+    assert response.json() == {"detail": "Not authenticated"}
+    assert response.headers["WWW-Authenticate"] == "Bearer"
index cd3f13e7a9987a60745f29e24c0f24ae4b045262..c88fd0bcdc30a8eba292ae30c4e75c3aa1f1fc2a 100644 (file)
@@ -1,4 +1,5 @@
 import importlib
+import os
 from pathlib import Path
 
 import pytest
@@ -283,7 +284,10 @@ openapi_schema = {
 
 
 @pytest.fixture(scope="module")
-def client():
+def client(tmp_path_factory: pytest.TempPathFactory):
+    tmp_path = tmp_path_factory.mktemp("data")
+    cwd = os.getcwd()
+    os.chdir(tmp_path)
     test_db = Path("./sql_app.db")
     if test_db.is_file():  # pragma: nocover
         test_db.unlink()
@@ -296,6 +300,7 @@ def client():
         yield c
     if test_db.is_file():  # pragma: nocover
         test_db.unlink()
+    os.chdir(cwd)
 
 
 def test_openapi_schema(client):
diff --git a/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py310.py b/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py310.py
new file mode 100644 (file)
index 0000000..1d0442e
--- /dev/null
@@ -0,0 +1,384 @@
+import importlib
+import os
+from pathlib import Path
+
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+    "openapi": "3.0.2",
+    "info": {"title": "FastAPI", "version": "0.1.0"},
+    "paths": {
+        "/users/": {
+            "get": {
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "title": "Response Read Users Users  Get",
+                                    "type": "array",
+                                    "items": {"$ref": "#/components/schemas/User"},
+                                }
+                            }
+                        },
+                    },
+                    "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": "Skip", "type": "integer", "default": 0},
+                        "name": "skip",
+                        "in": "query",
+                    },
+                    {
+                        "required": False,
+                        "schema": {"title": "Limit", "type": "integer", "default": 100},
+                        "name": "limit",
+                        "in": "query",
+                    },
+                ],
+            },
+            "post": {
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {
+                            "application/json": {
+                                "schema": {"$ref": "#/components/schemas/User"}
+                            }
+                        },
+                    },
+                    "422": {
+                        "description": "Validation Error",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/HTTPValidationError"
+                                }
+                            }
+                        },
+                    },
+                },
+                "summary": "Create User",
+                "operationId": "create_user_users__post",
+                "requestBody": {
+                    "content": {
+                        "application/json": {
+                            "schema": {"$ref": "#/components/schemas/UserCreate"}
+                        }
+                    },
+                    "required": True,
+                },
+            },
+        },
+        "/users/{user_id}": {
+            "get": {
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {
+                            "application/json": {
+                                "schema": {"$ref": "#/components/schemas/User"}
+                            }
+                        },
+                    },
+                    "422": {
+                        "description": "Validation Error",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/HTTPValidationError"
+                                }
+                            }
+                        },
+                    },
+                },
+                "summary": "Read User",
+                "operationId": "read_user_users__user_id__get",
+                "parameters": [
+                    {
+                        "required": True,
+                        "schema": {"title": "User Id", "type": "integer"},
+                        "name": "user_id",
+                        "in": "path",
+                    }
+                ],
+            }
+        },
+        "/users/{user_id}/items/": {
+            "post": {
+                "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": "Create Item For User",
+                "operationId": "create_item_for_user_users__user_id__items__post",
+                "parameters": [
+                    {
+                        "required": True,
+                        "schema": {"title": "User Id", "type": "integer"},
+                        "name": "user_id",
+                        "in": "path",
+                    }
+                ],
+                "requestBody": {
+                    "content": {
+                        "application/json": {
+                            "schema": {"$ref": "#/components/schemas/ItemCreate"}
+                        }
+                    },
+                    "required": True,
+                },
+            }
+        },
+        "/items/": {
+            "get": {
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "title": "Response Read Items Items  Get",
+                                    "type": "array",
+                                    "items": {"$ref": "#/components/schemas/Item"},
+                                }
+                            }
+                        },
+                    },
+                    "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": "Skip", "type": "integer", "default": 0},
+                        "name": "skip",
+                        "in": "query",
+                    },
+                    {
+                        "required": False,
+                        "schema": {"title": "Limit", "type": "integer", "default": 100},
+                        "name": "limit",
+                        "in": "query",
+                    },
+                ],
+            }
+        },
+    },
+    "components": {
+        "schemas": {
+            "ItemCreate": {
+                "title": "ItemCreate",
+                "required": ["title"],
+                "type": "object",
+                "properties": {
+                    "title": {"title": "Title", "type": "string"},
+                    "description": {"title": "Description", "type": "string"},
+                },
+            },
+            "Item": {
+                "title": "Item",
+                "required": ["title", "id", "owner_id"],
+                "type": "object",
+                "properties": {
+                    "title": {"title": "Title", "type": "string"},
+                    "description": {"title": "Description", "type": "string"},
+                    "id": {"title": "Id", "type": "integer"},
+                    "owner_id": {"title": "Owner Id", "type": "integer"},
+                },
+            },
+            "User": {
+                "title": "User",
+                "required": ["email", "id", "is_active"],
+                "type": "object",
+                "properties": {
+                    "email": {"title": "Email", "type": "string"},
+                    "id": {"title": "Id", "type": "integer"},
+                    "is_active": {"title": "Is Active", "type": "boolean"},
+                    "items": {
+                        "title": "Items",
+                        "type": "array",
+                        "items": {"$ref": "#/components/schemas/Item"},
+                        "default": [],
+                    },
+                },
+            },
+            "UserCreate": {
+                "title": "UserCreate",
+                "required": ["email", "password"],
+                "type": "object",
+                "properties": {
+                    "email": {"title": "Email", "type": "string"},
+                    "password": {"title": "Password", "type": "string"},
+                },
+            },
+            "ValidationError": {
+                "title": "ValidationError",
+                "required": ["loc", "msg", "type"],
+                "type": "object",
+                "properties": {
+                    "loc": {
+                        "title": "Location",
+                        "type": "array",
+                        "items": {"type": "string"},
+                    },
+                    "msg": {"title": "Message", "type": "string"},
+                    "type": {"title": "Error Type", "type": "string"},
+                },
+            },
+            "HTTPValidationError": {
+                "title": "HTTPValidationError",
+                "type": "object",
+                "properties": {
+                    "detail": {
+                        "title": "Detail",
+                        "type": "array",
+                        "items": {"$ref": "#/components/schemas/ValidationError"},
+                    }
+                },
+            },
+        }
+    },
+}
+
+
+@pytest.fixture(scope="module")
+def client(tmp_path_factory: pytest.TempPathFactory):
+    tmp_path = tmp_path_factory.mktemp("data")
+    cwd = os.getcwd()
+    os.chdir(tmp_path)
+    test_db = Path("./sql_app.db")
+    if test_db.is_file():  # pragma: nocover
+        test_db.unlink()
+    # Import while creating the client to create the DB after starting the test session
+    from docs_src.sql_databases.sql_app_py310 import alt_main
+
+    # Ensure import side effects are re-executed
+    importlib.reload(alt_main)
+
+    with TestClient(alt_main.app) as c:
+        yield c
+    if test_db.is_file():  # pragma: nocover
+        test_db.unlink()
+    os.chdir(cwd)
+
+
+@needs_py310
+def test_openapi_schema(client):
+    response = client.get("/openapi.json")
+    assert response.status_code == 200, response.text
+    assert response.json() == openapi_schema
+
+
+@needs_py310
+def test_create_user(client):
+    test_user = {"email": "johndoe@example.com", "password": "secret"}
+    response = client.post("/users/", json=test_user)
+    assert response.status_code == 200, response.text
+    data = response.json()
+    assert test_user["email"] == data["email"]
+    assert "id" in data
+    response = client.post("/users/", json=test_user)
+    assert response.status_code == 400, response.text
+
+
+@needs_py310
+def test_get_user(client):
+    response = client.get("/users/1")
+    assert response.status_code == 200, response.text
+    data = response.json()
+    assert "email" in data
+    assert "id" in data
+
+
+@needs_py310
+def test_inexistent_user(client):
+    response = client.get("/users/999")
+    assert response.status_code == 404, response.text
+
+
+@needs_py310
+def test_get_users(client):
+    response = client.get("/users/")
+    assert response.status_code == 200, response.text
+    data = response.json()
+    assert "email" in data[0]
+    assert "id" in data[0]
+
+
+@needs_py310
+def test_create_item(client):
+    item = {"title": "Foo", "description": "Something that fights"}
+    response = client.post("/users/1/items/", json=item)
+    assert response.status_code == 200, response.text
+    item_data = response.json()
+    assert item["title"] == item_data["title"]
+    assert item["description"] == item_data["description"]
+    assert "id" in item_data
+    assert "owner_id" in item_data
+    response = client.get("/users/1")
+    assert response.status_code == 200, response.text
+    user_data = response.json()
+    item_to_check = [it for it in user_data["items"] if it["id"] == item_data["id"]][0]
+    assert item_to_check["title"] == item["title"]
+    assert item_to_check["description"] == item["description"]
+    response = client.get("/users/1")
+    assert response.status_code == 200, response.text
+    user_data = response.json()
+    item_to_check = [it for it in user_data["items"] if it["id"] == item_data["id"]][0]
+    assert item_to_check["title"] == item["title"]
+    assert item_to_check["description"] == item["description"]
+
+
+@needs_py310
+def test_read_items(client):
+    response = client.get("/items/")
+    assert response.status_code == 200, response.text
+    data = response.json()
+    assert data
+    first_item = data[0]
+    assert "title" in first_item
+    assert "description" in first_item
diff --git a/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py39.py b/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py39.py
new file mode 100644 (file)
index 0000000..8764d07
--- /dev/null
@@ -0,0 +1,384 @@
+import importlib
+import os
+from pathlib import Path
+
+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": {
+        "/users/": {
+            "get": {
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "title": "Response Read Users Users  Get",
+                                    "type": "array",
+                                    "items": {"$ref": "#/components/schemas/User"},
+                                }
+                            }
+                        },
+                    },
+                    "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": "Skip", "type": "integer", "default": 0},
+                        "name": "skip",
+                        "in": "query",
+                    },
+                    {
+                        "required": False,
+                        "schema": {"title": "Limit", "type": "integer", "default": 100},
+                        "name": "limit",
+                        "in": "query",
+                    },
+                ],
+            },
+            "post": {
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {
+                            "application/json": {
+                                "schema": {"$ref": "#/components/schemas/User"}
+                            }
+                        },
+                    },
+                    "422": {
+                        "description": "Validation Error",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/HTTPValidationError"
+                                }
+                            }
+                        },
+                    },
+                },
+                "summary": "Create User",
+                "operationId": "create_user_users__post",
+                "requestBody": {
+                    "content": {
+                        "application/json": {
+                            "schema": {"$ref": "#/components/schemas/UserCreate"}
+                        }
+                    },
+                    "required": True,
+                },
+            },
+        },
+        "/users/{user_id}": {
+            "get": {
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {
+                            "application/json": {
+                                "schema": {"$ref": "#/components/schemas/User"}
+                            }
+                        },
+                    },
+                    "422": {
+                        "description": "Validation Error",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/HTTPValidationError"
+                                }
+                            }
+                        },
+                    },
+                },
+                "summary": "Read User",
+                "operationId": "read_user_users__user_id__get",
+                "parameters": [
+                    {
+                        "required": True,
+                        "schema": {"title": "User Id", "type": "integer"},
+                        "name": "user_id",
+                        "in": "path",
+                    }
+                ],
+            }
+        },
+        "/users/{user_id}/items/": {
+            "post": {
+                "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": "Create Item For User",
+                "operationId": "create_item_for_user_users__user_id__items__post",
+                "parameters": [
+                    {
+                        "required": True,
+                        "schema": {"title": "User Id", "type": "integer"},
+                        "name": "user_id",
+                        "in": "path",
+                    }
+                ],
+                "requestBody": {
+                    "content": {
+                        "application/json": {
+                            "schema": {"$ref": "#/components/schemas/ItemCreate"}
+                        }
+                    },
+                    "required": True,
+                },
+            }
+        },
+        "/items/": {
+            "get": {
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "title": "Response Read Items Items  Get",
+                                    "type": "array",
+                                    "items": {"$ref": "#/components/schemas/Item"},
+                                }
+                            }
+                        },
+                    },
+                    "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": "Skip", "type": "integer", "default": 0},
+                        "name": "skip",
+                        "in": "query",
+                    },
+                    {
+                        "required": False,
+                        "schema": {"title": "Limit", "type": "integer", "default": 100},
+                        "name": "limit",
+                        "in": "query",
+                    },
+                ],
+            }
+        },
+    },
+    "components": {
+        "schemas": {
+            "ItemCreate": {
+                "title": "ItemCreate",
+                "required": ["title"],
+                "type": "object",
+                "properties": {
+                    "title": {"title": "Title", "type": "string"},
+                    "description": {"title": "Description", "type": "string"},
+                },
+            },
+            "Item": {
+                "title": "Item",
+                "required": ["title", "id", "owner_id"],
+                "type": "object",
+                "properties": {
+                    "title": {"title": "Title", "type": "string"},
+                    "description": {"title": "Description", "type": "string"},
+                    "id": {"title": "Id", "type": "integer"},
+                    "owner_id": {"title": "Owner Id", "type": "integer"},
+                },
+            },
+            "User": {
+                "title": "User",
+                "required": ["email", "id", "is_active"],
+                "type": "object",
+                "properties": {
+                    "email": {"title": "Email", "type": "string"},
+                    "id": {"title": "Id", "type": "integer"},
+                    "is_active": {"title": "Is Active", "type": "boolean"},
+                    "items": {
+                        "title": "Items",
+                        "type": "array",
+                        "items": {"$ref": "#/components/schemas/Item"},
+                        "default": [],
+                    },
+                },
+            },
+            "UserCreate": {
+                "title": "UserCreate",
+                "required": ["email", "password"],
+                "type": "object",
+                "properties": {
+                    "email": {"title": "Email", "type": "string"},
+                    "password": {"title": "Password", "type": "string"},
+                },
+            },
+            "ValidationError": {
+                "title": "ValidationError",
+                "required": ["loc", "msg", "type"],
+                "type": "object",
+                "properties": {
+                    "loc": {
+                        "title": "Location",
+                        "type": "array",
+                        "items": {"type": "string"},
+                    },
+                    "msg": {"title": "Message", "type": "string"},
+                    "type": {"title": "Error Type", "type": "string"},
+                },
+            },
+            "HTTPValidationError": {
+                "title": "HTTPValidationError",
+                "type": "object",
+                "properties": {
+                    "detail": {
+                        "title": "Detail",
+                        "type": "array",
+                        "items": {"$ref": "#/components/schemas/ValidationError"},
+                    }
+                },
+            },
+        }
+    },
+}
+
+
+@pytest.fixture(scope="module")
+def client(tmp_path_factory: pytest.TempPathFactory):
+    tmp_path = tmp_path_factory.mktemp("data")
+    cwd = os.getcwd()
+    os.chdir(tmp_path)
+    test_db = Path("./sql_app.db")
+    if test_db.is_file():  # pragma: nocover
+        test_db.unlink()
+    # Import while creating the client to create the DB after starting the test session
+    from docs_src.sql_databases.sql_app_py39 import alt_main
+
+    # Ensure import side effects are re-executed
+    importlib.reload(alt_main)
+
+    with TestClient(alt_main.app) as c:
+        yield c
+    if test_db.is_file():  # pragma: nocover
+        test_db.unlink()
+    os.chdir(cwd)
+
+
+@needs_py39
+def test_openapi_schema(client):
+    response = client.get("/openapi.json")
+    assert response.status_code == 200, response.text
+    assert response.json() == openapi_schema
+
+
+@needs_py39
+def test_create_user(client):
+    test_user = {"email": "johndoe@example.com", "password": "secret"}
+    response = client.post("/users/", json=test_user)
+    assert response.status_code == 200, response.text
+    data = response.json()
+    assert test_user["email"] == data["email"]
+    assert "id" in data
+    response = client.post("/users/", json=test_user)
+    assert response.status_code == 400, response.text
+
+
+@needs_py39
+def test_get_user(client):
+    response = client.get("/users/1")
+    assert response.status_code == 200, response.text
+    data = response.json()
+    assert "email" in data
+    assert "id" in data
+
+
+@needs_py39
+def test_inexistent_user(client):
+    response = client.get("/users/999")
+    assert response.status_code == 404, response.text
+
+
+@needs_py39
+def test_get_users(client):
+    response = client.get("/users/")
+    assert response.status_code == 200, response.text
+    data = response.json()
+    assert "email" in data[0]
+    assert "id" in data[0]
+
+
+@needs_py39
+def test_create_item(client):
+    item = {"title": "Foo", "description": "Something that fights"}
+    response = client.post("/users/1/items/", json=item)
+    assert response.status_code == 200, response.text
+    item_data = response.json()
+    assert item["title"] == item_data["title"]
+    assert item["description"] == item_data["description"]
+    assert "id" in item_data
+    assert "owner_id" in item_data
+    response = client.get("/users/1")
+    assert response.status_code == 200, response.text
+    user_data = response.json()
+    item_to_check = [it for it in user_data["items"] if it["id"] == item_data["id"]][0]
+    assert item_to_check["title"] == item["title"]
+    assert item_to_check["description"] == item["description"]
+    response = client.get("/users/1")
+    assert response.status_code == 200, response.text
+    user_data = response.json()
+    item_to_check = [it for it in user_data["items"] if it["id"] == item_data["id"]][0]
+    assert item_to_check["title"] == item["title"]
+    assert item_to_check["description"] == item["description"]
+
+
+@needs_py39
+def test_read_items(client):
+    response = client.get("/items/")
+    assert response.status_code == 200, response.text
+    data = response.json()
+    assert data
+    first_item = data[0]
+    assert "title" in first_item
+    assert "description" in first_item
diff --git a/tests/test_tutorial/test_sql_databases/test_sql_databases_py310.py b/tests/test_tutorial/test_sql_databases/test_sql_databases_py310.py
new file mode 100644 (file)
index 0000000..f7e73de
--- /dev/null
@@ -0,0 +1,383 @@
+import importlib
+import os
+from pathlib import Path
+
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+    "openapi": "3.0.2",
+    "info": {"title": "FastAPI", "version": "0.1.0"},
+    "paths": {
+        "/users/": {
+            "get": {
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "title": "Response Read Users Users  Get",
+                                    "type": "array",
+                                    "items": {"$ref": "#/components/schemas/User"},
+                                }
+                            }
+                        },
+                    },
+                    "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": "Skip", "type": "integer", "default": 0},
+                        "name": "skip",
+                        "in": "query",
+                    },
+                    {
+                        "required": False,
+                        "schema": {"title": "Limit", "type": "integer", "default": 100},
+                        "name": "limit",
+                        "in": "query",
+                    },
+                ],
+            },
+            "post": {
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {
+                            "application/json": {
+                                "schema": {"$ref": "#/components/schemas/User"}
+                            }
+                        },
+                    },
+                    "422": {
+                        "description": "Validation Error",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/HTTPValidationError"
+                                }
+                            }
+                        },
+                    },
+                },
+                "summary": "Create User",
+                "operationId": "create_user_users__post",
+                "requestBody": {
+                    "content": {
+                        "application/json": {
+                            "schema": {"$ref": "#/components/schemas/UserCreate"}
+                        }
+                    },
+                    "required": True,
+                },
+            },
+        },
+        "/users/{user_id}": {
+            "get": {
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {
+                            "application/json": {
+                                "schema": {"$ref": "#/components/schemas/User"}
+                            }
+                        },
+                    },
+                    "422": {
+                        "description": "Validation Error",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/HTTPValidationError"
+                                }
+                            }
+                        },
+                    },
+                },
+                "summary": "Read User",
+                "operationId": "read_user_users__user_id__get",
+                "parameters": [
+                    {
+                        "required": True,
+                        "schema": {"title": "User Id", "type": "integer"},
+                        "name": "user_id",
+                        "in": "path",
+                    }
+                ],
+            }
+        },
+        "/users/{user_id}/items/": {
+            "post": {
+                "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": "Create Item For User",
+                "operationId": "create_item_for_user_users__user_id__items__post",
+                "parameters": [
+                    {
+                        "required": True,
+                        "schema": {"title": "User Id", "type": "integer"},
+                        "name": "user_id",
+                        "in": "path",
+                    }
+                ],
+                "requestBody": {
+                    "content": {
+                        "application/json": {
+                            "schema": {"$ref": "#/components/schemas/ItemCreate"}
+                        }
+                    },
+                    "required": True,
+                },
+            }
+        },
+        "/items/": {
+            "get": {
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "title": "Response Read Items Items  Get",
+                                    "type": "array",
+                                    "items": {"$ref": "#/components/schemas/Item"},
+                                }
+                            }
+                        },
+                    },
+                    "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": "Skip", "type": "integer", "default": 0},
+                        "name": "skip",
+                        "in": "query",
+                    },
+                    {
+                        "required": False,
+                        "schema": {"title": "Limit", "type": "integer", "default": 100},
+                        "name": "limit",
+                        "in": "query",
+                    },
+                ],
+            }
+        },
+    },
+    "components": {
+        "schemas": {
+            "ItemCreate": {
+                "title": "ItemCreate",
+                "required": ["title"],
+                "type": "object",
+                "properties": {
+                    "title": {"title": "Title", "type": "string"},
+                    "description": {"title": "Description", "type": "string"},
+                },
+            },
+            "Item": {
+                "title": "Item",
+                "required": ["title", "id", "owner_id"],
+                "type": "object",
+                "properties": {
+                    "title": {"title": "Title", "type": "string"},
+                    "description": {"title": "Description", "type": "string"},
+                    "id": {"title": "Id", "type": "integer"},
+                    "owner_id": {"title": "Owner Id", "type": "integer"},
+                },
+            },
+            "User": {
+                "title": "User",
+                "required": ["email", "id", "is_active"],
+                "type": "object",
+                "properties": {
+                    "email": {"title": "Email", "type": "string"},
+                    "id": {"title": "Id", "type": "integer"},
+                    "is_active": {"title": "Is Active", "type": "boolean"},
+                    "items": {
+                        "title": "Items",
+                        "type": "array",
+                        "items": {"$ref": "#/components/schemas/Item"},
+                        "default": [],
+                    },
+                },
+            },
+            "UserCreate": {
+                "title": "UserCreate",
+                "required": ["email", "password"],
+                "type": "object",
+                "properties": {
+                    "email": {"title": "Email", "type": "string"},
+                    "password": {"title": "Password", "type": "string"},
+                },
+            },
+            "ValidationError": {
+                "title": "ValidationError",
+                "required": ["loc", "msg", "type"],
+                "type": "object",
+                "properties": {
+                    "loc": {
+                        "title": "Location",
+                        "type": "array",
+                        "items": {"type": "string"},
+                    },
+                    "msg": {"title": "Message", "type": "string"},
+                    "type": {"title": "Error Type", "type": "string"},
+                },
+            },
+            "HTTPValidationError": {
+                "title": "HTTPValidationError",
+                "type": "object",
+                "properties": {
+                    "detail": {
+                        "title": "Detail",
+                        "type": "array",
+                        "items": {"$ref": "#/components/schemas/ValidationError"},
+                    }
+                },
+            },
+        }
+    },
+}
+
+
+@pytest.fixture(scope="module", name="client")
+def get_client(tmp_path_factory: pytest.TempPathFactory):
+    tmp_path = tmp_path_factory.mktemp("data")
+    cwd = os.getcwd()
+    os.chdir(tmp_path)
+    test_db = Path("./sql_app.db")
+    if test_db.is_file():  # pragma: nocover
+        test_db.unlink()
+    # Import while creating the client to create the DB after starting the test session
+    from docs_src.sql_databases.sql_app_py310 import main
+
+    # Ensure import side effects are re-executed
+    importlib.reload(main)
+    with TestClient(main.app) as c:
+        yield c
+    if test_db.is_file():  # pragma: nocover
+        test_db.unlink()
+    os.chdir(cwd)
+
+
+@needs_py310
+def test_openapi_schema(client):
+    response = client.get("/openapi.json")
+    assert response.status_code == 200, response.text
+    assert response.json() == openapi_schema
+
+
+@needs_py310
+def test_create_user(client):
+    test_user = {"email": "johndoe@example.com", "password": "secret"}
+    response = client.post("/users/", json=test_user)
+    assert response.status_code == 200, response.text
+    data = response.json()
+    assert test_user["email"] == data["email"]
+    assert "id" in data
+    response = client.post("/users/", json=test_user)
+    assert response.status_code == 400, response.text
+
+
+@needs_py310
+def test_get_user(client):
+    response = client.get("/users/1")
+    assert response.status_code == 200, response.text
+    data = response.json()
+    assert "email" in data
+    assert "id" in data
+
+
+@needs_py310
+def test_inexistent_user(client):
+    response = client.get("/users/999")
+    assert response.status_code == 404, response.text
+
+
+@needs_py310
+def test_get_users(client):
+    response = client.get("/users/")
+    assert response.status_code == 200, response.text
+    data = response.json()
+    assert "email" in data[0]
+    assert "id" in data[0]
+
+
+@needs_py310
+def test_create_item(client):
+    item = {"title": "Foo", "description": "Something that fights"}
+    response = client.post("/users/1/items/", json=item)
+    assert response.status_code == 200, response.text
+    item_data = response.json()
+    assert item["title"] == item_data["title"]
+    assert item["description"] == item_data["description"]
+    assert "id" in item_data
+    assert "owner_id" in item_data
+    response = client.get("/users/1")
+    assert response.status_code == 200, response.text
+    user_data = response.json()
+    item_to_check = [it for it in user_data["items"] if it["id"] == item_data["id"]][0]
+    assert item_to_check["title"] == item["title"]
+    assert item_to_check["description"] == item["description"]
+    response = client.get("/users/1")
+    assert response.status_code == 200, response.text
+    user_data = response.json()
+    item_to_check = [it for it in user_data["items"] if it["id"] == item_data["id"]][0]
+    assert item_to_check["title"] == item["title"]
+    assert item_to_check["description"] == item["description"]
+
+
+@needs_py310
+def test_read_items(client):
+    response = client.get("/items/")
+    assert response.status_code == 200, response.text
+    data = response.json()
+    assert data
+    first_item = data[0]
+    assert "title" in first_item
+    assert "description" in first_item
diff --git a/tests/test_tutorial/test_sql_databases/test_sql_databases_py39.py b/tests/test_tutorial/test_sql_databases/test_sql_databases_py39.py
new file mode 100644 (file)
index 0000000..c194c85
--- /dev/null
@@ -0,0 +1,383 @@
+import importlib
+import os
+from pathlib import Path
+
+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": {
+        "/users/": {
+            "get": {
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "title": "Response Read Users Users  Get",
+                                    "type": "array",
+                                    "items": {"$ref": "#/components/schemas/User"},
+                                }
+                            }
+                        },
+                    },
+                    "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": "Skip", "type": "integer", "default": 0},
+                        "name": "skip",
+                        "in": "query",
+                    },
+                    {
+                        "required": False,
+                        "schema": {"title": "Limit", "type": "integer", "default": 100},
+                        "name": "limit",
+                        "in": "query",
+                    },
+                ],
+            },
+            "post": {
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {
+                            "application/json": {
+                                "schema": {"$ref": "#/components/schemas/User"}
+                            }
+                        },
+                    },
+                    "422": {
+                        "description": "Validation Error",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/HTTPValidationError"
+                                }
+                            }
+                        },
+                    },
+                },
+                "summary": "Create User",
+                "operationId": "create_user_users__post",
+                "requestBody": {
+                    "content": {
+                        "application/json": {
+                            "schema": {"$ref": "#/components/schemas/UserCreate"}
+                        }
+                    },
+                    "required": True,
+                },
+            },
+        },
+        "/users/{user_id}": {
+            "get": {
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {
+                            "application/json": {
+                                "schema": {"$ref": "#/components/schemas/User"}
+                            }
+                        },
+                    },
+                    "422": {
+                        "description": "Validation Error",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/HTTPValidationError"
+                                }
+                            }
+                        },
+                    },
+                },
+                "summary": "Read User",
+                "operationId": "read_user_users__user_id__get",
+                "parameters": [
+                    {
+                        "required": True,
+                        "schema": {"title": "User Id", "type": "integer"},
+                        "name": "user_id",
+                        "in": "path",
+                    }
+                ],
+            }
+        },
+        "/users/{user_id}/items/": {
+            "post": {
+                "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": "Create Item For User",
+                "operationId": "create_item_for_user_users__user_id__items__post",
+                "parameters": [
+                    {
+                        "required": True,
+                        "schema": {"title": "User Id", "type": "integer"},
+                        "name": "user_id",
+                        "in": "path",
+                    }
+                ],
+                "requestBody": {
+                    "content": {
+                        "application/json": {
+                            "schema": {"$ref": "#/components/schemas/ItemCreate"}
+                        }
+                    },
+                    "required": True,
+                },
+            }
+        },
+        "/items/": {
+            "get": {
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "title": "Response Read Items Items  Get",
+                                    "type": "array",
+                                    "items": {"$ref": "#/components/schemas/Item"},
+                                }
+                            }
+                        },
+                    },
+                    "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": "Skip", "type": "integer", "default": 0},
+                        "name": "skip",
+                        "in": "query",
+                    },
+                    {
+                        "required": False,
+                        "schema": {"title": "Limit", "type": "integer", "default": 100},
+                        "name": "limit",
+                        "in": "query",
+                    },
+                ],
+            }
+        },
+    },
+    "components": {
+        "schemas": {
+            "ItemCreate": {
+                "title": "ItemCreate",
+                "required": ["title"],
+                "type": "object",
+                "properties": {
+                    "title": {"title": "Title", "type": "string"},
+                    "description": {"title": "Description", "type": "string"},
+                },
+            },
+            "Item": {
+                "title": "Item",
+                "required": ["title", "id", "owner_id"],
+                "type": "object",
+                "properties": {
+                    "title": {"title": "Title", "type": "string"},
+                    "description": {"title": "Description", "type": "string"},
+                    "id": {"title": "Id", "type": "integer"},
+                    "owner_id": {"title": "Owner Id", "type": "integer"},
+                },
+            },
+            "User": {
+                "title": "User",
+                "required": ["email", "id", "is_active"],
+                "type": "object",
+                "properties": {
+                    "email": {"title": "Email", "type": "string"},
+                    "id": {"title": "Id", "type": "integer"},
+                    "is_active": {"title": "Is Active", "type": "boolean"},
+                    "items": {
+                        "title": "Items",
+                        "type": "array",
+                        "items": {"$ref": "#/components/schemas/Item"},
+                        "default": [],
+                    },
+                },
+            },
+            "UserCreate": {
+                "title": "UserCreate",
+                "required": ["email", "password"],
+                "type": "object",
+                "properties": {
+                    "email": {"title": "Email", "type": "string"},
+                    "password": {"title": "Password", "type": "string"},
+                },
+            },
+            "ValidationError": {
+                "title": "ValidationError",
+                "required": ["loc", "msg", "type"],
+                "type": "object",
+                "properties": {
+                    "loc": {
+                        "title": "Location",
+                        "type": "array",
+                        "items": {"type": "string"},
+                    },
+                    "msg": {"title": "Message", "type": "string"},
+                    "type": {"title": "Error Type", "type": "string"},
+                },
+            },
+            "HTTPValidationError": {
+                "title": "HTTPValidationError",
+                "type": "object",
+                "properties": {
+                    "detail": {
+                        "title": "Detail",
+                        "type": "array",
+                        "items": {"$ref": "#/components/schemas/ValidationError"},
+                    }
+                },
+            },
+        }
+    },
+}
+
+
+@pytest.fixture(scope="module", name="client")
+def get_client(tmp_path_factory: pytest.TempPathFactory):
+    tmp_path = tmp_path_factory.mktemp("data")
+    cwd = os.getcwd()
+    os.chdir(tmp_path)
+    test_db = Path("./sql_app.db")
+    if test_db.is_file():  # pragma: nocover
+        test_db.unlink()
+    # Import while creating the client to create the DB after starting the test session
+    from docs_src.sql_databases.sql_app_py39 import main
+
+    # Ensure import side effects are re-executed
+    importlib.reload(main)
+    with TestClient(main.app) as c:
+        yield c
+    if test_db.is_file():  # pragma: nocover
+        test_db.unlink()
+    os.chdir(cwd)
+
+
+@needs_py39
+def test_openapi_schema(client):
+    response = client.get("/openapi.json")
+    assert response.status_code == 200, response.text
+    assert response.json() == openapi_schema
+
+
+@needs_py39
+def test_create_user(client):
+    test_user = {"email": "johndoe@example.com", "password": "secret"}
+    response = client.post("/users/", json=test_user)
+    assert response.status_code == 200, response.text
+    data = response.json()
+    assert test_user["email"] == data["email"]
+    assert "id" in data
+    response = client.post("/users/", json=test_user)
+    assert response.status_code == 400, response.text
+
+
+@needs_py39
+def test_get_user(client):
+    response = client.get("/users/1")
+    assert response.status_code == 200, response.text
+    data = response.json()
+    assert "email" in data
+    assert "id" in data
+
+
+@needs_py39
+def test_inexistent_user(client):
+    response = client.get("/users/999")
+    assert response.status_code == 404, response.text
+
+
+@needs_py39
+def test_get_users(client):
+    response = client.get("/users/")
+    assert response.status_code == 200, response.text
+    data = response.json()
+    assert "email" in data[0]
+    assert "id" in data[0]
+
+
+@needs_py39
+def test_create_item(client):
+    item = {"title": "Foo", "description": "Something that fights"}
+    response = client.post("/users/1/items/", json=item)
+    assert response.status_code == 200, response.text
+    item_data = response.json()
+    assert item["title"] == item_data["title"]
+    assert item["description"] == item_data["description"]
+    assert "id" in item_data
+    assert "owner_id" in item_data
+    response = client.get("/users/1")
+    assert response.status_code == 200, response.text
+    user_data = response.json()
+    item_to_check = [it for it in user_data["items"] if it["id"] == item_data["id"]][0]
+    assert item_to_check["title"] == item["title"]
+    assert item_to_check["description"] == item["description"]
+    response = client.get("/users/1")
+    assert response.status_code == 200, response.text
+    user_data = response.json()
+    item_to_check = [it for it in user_data["items"] if it["id"] == item_data["id"]][0]
+    assert item_to_check["title"] == item["title"]
+    assert item_to_check["description"] == item["description"]
+
+
+@needs_py39
+def test_read_items(client):
+    response = client.get("/items/")
+    assert response.status_code == 200, response.text
+    data = response.json()
+    assert data
+    first_item = data[0]
+    assert "title" in first_item
+    assert "description" in first_item
index 9acfd8908ff1f5c96db860861d2542a1153b465f..6f667dea03ce191393602b07ed5049d53619f66e 100644 (file)
@@ -1,8 +1,14 @@
 import importlib
+import os
 from pathlib import Path
 
+import pytest
 
-def test_testing_dbs():
+
+def test_testing_dbs(tmp_path_factory: pytest.TempPathFactory):
+    tmp_path = tmp_path_factory.mktemp("data")
+    cwd = os.getcwd()
+    os.chdir(tmp_path)
     test_db = Path("./test.db")
     if test_db.is_file():  # pragma: nocover
         test_db.unlink()
@@ -14,3 +20,4 @@ def test_testing_dbs():
     test_sql_app.test_create_user()
     if test_db.is_file():  # pragma: nocover
         test_db.unlink()
+    os.chdir(cwd)
diff --git a/tests/test_tutorial/test_sql_databases/test_testing_databases_py310.py b/tests/test_tutorial/test_sql_databases/test_testing_databases_py310.py
new file mode 100644 (file)
index 0000000..9e6b3f3
--- /dev/null
@@ -0,0 +1,26 @@
+import importlib
+import os
+from pathlib import Path
+
+import pytest
+
+from ...utils import needs_py310
+
+
+@needs_py310
+def test_testing_dbs_py39(tmp_path_factory: pytest.TempPathFactory):
+    tmp_path = tmp_path_factory.mktemp("data")
+    cwd = os.getcwd()
+    os.chdir(tmp_path)
+    test_db = Path("./test.db")
+    if test_db.is_file():  # pragma: nocover
+        test_db.unlink()
+    # Import while creating the client to create the DB after starting the test session
+    from docs_src.sql_databases.sql_app_py310.tests import test_sql_app
+
+    # Ensure import side effects are re-executed
+    importlib.reload(test_sql_app)
+    test_sql_app.test_create_user()
+    if test_db.is_file():  # pragma: nocover
+        test_db.unlink()
+    os.chdir(cwd)
diff --git a/tests/test_tutorial/test_sql_databases/test_testing_databases_py39.py b/tests/test_tutorial/test_sql_databases/test_testing_databases_py39.py
new file mode 100644 (file)
index 0000000..0b27adf
--- /dev/null
@@ -0,0 +1,26 @@
+import importlib
+import os
+from pathlib import Path
+
+import pytest
+
+from ...utils import needs_py39
+
+
+@needs_py39
+def test_testing_dbs_py39(tmp_path_factory: pytest.TempPathFactory):
+    tmp_path = tmp_path_factory.mktemp("data")
+    cwd = os.getcwd()
+    os.chdir(tmp_path)
+    test_db = Path("./test.db")
+    if test_db.is_file():  # pragma: nocover
+        test_db.unlink()
+    # Import while creating the client to create the DB after starting the test session
+    from docs_src.sql_databases.sql_app_py39.tests import test_sql_app
+
+    # Ensure import side effects are re-executed
+    importlib.reload(test_sql_app)
+    test_sql_app.test_create_user()
+    if test_db.is_file():  # pragma: nocover
+        test_db.unlink()
+    os.chdir(cwd)
index 820e4856d61476cf7f14e4b72eb7c257285ea29d..fc1a832f996209c5982a5a4b80bfd3241f4f878c 100644 (file)
@@ -1,10 +1,10 @@
-from docs_src.app_testing import test_main_b
+from docs_src.app_testing.app_b import test_main
 
 
 def test_app():
-    test_main_b.test_create_existing_item()
-    test_main_b.test_create_item()
-    test_main_b.test_create_item_bad_token()
-    test_main_b.test_read_inexistent_item()
-    test_main_b.test_read_item()
-    test_main_b.test_read_item_bad_token()
+    test_main.test_create_existing_item()
+    test_main.test_create_item()
+    test_main.test_create_item_bad_token()
+    test_main.test_read_inexistent_item()
+    test_main.test_read_item()
+    test_main.test_read_item_bad_token()
diff --git a/tests/test_tutorial/test_testing/test_main_b_py310.py b/tests/test_tutorial/test_testing/test_main_b_py310.py
new file mode 100644 (file)
index 0000000..a504ed2
--- /dev/null
@@ -0,0 +1,13 @@
+from ...utils import needs_py310
+
+
+@needs_py310
+def test_app():
+    from docs_src.app_testing.app_b_py310 import test_main
+
+    test_main.test_create_existing_item()
+    test_main.test_create_item()
+    test_main.test_create_item_bad_token()
+    test_main.test_read_inexistent_item()
+    test_main.test_read_item()
+    test_main.test_read_item_bad_token()
index 92d1c48e873e24d848ee98c1a5de70ed3d097d8c..777bfe81daff7d6c994f633e89276160cd2ce5fd 100644 (file)
@@ -4,3 +4,6 @@ import pytest
 
 needs_py37 = pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python3.7+")
 needs_py39 = pytest.mark.skipif(sys.version_info < (3, 9), reason="requires python3.9+")
+needs_py310 = pytest.mark.skipif(
+    sys.version_info < (3, 10), reason="requires python3.10+"
+)