Skip to content

Commit

Permalink
✨ Add support for Pydantic v1 and above 🎉 (#646)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
dmontagu authored and tiangolo committed Nov 27, 2019
1 parent 90a5796 commit ab2b86f
Show file tree
Hide file tree
Showing 66 changed files with 802 additions and 425 deletions.
2 changes: 1 addition & 1 deletion Pipfile
Expand Up @@ -26,7 +26,7 @@ uvicorn = "*"

[packages]
starlette = "==0.12.9"
pydantic = "==0.32.2"
pydantic = "==1.0.0"
databases = {extras = ["sqlite"],version = "*"}
hypercorn = "*"
orjson = "*"
Expand Down
6 changes: 3 additions & 3 deletions docs/src/body_schema/tutorial001.py
@@ -1,13 +1,13 @@
from fastapi import Body, FastAPI
from pydantic import BaseModel, Schema
from pydantic import BaseModel, Field

app = FastAPI()


class Item(BaseModel):
name: str
description: str = Schema(None, title="The description of the item", max_length=300)
price: float = Schema(..., gt=0, description="The price must be greater than zero")
description: str = 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


Expand Down
2 changes: 1 addition & 1 deletion docs/src/body_updates/tutorial002.py
Expand Up @@ -31,7 +31,7 @@ async def read_item(item_id: str):
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(skip_defaults=True)
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
3 changes: 1 addition & 2 deletions docs/src/extra_models/tutorial001.py
@@ -1,6 +1,5 @@
from fastapi import FastAPI
from pydantic import BaseModel
from pydantic.types import EmailStr
from pydantic import BaseModel, EmailStr

app = FastAPI()

Expand Down
3 changes: 1 addition & 2 deletions docs/src/extra_models/tutorial002.py
@@ -1,6 +1,5 @@
from fastapi import FastAPI
from pydantic import BaseModel
from pydantic.types import EmailStr
from pydantic import BaseModel, EmailStr

app = FastAPI()

Expand Down
3 changes: 1 addition & 2 deletions docs/src/response_model/tutorial002.py
@@ -1,6 +1,5 @@
from fastapi import FastAPI
from pydantic import BaseModel
from pydantic.types import EmailStr
from pydantic import BaseModel, EmailStr

app = FastAPI()

Expand Down
3 changes: 1 addition & 2 deletions docs/src/response_model/tutorial003.py
@@ -1,6 +1,5 @@
from fastapi import FastAPI
from pydantic import BaseModel
from pydantic.types import EmailStr
from pydantic import BaseModel, EmailStr

app = FastAPI()

Expand Down
2 changes: 1 addition & 1 deletion docs/src/response_model/tutorial004.py
Expand Up @@ -21,6 +21,6 @@ class Item(BaseModel):
}


@app.get("/items/{item_id}", response_model=Item, response_model_skip_defaults=True)
@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
async def read_item(item_id: str):
return items[item_id]
26 changes: 14 additions & 12 deletions docs/tutorial/body-schema.md
@@ -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:

Expand All @@ -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.

Expand All @@ -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.
10 changes: 5 additions & 5 deletions docs/tutorial/body-updates.md
Expand Up @@ -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!}
Expand All @@ -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`).
Expand Down
4 changes: 2 additions & 2 deletions docs/tutorial/extra-models.md
Expand Up @@ -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!}
```

Expand Down Expand Up @@ -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!}
```

Expand Down
20 changes: 10 additions & 10 deletions docs/tutorial/response-model.md
Expand Up @@ -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!}
```

Expand All @@ -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!}
```

Expand Down Expand Up @@ -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:

Expand All @@ -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

Expand Down Expand Up @@ -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.

0 comments on commit ab2b86f

Please sign in to comment.