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:
features:
- search.suggest
- search.highlight
- - content.tabs.link
icon:
repo: fontawesome/brands/github-alt
logo: https://fastapi.tiangolo.com/img/icon-white.svg
features:
- search.suggest
- search.highlight
- - content.tabs.link
icon:
repo: fontawesome/brands/github-alt
logo: https://fastapi.tiangolo.com/img/icon-white.svg
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">
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.
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:
* 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!}
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
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>.
**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.
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).
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.
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.
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).
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
{
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.
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:
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.
### 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
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
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.
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:
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.
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:
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
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.
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.
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.
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
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
* 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.
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
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.
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`.
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
**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
**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:
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
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.
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*.
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.
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.
..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:
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.
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.
### 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.
**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:
* 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`.
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`.
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!}
+ ```
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()`
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`
!!! 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`
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
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
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.
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.
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:
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.
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:
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
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:
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.
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
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.
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`.
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.
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
= 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:
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:
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:
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:
#### 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!}
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
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
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:
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:
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
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:
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.
* `@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.
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.
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).
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`.
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.
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.
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
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.
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.
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
* `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
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
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`.
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`.
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.
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
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"`.
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
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*
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`
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:
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
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`
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.
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.
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
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`.
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:
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
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.
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.
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.
* `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`:
* `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
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.
### 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.
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`.
features:
- search.suggest
- search.highlight
- - content.tabs.link
icon:
repo: fontawesome/brands/github-alt
logo: img/icon-white.svg
features:
- search.suggest
- search.highlight
- - content.tabs.link
icon:
repo: fontawesome/brands/github-alt
logo: https://fastapi.tiangolo.com/img/icon-white.svg
features:
- search.suggest
- search.highlight
- - content.tabs.link
icon:
repo: fontawesome/brands/github-alt
logo: https://fastapi.tiangolo.com/img/icon-white.svg
features:
- search.suggest
- search.highlight
- - content.tabs.link
icon:
repo: fontawesome/brands/github-alt
logo: https://fastapi.tiangolo.com/img/icon-white.svg
features:
- search.suggest
- search.highlight
- - content.tabs.link
icon:
repo: fontawesome/brands/github-alt
logo: https://fastapi.tiangolo.com/img/icon-white.svg
features:
- search.suggest
- search.highlight
- - content.tabs.link
icon:
repo: fontawesome/brands/github-alt
logo: https://fastapi.tiangolo.com/img/icon-white.svg
features:
- search.suggest
- search.highlight
- - content.tabs.link
icon:
repo: fontawesome/brands/github-alt
logo: https://fastapi.tiangolo.com/img/icon-white.svg
features:
- search.suggest
- search.highlight
- - content.tabs.link
icon:
repo: fontawesome/brands/github-alt
logo: https://fastapi.tiangolo.com/img/icon-white.svg
features:
- search.suggest
- search.highlight
- - content.tabs.link
icon:
repo: fontawesome/brands/github-alt
logo: https://fastapi.tiangolo.com/img/icon-white.svg
features:
- search.suggest
- search.highlight
- - content.tabs.link
icon:
repo: fontawesome/brands/github-alt
logo: https://fastapi.tiangolo.com/img/icon-white.svg
features:
- search.suggest
- search.highlight
- - content.tabs.link
icon:
repo: fontawesome/brands/github-alt
logo: https://fastapi.tiangolo.com/img/icon-white.svg
features:
- search.suggest
- search.highlight
- - content.tabs.link
icon:
repo: fontawesome/brands/github-alt
logo: https://fastapi.tiangolo.com/img/icon-white.svg
features:
- search.suggest
- search.highlight
- - content.tabs.link
icon:
repo: fontawesome/brands/github-alt
logo: https://fastapi.tiangolo.com/img/icon-white.svg
features:
- search.suggest
- search.highlight
- - content.tabs.link
icon:
repo: fontawesome/brands/github-alt
logo: https://fastapi.tiangolo.com/img/icon-white.svg
from fastapi.testclient import TestClient
-from .main_b import app
+from .main import app
client = TestClient(app)
--- /dev/null
+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
--- /dev/null
+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"}
--- /dev/null
+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"}
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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()}
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+from fastapi import FastAPI
+
+app = FastAPI()
+
+
+@app.post("/index-weights/")
+async def create_index_weights(weights: dict[int, float]):
+ return weights
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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}
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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}
--- /dev/null
+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
--- /dev/null
+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,
+ }
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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]
--- /dev/null
+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
--- /dev/null
+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}
--- /dev/null
+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}
--- /dev/null
+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}
--- /dev/null
+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}
--- /dev/null
+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}
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)
--- /dev/null
+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
--- /dev/null
+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
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"])
--- /dev/null
+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"}]
--- /dev/null
+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"}]
description: Optional[str] = None
price: float
tax: Optional[float] = None
- tags: Set[str] = []
+ tags: Set[str] = set()
@app.post(
--- /dev/null
+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
--- /dev/null
+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
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")
--- /dev/null
+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
--- /dev/null
+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
description: Optional[str] = None
price: float
tax: Optional[float] = None
- tags: Set[str] = []
+ tags: Set[str] = set()
@app.post(
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+def process_items(items: list[str]):
+ for item in items:
+ print(item)
--- /dev/null
+def process_items(items_t: tuple[int, int, str], items_s: set[bytes]):
+ return items_t, items_s
-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)
--- /dev/null
+from typing import Union
+
+
+def process_item(item: Union[int, str]):
+ print(item)
--- /dev/null
+def process_item(item: int | str):
+ print(item)
--- /dev/null
+def say_hi(name: str | None = None):
+ if name is not None:
+ print(f"Hey {name}!")
+ else:
+ print("Hello World")
--- /dev/null
+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")
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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}
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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)
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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]
--- /dev/null
+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]
--- /dev/null
+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]
--- /dev/null
+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]
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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}]
--- /dev/null
+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"}
--- /dev/null
+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"}
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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()
--- /dev/null
+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
--- /dev/null
+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")
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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()
--- /dev/null
+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
--- /dev/null
+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")
--- /dev/null
+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
--- /dev/null
+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
"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",
]
--- /dev/null
+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()
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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",
+ }
+ ]
+ }
--- /dev/null
+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": [],
+ }
--- /dev/null
+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": [],
+ }
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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,
+ }
--- /dev/null
+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"},
+ ]
--- /dev/null
+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}
--- /dev/null
+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
--- /dev/null
+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": [],
+ }
--- /dev/null
+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": [],
+ }
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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}
--- /dev/null
+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}
--- /dev/null
+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"]}
--- /dev/null
+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
--- /dev/null
+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",
+ }
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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,
+ }
--- /dev/null
+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,
+ }
--- /dev/null
+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
--- /dev/null
+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"}
--- /dev/null
+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"
--- /dev/null
+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"
import importlib
+import os
from pathlib import Path
import pytest
@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()
yield c
if test_db.is_file(): # pragma: nocover
test_db.unlink()
+ os.chdir(cwd)
def test_openapi_schema(client):
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
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()
test_sql_app.test_create_user()
if test_db.is_file(): # pragma: nocover
test_db.unlink()
+ os.chdir(cwd)
--- /dev/null
+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)
--- /dev/null
+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)
-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()
--- /dev/null
+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()
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+"
+)