Esquemas OpenAPI Separados para Entrada e Saída ou Não¶
Ao usar Pydantic v2, o OpenAPI gerado é um pouco mais exato e correto do que antes. 😎
Inclusive, em alguns casos, ele terá até dois JSON Schemas no OpenAPI para o mesmo modelo Pydantic, para entrada e saída, dependendo se eles possuem valores padrão.
Vamos ver como isso funciona e como alterar se for necessário.
Modelos Pydantic para Entrada e Saída¶
Digamos que você tenha um modelo Pydantic com valores padrão, como este:
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str | None = None
# Code below omitted 👇
👀 Full file preview
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str | None = None
app = FastAPI()
@app.post("/items/")
def create_item(item: Item):
return item
@app.get("/items/")
def read_items() -> list[Item]:
return [
Item(
name="Portal Gun",
description="Device to travel through the multi-rick-verse",
),
Item(name="Plumbus"),
]
🤓 Other versions and variants
from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: Optional[str] = None
app = FastAPI()
@app.post("/items/")
def create_item(item: Item):
return item
@app.get("/items/")
def read_items() -> list[Item]:
return [
Item(
name="Portal Gun",
description="Device to travel through the multi-rick-verse",
),
Item(name="Plumbus"),
]
from typing import List, Union
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: Union[str, None] = None
app = FastAPI()
@app.post("/items/")
def create_item(item: Item):
return item
@app.get("/items/")
def read_items() -> List[Item]:
return [
Item(
name="Portal Gun",
description="Device to travel through the multi-rick-verse",
),
Item(name="Plumbus"),
]
Modelo para Entrada¶
Se você usar esse modelo como entrada, como aqui:
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str | None = None
app = FastAPI()
@app.post("/items/")
def create_item(item: Item):
return item
# Code below omitted 👇
👀 Full file preview
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str | None = None
app = FastAPI()
@app.post("/items/")
def create_item(item: Item):
return item
@app.get("/items/")
def read_items() -> list[Item]:
return [
Item(
name="Portal Gun",
description="Device to travel through the multi-rick-verse",
),
Item(name="Plumbus"),
]
🤓 Other versions and variants
from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: Optional[str] = None
app = FastAPI()
@app.post("/items/")
def create_item(item: Item):
return item
@app.get("/items/")
def read_items() -> list[Item]:
return [
Item(
name="Portal Gun",
description="Device to travel through the multi-rick-verse",
),
Item(name="Plumbus"),
]
from typing import List, Union
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: Union[str, None] = None
app = FastAPI()
@app.post("/items/")
def create_item(item: Item):
return item
@app.get("/items/")
def read_items() -> List[Item]:
return [
Item(
name="Portal Gun",
description="Device to travel through the multi-rick-verse",
),
Item(name="Plumbus"),
]
... então o campo description
não será obrigatório. Porque ele tem um valor padrão de None
.
Modelo de Entrada na Documentação¶
Você pode confirmar que na documentação, o campo description
não tem um asterisco vermelho, não é marcado como obrigatório:
Modelo para Saída¶
Mas se você usar o mesmo modelo como saída, como aqui:
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str | None = None
app = FastAPI()
@app.post("/items/")
def create_item(item: Item):
return item
@app.get("/items/")
def read_items() -> list[Item]:
return [
Item(
name="Portal Gun",
description="Device to travel through the multi-rick-verse",
),
Item(name="Plumbus"),
]
🤓 Other versions and variants
from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: Optional[str] = None
app = FastAPI()
@app.post("/items/")
def create_item(item: Item):
return item
@app.get("/items/")
def read_items() -> list[Item]:
return [
Item(
name="Portal Gun",
description="Device to travel through the multi-rick-verse",
),
Item(name="Plumbus"),
]
from typing import List, Union
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: Union[str, None] = None
app = FastAPI()
@app.post("/items/")
def create_item(item: Item):
return item
@app.get("/items/")
def read_items() -> List[Item]:
return [
Item(
name="Portal Gun",
description="Device to travel through the multi-rick-verse",
),
Item(name="Plumbus"),
]
... então, como description
tem um valor padrão, se você não retornar nada para esse campo, ele ainda terá o valor padrão.
Modelo para Dados de Resposta de Saída¶
Se você interagir com a documentação e verificar a resposta, mesmo que o código não tenha adicionado nada em um dos campos description
, a resposta JSON contém o valor padrão (null
):
Isso significa que ele sempre terá um valor, só que às vezes o valor pode ser None
(ou null
em termos de JSON).
Isso quer dizer que, os clientes que usam sua API não precisam verificar se o valor existe ou não, eles podem assumir que o campo sempre estará lá, mas que em alguns casos terá o valor padrão de None
.
A maneira de descrever isso no OpenAPI é marcar esse campo como obrigatório, porque ele sempre estará lá.
Por causa disso, o JSON Schema para um modelo pode ser diferente dependendo se ele é usado para entrada ou saída:
- para entrada, o
description
não será obrigatório - para saída, ele será obrigatório (e possivelmente
None
, ou em termos de JSON,null
)
Modelo para Saída na Documentação¶
Você pode verificar o modelo de saída na documentação também, ambos name
e description
são marcados como obrigatórios com um asterisco vermelho:
Modelo para Entrada e Saída na Documentação¶
E se você verificar todos os Schemas disponíveis (JSON Schemas) no OpenAPI, verá que há dois, um Item-Input
e um Item-Output
.
Para Item-Input
, description
não é obrigatório, não tem um asterisco vermelho.
Mas para Item-Output
, description
é obrigatório, tem um asterisco vermelho.
Com esse recurso do Pydantic v2, sua documentação da API fica mais precisa, e se você tiver clientes e SDKs gerados automaticamente, eles serão mais precisos também, proporcionando uma melhor experiência para desenvolvedores e consistência. 🎉
Não Separe Schemas¶
Agora, há alguns casos em que você pode querer ter o mesmo esquema para entrada e saída.
Provavelmente, o principal caso de uso para isso é se você já tem algum código de cliente/SDK gerado automaticamente e não quer atualizar todo o código de cliente/SDK gerado ainda, você provavelmente vai querer fazer isso em algum momento, mas talvez não agora.
Nesse caso, você pode desativar esse recurso no FastAPI, com o parâmetro separate_input_output_schemas=False
.
Informação
O suporte para separate_input_output_schemas
foi adicionado no FastAPI 0.102.0
. 🤓
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str | None = None
app = FastAPI(separate_input_output_schemas=False)
@app.post("/items/")
def create_item(item: Item):
return item
@app.get("/items/")
def read_items() -> list[Item]:
return [
Item(
name="Portal Gun",
description="Device to travel through the multi-rick-verse",
),
Item(name="Plumbus"),
]
🤓 Other versions and variants
from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: Optional[str] = None
app = FastAPI(separate_input_output_schemas=False)
@app.post("/items/")
def create_item(item: Item):
return item
@app.get("/items/")
def read_items() -> list[Item]:
return [
Item(
name="Portal Gun",
description="Device to travel through the multi-rick-verse",
),
Item(name="Plumbus"),
]
from typing import List, Union
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: Union[str, None] = None
app = FastAPI(separate_input_output_schemas=False)
@app.post("/items/")
def create_item(item: Item):
return item
@app.get("/items/")
def read_items() -> List[Item]:
return [
Item(
name="Portal Gun",
description="Device to travel through the multi-rick-verse",
),
Item(name="Plumbus"),
]
Mesmo Esquema para Modelos de Entrada e Saída na Documentação¶
E agora haverá um único esquema para entrada e saída para o modelo, apenas Item
, e description
não será obrigatório:
Esse é o mesmo comportamento do Pydantic v1. 🤓