Add support for Pydantic v1 and above 🎉 (#646)

* Make compatible with pydantic v1

* Remove unused import

* Remove unused ignores

* Update pydantic version

* Fix minor formatting issue

*  Revert removing iterate_in_threadpool

*  Add backwards compatibility with Pydantic 0.32.2

with deprecation warnings

*  Update tests to not break when using Pydantic < 1.0.0

* 📝 Update docs for Pydantic version 1.0.0

* 📌 Update Pydantic range version to support from 0.32.2

* 🎨 Format test imports

*  Add support for Pydantic < 1.2 for populate_validators

*  Add backwards compatibility for Pydantic < 1.2.0 with required fields

* 📌 Relax requirement for Pydantic to < 2.0.0 🎉 🚀

* 💚 Update pragma coverage for older Pydantic versions
This commit is contained in:
dmontagu
2019-11-27 11:32:02 -08:00
committed by Sebastián Ramírez
parent 90a5796b94
commit ab2b86fe2c
66 changed files with 802 additions and 425 deletions

View File

@@ -1,6 +1,6 @@
The same way you can declare additional validation and metadata in path operation function parameters with `Query`, `Path` and `Body`, you can declare validation and metadata inside of Pydantic models using `Schema`.
The same way you can declare additional validation and metadata in path operation function parameters with `Query`, `Path` and `Body`, you can declare validation and metadata inside of Pydantic models using Pydantic's `Field`.
## Import Schema
## Import `Field`
First, you have to import it:
@@ -9,32 +9,34 @@ First, you have to import it:
```
!!! warning
Notice that `Schema` is imported directly from `pydantic`, not from `fastapi` as are all the rest (`Query`, `Path`, `Body`, etc).
Notice that `Field` is imported directly from `pydantic`, not from `fastapi` as are all the rest (`Query`, `Path`, `Body`, etc).
## Declare model attributes
You can then use `Schema` with model attributes:
You can then use `Field` with model attributes:
```Python hl_lines="9 10"
{!./src/body_schema/tutorial001.py!}
```
`Schema` works the same way as `Query`, `Path` and `Body`, it has all the same parameters, etc.
`Field` works the same way as `Query`, `Path` and `Body`, it has all the same parameters, etc.
!!! note "Technical Details"
Actually, `Query`, `Path` and others you'll see next are subclasses of a common `Param` which is itself a subclass of Pydantic's `Schema`.
Actually, `Query`, `Path` and others you'll see next create objects of subclasses of a common `Param` class, which is itself a subclass of Pydantic's `FieldInfo` class.
`Body` is also a subclass of `Schema` directly. And there are others you will see later that are subclasses of `Body`.
And Pydantic's `Field` returns an instance of `FieldInfo` as well.
But remember that when you import `Query`, `Path` and others from `fastapi`, <a href="https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#recap" target="_blank">those are actually functions that return classes of the same name</a>.
`Body` also returns objects of a subclass of `FieldInfo` directly. And there are others you will see later that are subclasses of the `Body` class.
Remember that when you import `Query`, `Path`, and others from `fastapi`, <a href="https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#recap" target="_blank">those are actually functions that return classes of the same name</a>.
!!! tip
Notice how each model's attribute with a type, default value and `Schema` has the same structure as a path operation function's parameter, with `Schema` instead of `Path`, `Query` and `Body`.
Notice how each model's attribute with a type, default value and `Field` has the same structure as a path operation function's parameter, with `Field` instead of `Path`, `Query` and `Body`.
## Schema extras
In `Schema`, `Path`, `Query`, `Body` and others you'll see later, you can declare extra parameters apart from those described before.
In `Field`, `Path`, `Query`, `Body` and others you'll see later, you can declare extra parameters apart from those described before.
Those parameters will be added as-is to the output JSON Schema.
@@ -55,6 +57,6 @@ And it would look in the `/docs` like this:
## Recap
You can use Pydantic's `Schema` to declare extra validations and metadata for model attributes.
You can use Pydantic's `Field` to declare extra validations and metadata for model attributes.
You can also use the extra keyword arguments to pass additional JSON Schema metadata.
You can also use the extra keyword arguments to pass additional JSON Schema metadata.

View File

@@ -41,15 +41,15 @@ This means that you can send only the data that you want to update, leaving the
But this guide shows you, more or less, how they are intended to be used.
### Using Pydantic's `skip_defaults` parameter
### Using Pydantic's `exclude_unset` parameter
If you want to receive partial updates, it's very useful to use the parameter `skip_defaults` in Pydantic's model's `.dict()`.
If you want to receive partial updates, it's very useful to use the parameter `exclude_unset` in Pydantic's model's `.dict()`.
Like `item.dict(skip_defaults=True)`.
Like `item.dict(exclude_unset=True)`.
That would generate a `dict` with only the data that was set when creating the `item` model, excluding default values.
Then you can use this to generate a `dict` with only the data that was set, omitting default values:
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"
{!./src/body_updates/tutorial002.py!}
@@ -72,7 +72,7 @@ In summary, to apply partial updates you would:
* (Optionally) use `PATCH` instead of `PUT`.
* Retrieve the stored data.
* Put that data in a Pydantic model.
* Generate a `dict` without default values from the input model (using `skip_defaults`).
* Generate a `dict` without default values from the input model (using `exclude_unset`).
* This way you can update only the values actually set by the user, instead of overriding values already stored with default values in your model.
* Create a copy of the stored model, updating it's attributes with the received partial updates (using the `update` parameter).
* Convert the copied model to something that can be stored in your DB (for example, using the `jsonable_encoder`).

View File

@@ -15,7 +15,7 @@ This is especially the case for user models, because:
Here's a general idea of how the models could look like with their password fields and the places where they are used:
```Python hl_lines="8 10 15 21 23 32 34 39 40"
```Python hl_lines="7 9 14 20 22 27 28 31 32 33 38 39"
{!./src/extra_models/tutorial001.py!}
```
@@ -148,7 +148,7 @@ All the data conversion, validation, documentation, etc. will still work as norm
That way, we can declare just the differences between the models (with plaintext `password`, with `hashed_password` and without password):
```Python hl_lines="8 14 15 18 19 22 23"
```Python hl_lines="7 13 14 17 18 21 22"
{!./src/extra_models/tutorial002.py!}
```

View File

@@ -33,13 +33,13 @@ But most importantly:
Here we are declaring a `UserIn` model, it will contain a plaintext password:
```Python hl_lines="8 10"
```Python hl_lines="7 9"
{!./src/response_model/tutorial002.py!}
```
And we are using this model to declare our input and the same model to declare our output:
```Python hl_lines="16 17"
```Python hl_lines="15 16"
{!./src/response_model/tutorial002.py!}
```
@@ -56,19 +56,19 @@ But if we use the same model for another path operation, we could be sending our
We can instead create an input model with the plaintext password and an output model without it:
```Python hl_lines="8 10 15"
```Python hl_lines="7 9 14"
{!./src/response_model/tutorial003.py!}
```
Here, even though our path operation function is returning the same input user that contains the password:
```Python hl_lines="23"
```Python hl_lines="22"
{!./src/response_model/tutorial003.py!}
```
...we declared the `response_model` to be our model `UserOut`, that doesn't include the password:
```Python hl_lines="21"
```Python hl_lines="20"
{!./src/response_model/tutorial003.py!}
```
@@ -100,15 +100,15 @@ but you might want to omit them from the result if they were not actually stored
For example, if you have models with many optional attributes in a NoSQL database, but you don't want to send very long JSON responses full of default values.
### Use the `response_model_skip_defaults` parameter
### Use the `response_model_exclude_unset` parameter
You can set the *path operation decorator* parameter `response_model_skip_defaults=True`:
You can set the *path operation decorator* parameter `response_model_exclude_unset=True`:
```Python hl_lines="24"
{!./src/response_model/tutorial004.py!}
```
and those default values won't be included in the response.
and those default values won't be included in the response, only the values actually set.
So, if you send a request to that *path operation* for the item with ID `foo`, the response (not including default values) will be:
@@ -120,7 +120,7 @@ So, if you send a request to that *path operation* for the item with ID `foo`, t
```
!!! info
FastAPI uses Pydantic model's `.dict()` with <a href="https://pydantic-docs.helpmanual.io/#copying" target="_blank">its `skip_defaults` parameter</a> to achieve this.
FastAPI uses Pydantic model's `.dict()` with <a href="https://pydantic-docs.helpmanual.io/usage/exporting_models/#modeldict" target="_blank">its `exclude_unset` parameter</a> to achieve this.
#### Data with values for fields with defaults
@@ -194,4 +194,4 @@ If you forget to use a `set` and use a `list` or `tuple` instead, FastAPI will s
Use the path operation decorator's parameter `response_model` to define response models and especially to ensure private data is filtered out.
Use `response_model_skip_defaults` to return only the values explicitly set.
Use `response_model_exclude_unset` to return only the values explicitly set.