11 changed files with 402 additions and 0 deletions
After Width: | Height: | Size: 40 KiB |
@ -0,0 +1,213 @@ |
|||||
|
With **FastAPI**, you can define, validate, document, and use arbitrarily deeply nested models (thanks to Pydantic). |
||||
|
|
||||
|
## List fields |
||||
|
|
||||
|
You can define an attribute to be a subtype. For example, a Python `list`: |
||||
|
|
||||
|
```Python hl_lines="12" |
||||
|
{!./tutorial/src/body-nested-models/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
This will make `tags` be a list of items. Although it doesn't declare the type of each of the items. |
||||
|
|
||||
|
## List fields with subtype |
||||
|
|
||||
|
But Python has a specific way to declare lists with subtypes: |
||||
|
|
||||
|
### Import typing's `List` |
||||
|
|
||||
|
First, import `List` from standard Python's `typing` module: |
||||
|
|
||||
|
```Python hl_lines="1" |
||||
|
{!./tutorial/src/body-nested-models/tutorial002.py!} |
||||
|
``` |
||||
|
|
||||
|
### Declare a `List` with a subtype |
||||
|
|
||||
|
To declare types that have subtypes, like `list`, `dict`, `tuple`: |
||||
|
|
||||
|
* Import them from the `typing` module |
||||
|
* Pass the subtype(s) as "type arguments" using square brackets: `[` and `]` |
||||
|
|
||||
|
```Python |
||||
|
from typing import List |
||||
|
|
||||
|
my_list: List[str] |
||||
|
``` |
||||
|
|
||||
|
That's all standard Python syntax for type declarations. |
||||
|
|
||||
|
Use that same standard syntax for model attributes with subtypes. |
||||
|
|
||||
|
So, in our example, we can make `tags` be specifically a "list of strings": |
||||
|
|
||||
|
```Python hl_lines="14" |
||||
|
{!./tutorial/src/body-nested-models/tutorial002.py!} |
||||
|
``` |
||||
|
|
||||
|
## Set types |
||||
|
|
||||
|
But then we think about it, and realize that tags shouldn't repeat, they would probably be unique strings. |
||||
|
|
||||
|
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`: |
||||
|
|
||||
|
```Python hl_lines="1 14" |
||||
|
{!./tutorial/src/body-nested-models/tutorial003.py!} |
||||
|
``` |
||||
|
|
||||
|
With this, even if you receive a request with duplicate data, it will be converted to a set of unique items. |
||||
|
|
||||
|
And whenever you output that data, even if the source had duplicates, it will be output as a set of unique items. |
||||
|
|
||||
|
And it will be annotated / documented accordingly too. |
||||
|
|
||||
|
## Nested Models |
||||
|
|
||||
|
Each attribute of a Pydantic model has a type. |
||||
|
|
||||
|
But that type can itself be another Pydantic model. |
||||
|
|
||||
|
So, you can declare deeply nested JSON `object`s with specific attribute names, types and validations. |
||||
|
|
||||
|
All that, arbitrarily nested. |
||||
|
|
||||
|
### Define a submodel |
||||
|
|
||||
|
For example, we can define an `Image` model: |
||||
|
|
||||
|
```Python hl_lines="9 10 11" |
||||
|
{!./tutorial/src/body-nested-models/tutorial004.py!} |
||||
|
``` |
||||
|
|
||||
|
### Use the submodel as a type |
||||
|
|
||||
|
And then we can use it as the type of an attribute: |
||||
|
|
||||
|
```Python hl_lines="20" |
||||
|
{!./tutorial/src/body-nested-models/tutorial004.py!} |
||||
|
``` |
||||
|
|
||||
|
This would mean that **FastAPI** would expect a body similar to: |
||||
|
|
||||
|
```JSON |
||||
|
{ |
||||
|
"name": "Foo", |
||||
|
"description": "The pretender", |
||||
|
"price": 42.0, |
||||
|
"tax": 3.2, |
||||
|
"tags": ["rock", "metal", "bar"], |
||||
|
"image": { |
||||
|
"url": "http://example.com/baz.jpg", |
||||
|
"name": "The Foo live" |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
Again, doing just that declaration, with **FastAPI** you get: |
||||
|
|
||||
|
* Editor support (completion, etc), even for nested models |
||||
|
* Data conversion |
||||
|
* Data validation |
||||
|
* Automatic documentation |
||||
|
|
||||
|
## Special types and validation |
||||
|
|
||||
|
Apart from normal singular types like `str`, `int`, `float`, etc. You can use more complex singular types that inherit from `str`. |
||||
|
|
||||
|
To see all the options you have, checkout the docs for <a href="https://pydantic-docs.helpmanual.io/#exotic-types" target="_blank">Pydantic's exotic types</a>. |
||||
|
|
||||
|
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 `UrlStr`: |
||||
|
|
||||
|
```Python hl_lines="5 11" |
||||
|
{!./tutorial/src/body-nested-models/tutorial005.py!} |
||||
|
``` |
||||
|
|
||||
|
The string will be checked to be a valid URL, and documented in JSON Schema / OpenAPI as such. |
||||
|
|
||||
|
## Attributes with lists of submodels |
||||
|
|
||||
|
You can also use Pydantic models as subtypes of `list`, `set`, etc: |
||||
|
|
||||
|
```Python hl_lines="21" |
||||
|
{!./tutorial/src/body-nested-models/tutorial006.py!} |
||||
|
``` |
||||
|
|
||||
|
This will expect (convert, validate, document, etc) a JSON body like: |
||||
|
|
||||
|
```JSON hl_lines="11" |
||||
|
{ |
||||
|
"name": "Foo", |
||||
|
"description": "The pretender", |
||||
|
"price": 42.0, |
||||
|
"tax": 3.2, |
||||
|
"tags": [ |
||||
|
"rock", |
||||
|
"metal", |
||||
|
"bar" |
||||
|
], |
||||
|
"images": [ |
||||
|
{ |
||||
|
"url": "http://example.com/baz.jpg", |
||||
|
"name": "The Foo live" |
||||
|
}, |
||||
|
{ |
||||
|
"url": "http://example.com/dave.jpg", |
||||
|
"name": "The Baz" |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
!!! info |
||||
|
Notice how the `images` key now has a list of image objects. |
||||
|
|
||||
|
## Deeply nested models |
||||
|
|
||||
|
You can define arbitrarily deeply nested models: |
||||
|
|
||||
|
```Python hl_lines="10 15 21 24 28" |
||||
|
{!./tutorial/src/body-nested-models/tutorial007.py!} |
||||
|
``` |
||||
|
|
||||
|
!!! info |
||||
|
Notice how `Offer` as a list of `Item`s, which in turn have an optional list of `Image`s |
||||
|
|
||||
|
## Bodies of pure lists |
||||
|
|
||||
|
If the top level value of the JSON body you expect is a JSON `array` (a Python `list`), you can declare the type in the parameter of the function, the same as in Pydantic models: |
||||
|
|
||||
|
```Python |
||||
|
images: List[Image] |
||||
|
``` |
||||
|
|
||||
|
as in: |
||||
|
|
||||
|
```Python hl_lines="16" |
||||
|
{!./tutorial/src/body-nested-models/tutorial008.py!} |
||||
|
``` |
||||
|
|
||||
|
## Editor support everywhere |
||||
|
|
||||
|
And you get editor support everywhere. |
||||
|
|
||||
|
Even for items inside of lists: |
||||
|
|
||||
|
<img src="/img/tutorial/body-nested-models/image01.png"> |
||||
|
|
||||
|
You couldn't get this kind of editor support if you where working directly with `dict` instead of Pydantic models. |
||||
|
|
||||
|
But you don't have to worry about them either, incoming dicts are converted automatically and your output is converted automatically to JSON too. |
||||
|
|
||||
|
## Recap |
||||
|
|
||||
|
With **FastAPI** you have the maximum flexibility provided by Pydantic models, while keeping your code simple, short and elegant. |
||||
|
|
||||
|
But with all the benefits: |
||||
|
|
||||
|
* Editor support (completion everywhere!) |
||||
|
* Data conversion (a.k.a. parsing / serialization) |
||||
|
* Data validation |
||||
|
* Schema documentation |
||||
|
* Automatic docs |
@ -0,0 +1,18 @@ |
|||||
|
from fastapi import FastAPI |
||||
|
from pydantic import BaseModel |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
class Item(BaseModel): |
||||
|
name: str |
||||
|
description: str = None |
||||
|
price: float |
||||
|
tax: float = 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 |
@ -0,0 +1,20 @@ |
|||||
|
from typing import List |
||||
|
|
||||
|
from fastapi import FastAPI |
||||
|
from pydantic import BaseModel |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
class Item(BaseModel): |
||||
|
name: str |
||||
|
description: str = None |
||||
|
price: float |
||||
|
tax: 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 |
@ -0,0 +1,20 @@ |
|||||
|
from typing import Set |
||||
|
|
||||
|
from fastapi import FastAPI |
||||
|
from pydantic import BaseModel |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
class Item(BaseModel): |
||||
|
name: str |
||||
|
description: str = None |
||||
|
price: float |
||||
|
tax: 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 |
@ -0,0 +1,26 @@ |
|||||
|
from typing import Set |
||||
|
|
||||
|
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 |
||||
|
price: float |
||||
|
tax: float = None |
||||
|
tags: Set[str] = [] |
||||
|
image: 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 |
@ -0,0 +1,27 @@ |
|||||
|
from typing import Set |
||||
|
|
||||
|
from fastapi import FastAPI |
||||
|
from pydantic import BaseModel |
||||
|
from pydantic.types import UrlStr |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
class Image(BaseModel): |
||||
|
url: UrlStr |
||||
|
name: str |
||||
|
|
||||
|
|
||||
|
class Item(BaseModel): |
||||
|
name: str |
||||
|
description: str = None |
||||
|
price: float |
||||
|
tax: float = None |
||||
|
tags: Set[str] = [] |
||||
|
image: 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 |
@ -0,0 +1,27 @@ |
|||||
|
from typing import List, Set |
||||
|
|
||||
|
from fastapi import FastAPI |
||||
|
from pydantic import BaseModel |
||||
|
from pydantic.types import UrlStr |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
class Image(BaseModel): |
||||
|
url: UrlStr |
||||
|
name: str |
||||
|
|
||||
|
|
||||
|
class Item(BaseModel): |
||||
|
name: str |
||||
|
description: str = None |
||||
|
price: float |
||||
|
tax: float = None |
||||
|
tags: Set[str] = [] |
||||
|
image: 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 |
@ -0,0 +1,33 @@ |
|||||
|
from typing import List, Set |
||||
|
|
||||
|
from fastapi import FastAPI |
||||
|
from pydantic import BaseModel |
||||
|
from pydantic.types import UrlStr |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
class Image(BaseModel): |
||||
|
url: UrlStr |
||||
|
name: str |
||||
|
|
||||
|
|
||||
|
class Item(BaseModel): |
||||
|
name: str |
||||
|
description: str = None |
||||
|
price: float |
||||
|
tax: float = None |
||||
|
tags: Set[str] = [] |
||||
|
image: List[Image] = None |
||||
|
|
||||
|
|
||||
|
class Offer(BaseModel): |
||||
|
name: str |
||||
|
description: str = None |
||||
|
price: float |
||||
|
items: List[Item] |
||||
|
|
||||
|
|
||||
|
@app.post("/offers/") |
||||
|
async def create_offer(*, offer: Offer): |
||||
|
return offer |
@ -0,0 +1,17 @@ |
|||||
|
from typing import List |
||||
|
|
||||
|
from fastapi import FastAPI |
||||
|
from pydantic import BaseModel |
||||
|
from pydantic.types import UrlStr |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
class Image(BaseModel): |
||||
|
url: UrlStr |
||||
|
name: str |
||||
|
|
||||
|
|
||||
|
@app.post("/images/multiple/") |
||||
|
async def create_multiple_images(*, images: List[Image]): |
||||
|
return images |
Loading…
Reference in new issue