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.
fromfastapiimportFastAPIfrompydanticimportBaseModelclassItem(BaseModel):name:strdescription:str|None=Noneapp=FastAPI()@app.post("/items/")defcreate_item(item:Item):returnitem@app.get("/items/")defread_items()->list[Item]:return[Item(name="Portal Gun",description="Device to travel through the multi-rick-verse",),Item(name="Plumbus"),]
🤓 Other versions and variants
fromtypingimportOptionalfromfastapiimportFastAPIfrompydanticimportBaseModelclassItem(BaseModel):name:strdescription:Optional[str]=Noneapp=FastAPI()@app.post("/items/")defcreate_item(item:Item):returnitem@app.get("/items/")defread_items()->list[Item]:return[Item(name="Portal Gun",description="Device to travel through the multi-rick-verse",),Item(name="Plumbus"),]
fromtypingimportList,UnionfromfastapiimportFastAPIfrompydanticimportBaseModelclassItem(BaseModel):name:strdescription:Union[str,None]=Noneapp=FastAPI()@app.post("/items/")defcreate_item(item:Item):returnitem@app.get("/items/")defread_items()->List[Item]:return[Item(name="Portal Gun",description="Device to travel through the multi-rick-verse",),Item(name="Plumbus"),]
fromfastapiimportFastAPIfrompydanticimportBaseModelclassItem(BaseModel):name:strdescription:str|None=Noneapp=FastAPI()@app.post("/items/")defcreate_item(item:Item):returnitem@app.get("/items/")defread_items()->list[Item]:return[Item(name="Portal Gun",description="Device to travel through the multi-rick-verse",),Item(name="Plumbus"),]
🤓 Other versions and variants
fromtypingimportOptionalfromfastapiimportFastAPIfrompydanticimportBaseModelclassItem(BaseModel):name:strdescription:Optional[str]=Noneapp=FastAPI()@app.post("/items/")defcreate_item(item:Item):returnitem@app.get("/items/")defread_items()->list[Item]:return[Item(name="Portal Gun",description="Device to travel through the multi-rick-verse",),Item(name="Plumbus"),]
fromtypingimportList,UnionfromfastapiimportFastAPIfrompydanticimportBaseModelclassItem(BaseModel):name:strdescription:Union[str,None]=Noneapp=FastAPI()@app.post("/items/")defcreate_item(item:Item):returnitem@app.get("/items/")defread_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.
Mas se você usar o mesmo modelo como saída, como aqui:
fromfastapiimportFastAPIfrompydanticimportBaseModelclassItem(BaseModel):name:strdescription:str|None=Noneapp=FastAPI()@app.post("/items/")defcreate_item(item:Item):returnitem@app.get("/items/")defread_items()->list[Item]:return[Item(name="Portal Gun",description="Device to travel through the multi-rick-verse",),Item(name="Plumbus"),]
🤓 Other versions and variants
fromtypingimportOptionalfromfastapiimportFastAPIfrompydanticimportBaseModelclassItem(BaseModel):name:strdescription:Optional[str]=Noneapp=FastAPI()@app.post("/items/")defcreate_item(item:Item):returnitem@app.get("/items/")defread_items()->list[Item]:return[Item(name="Portal Gun",description="Device to travel through the multi-rick-verse",),Item(name="Plumbus"),]
fromtypingimportList,UnionfromfastapiimportFastAPIfrompydanticimportBaseModelclassItem(BaseModel):name:strdescription:Union[str,None]=Noneapp=FastAPI()@app.post("/items/")defcreate_item(item:Item):returnitem@app.get("/items/")defread_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.
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 descriptionnão será obrigatório
para saída, ele será obrigatório (e possivelmente None, ou em termos de JSON, null)
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, descriptionnã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. 🎉
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. 🤓
fromfastapiimportFastAPIfrompydanticimportBaseModelclassItem(BaseModel):name:strdescription:str|None=Noneapp=FastAPI(separate_input_output_schemas=False)@app.post("/items/")defcreate_item(item:Item):returnitem@app.get("/items/")defread_items()->list[Item]:return[Item(name="Portal Gun",description="Device to travel through the multi-rick-verse",),Item(name="Plumbus"),]
🤓 Other versions and variants
fromtypingimportOptionalfromfastapiimportFastAPIfrompydanticimportBaseModelclassItem(BaseModel):name:strdescription:Optional[str]=Noneapp=FastAPI(separate_input_output_schemas=False)@app.post("/items/")defcreate_item(item:Item):returnitem@app.get("/items/")defread_items()->list[Item]:return[Item(name="Portal Gun",description="Device to travel through the multi-rick-verse",),Item(name="Plumbus"),]
fromtypingimportList,UnionfromfastapiimportFastAPIfrompydanticimportBaseModelclassItem(BaseModel):name:strdescription:Union[str,None]=Noneapp=FastAPI(separate_input_output_schemas=False)@app.post("/items/")defcreate_item(item:Item):returnitem@app.get("/items/")defread_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 descriptionnão será obrigatório: