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 str
s:
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 Item
s, que por sua vez possui opcionalmente uma lista Image
s
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 dict
s 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