Ir para o conte√ļdo

Corpo - Modelos aninhados

Com o FastAPI, você pode definir, validar, documentar e usar modelos profundamente aninhados de forma arbitrária (graças ao Pydantic).

Campos do tipo Lista

Você pode definir um atributo como um subtipo. Por exemplo, uma list do Python:

from typing import Union

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    tax: Union[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

Isso far√° com que tags seja uma lista de itens mesmo sem declarar o tipo dos elementos desta lista.

Campos do tipo Lista com um par√Ęmetro de tipo

Mas o Python tem uma maneira espec√≠fica de declarar listas com tipos internos ou "par√Ęmetros de tipo":

Importe List do typing

Primeiramente, importe List do módulo typing que já vem por padrão no Python:

from typing import List, Union

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    tax: Union[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

Declare a List com um par√Ęmetro de tipo

Para declarar tipos que t√™m par√Ęmetros de tipo(tipos internos), como list, dict, tuple:

  • Importe os do modulo typing
  • Passe o(s) tipo(s) interno(s) como "par√Ęmetros de tipo" usando colchetes: [ e ]
from typing import List

my_list: List[str]

Essa √© a sintaxe padr√£o do Python para declara√ß√Ķes de tipo.

Use a mesma sintaxe padr√£o para atributos de modelo com tipos internos.

Portanto, em nosso exemplo, podemos fazer com que tags sejam especificamente uma "lista de strings":

from typing import List, Union

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    tax: Union[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

Tipo "set"

Mas ent√£o, quando n√≥s pensamos mais, percebemos que as tags n√£o devem se repetir, elas provavelmente devem ser strings √ļnicas.

E que o Python tem um tipo de dados especial para conjuntos de itens √ļnicos, o set.

Ent√£o podemos importar Set e declarar tags como um set de strs:

from typing import Set, Union

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    tax: Union[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

Com isso, mesmo que você receba uma requisição contendo dados duplicados, ela será convertida em um conjunto de itens exclusivos.

E sempre que você enviar esses dados como resposta, mesmo se a fonte tiver duplicatas, eles serão gerados como um conjunto de itens exclusivos.

E tamb√©m teremos anota√ß√Ķes/documenta√ß√£o em conformidade.

Modelos aninhados

Cada atributo de um modelo Pydantic tem um tipo.

Mas esse tipo pode ser outro modelo Pydantic.

Portanto, voc√™ pode declarar "objects" JSON profundamente aninhados com nomes, tipos e valida√ß√Ķes de atributos espec√≠ficos.

Tudo isso, aninhado arbitrariamente.

Defina um sub-modelo

Por exemplo, nós podemos definir um modelo Image:

from typing import Set, Union

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Image(BaseModel):
    url: str
    name: str


class Item(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    tax: Union[float, None] = None
    tags: Set[str] = set()
    image: Union[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

Use o sub-modelo como um tipo

E ent√£o podemos usa-lo como o tipo de um atributo:

from typing import Set, Union

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Image(BaseModel):
    url: str
    name: str


class Item(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    tax: Union[float, None] = None
    tags: Set[str] = set()
    image: Union[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

Isso significa que o FastAPI vai esperar um corpo similar à:

{
    "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"
    }
}

Novamente, apenas fazendo essa declaração, com o FastAPI, você ganha:

  • Suporte do editor de texto (comple√ß√£o, etc), inclusive para modelos aninhados
  • Convers√£o de dados
  • Valida√ß√£o de dados
  • Documenta√ß√£o automatica

Tipos especiais e validação

Além dos tipos singulares normais como str, int, float, etc. Você também pode usar tipos singulares mais complexos que herdam de str.

Para ver todas as op√ß√Ķes poss√≠veis, cheque a documenta√ß√£o para ostipos exoticos do Pydantic. Voc√™ ver√° alguns exemplos no pr√≥ximo capitulo.

Por exemplo, no modelo Image nós temos um campo url, nós podemos declara-lo como um HttpUrl do Pydantic invés de como uma str:

from typing import Set, Union

from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl

app = FastAPI()


class Image(BaseModel):
    url: HttpUrl
    name: str


class Item(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    tax: Union[float, None] = None
    tags: Set[str] = set()
    image: Union[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

A string ser√° verificada para se tornar uma URL v√°lida e documentada no esquema JSON/1OpenAPI como tal.

Atributos como listas de submodelos

Você também pode usar modelos Pydantic como subtipos de list, set, etc:

from typing import List, Set, Union

from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl

app = FastAPI()


class Image(BaseModel):
    url: HttpUrl
    name: str


class Item(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    tax: Union[float, None] = None
    tags: Set[str] = set()
    images: Union[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

Isso vai esperar(converter, validar, documentar, etc) um corpo JSON tal qual:

{
    "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"
        }
    ]
}

Informação

Note como o campo images agora tem uma lista de objetos de image.

Modelos profundamente aninhados

Você pode definir modelos profundamente aninhados de forma arbitrária:

from typing import List, Set, Union

from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl

app = FastAPI()


class Image(BaseModel):
    url: HttpUrl
    name: str


class Item(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    tax: Union[float, None] = None
    tags: Set[str] = set()
    images: Union[List[Image], None] = None


class Offer(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    items: List[Item]


@app.post("/offers/")
async def create_offer(offer: Offer):
    return offer

Informação

Note como Offer tem uma lista de Items, que por sua vez possui opcionalmente uma lista Images

Corpos de listas puras

Se o valor de primeiro n√≠vel do corpo JSON que voc√™ espera for um array do JSON (umalista do Python), voc√™ pode declarar o tipo no par√Ęmetro da fun√ß√£o, da mesma forma que nos modelos do Pydantic:

images: List[Image]

como em:

from typing import List

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

Suporte de editor em todo canto

E você obtém suporte do editor em todos os lugares.

Mesmo para itens dentro de listas:

Você não conseguiria este tipo de suporte de editor se estivesse trabalhando diretamente com dict em vez de modelos Pydantic.

Mas você também não precisa se preocupar com eles, os dicts de entrada são convertidos automaticamente e sua saída é convertida automaticamente para JSON também.

Corpos de dicts arbitr√°rios

Você também pode declarar um corpo como um dict com chaves de algum tipo e valores de outro tipo.

Sem ter que saber de antem√£o quais s√£o os nomes de campos/atributos v√°lidos (como seria o caso dos modelos Pydantic).

Isso seria √ļtil se voc√™ deseja receber chaves que ainda n√£o conhece.


Outro caso √ļtil √© quando voc√™ deseja ter chaves de outro tipo, por exemplo, int.

√Č isso que vamos ver aqui.

Neste caso, você aceitaria qualquer dict, desde que tenha chavesint com valores float:

from typing import Dict

from fastapi import FastAPI

app = FastAPI()


@app.post("/index-weights/")
async def create_index_weights(weights: Dict[int, float]):
    return weights

Dica

Leve em condideração que o JSON só suporta str como chaves.

Mas o Pydantic tem convers√£o autom√°tica de dados.

Isso significa que, embora os clientes da API só possam enviar strings como chaves, desde que essas strings contenham inteiros puros, o Pydantic irá convertê-los e validá-los.

E o dict que você recebe como weights terá, na verdade, chaves int e valoresfloat.

Recapitulação

Com FastAPI você tem a flexibilidade máxima fornecida pelos modelos Pydantic, enquanto seu código é mantido simples, curto e elegante.

Mas com todos os benefícios:

  • Suporte do editor (comple√ß√£o em todo canto!)
  • Convers√£o de dados (leia-se parsing/serializa√ß√£o)
  • Valida√ß√£o de dados
  • Documenta√ß√£o dos esquemas
  • Documenta√ß√£o autom√°tica