diff --git a/docs/img/tutorial/body-nested-models/image01.png b/docs/img/tutorial/body-nested-models/image01.png new file mode 100644 index 000000000..f3644ce79 Binary files /dev/null and b/docs/img/tutorial/body-nested-models/image01.png differ diff --git a/docs/tutorial/body-nested-models.md b/docs/tutorial/body-nested-models.md new file mode 100644 index 000000000..2d57dd78c --- /dev/null +++ b/docs/tutorial/body-nested-models.md @@ -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 Pydantic's exotic types. + +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: + + + +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 diff --git a/docs/tutorial/src/body-nested-models/tutorial001.py b/docs/tutorial/src/body-nested-models/tutorial001.py new file mode 100644 index 000000000..9e0fa4494 --- /dev/null +++ b/docs/tutorial/src/body-nested-models/tutorial001.py @@ -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 diff --git a/docs/tutorial/src/body-nested-models/tutorial002.py b/docs/tutorial/src/body-nested-models/tutorial002.py new file mode 100644 index 000000000..8f769279b --- /dev/null +++ b/docs/tutorial/src/body-nested-models/tutorial002.py @@ -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 diff --git a/docs/tutorial/src/body-nested-models/tutorial003.py b/docs/tutorial/src/body-nested-models/tutorial003.py new file mode 100644 index 000000000..bb539b127 --- /dev/null +++ b/docs/tutorial/src/body-nested-models/tutorial003.py @@ -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 diff --git a/docs/tutorial/src/body-nested-models/tutorial004.py b/docs/tutorial/src/body-nested-models/tutorial004.py new file mode 100644 index 000000000..257928ef3 --- /dev/null +++ b/docs/tutorial/src/body-nested-models/tutorial004.py @@ -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 diff --git a/docs/tutorial/src/body-nested-models/tutorial005.py b/docs/tutorial/src/body-nested-models/tutorial005.py new file mode 100644 index 000000000..f5f19b390 --- /dev/null +++ b/docs/tutorial/src/body-nested-models/tutorial005.py @@ -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 diff --git a/docs/tutorial/src/body-nested-models/tutorial006.py b/docs/tutorial/src/body-nested-models/tutorial006.py new file mode 100644 index 000000000..09d8be768 --- /dev/null +++ b/docs/tutorial/src/body-nested-models/tutorial006.py @@ -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 diff --git a/docs/tutorial/src/body-nested-models/tutorial007.py b/docs/tutorial/src/body-nested-models/tutorial007.py new file mode 100644 index 000000000..cda802d3e --- /dev/null +++ b/docs/tutorial/src/body-nested-models/tutorial007.py @@ -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 diff --git a/docs/tutorial/src/body-nested-models/tutorial008.py b/docs/tutorial/src/body-nested-models/tutorial008.py new file mode 100644 index 000000000..34b868563 --- /dev/null +++ b/docs/tutorial/src/body-nested-models/tutorial008.py @@ -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 diff --git a/mkdocs.yml b/mkdocs.yml index 30f803ef2..84107b828 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -26,6 +26,7 @@ nav: - Path Parameters - Numeric Validations: 'tutorial/path-params-numeric-validations.md' - Body - Multiple Parameters: 'tutorial/body-multiple-params.md' - Body - Schema: 'tutorial/body-schema.md' + - Body - Nested Models: 'tutorial/body-nested-models.md' - Concurrency and async / await: 'async.md' - Deployment: 'deployment.md'