Pular para o conteúdo

    Capítulo 6, Básico

    Corpo da requisição e POST com Pydantic

    O corpo é o dado que o cliente envia para criar ou alterar algo. Com um modelo do Pydantic, o FastAPI lê o JSON, valida cada campo e entrega à sua função um objeto pronto.

    O corpo da requisição

    Em um cadastro, o formulário manda nome, e-mail e senha. Esses dados vão no corpo da requisição, em JSON. O verbo para criar um recurso é o POST, e, ao contrário do GET, ele muda algo no servidor. Para descrever o corpo, você declara uma classe que herda de BaseModel:

    basico/cap06_corpo_pydantic.pylinhas 10 a 29
    from fastapi import FastAPI
    from fastapi.testclient import TestClient
    from pydantic import BaseModel
    
    app = FastAPI()
    
    
    class Produto(BaseModel):
        nome: str
        preco: float
    
    
    @app.post("/produtos")
    def criar_produto(produto: Produto):
        return {"mensagem": "Produto criado", "dados": produto}
    
    
    cliente = TestClient(app)
    resposta = cliente.post("/produtos", json={"nome": "Caneta", "preco": 3.5})
    print(resposta.status_code, resposta.json())
    
    Saída
    200 {'mensagem': 'Produto criado', 'dados': {'nome': 'Caneta', 'preco': 3.5}}
    

    O parâmetro produto: Produto diz ao FastAPI: "o corpo da requisição é um JSON com esta forma". Dentro da função, produto é um objeto, e você acessa produto.nome e produto.preco.

    O que acontece quando o dado está errado

    O Pydantic confere cada campo e responde com um 422 que aponta o problema. Os casos mais comuns são campo faltando, tipo errado e JSON malformado:

    basico/cap06_corpo_pydantic.pylinhas 34 a 41
    faltando = cliente.post("/produtos", json={"nome": "Caneta"})
    print(faltando.status_code, faltando.json()["detail"][0]["loc"], faltando.json()["detail"][0]["type"])
    
    tipo_errado = cliente.post("/produtos", json={"nome": "Caneta", "preco": "abc"})
    print(tipo_errado.json()["detail"][0]["msg"])
    
    quebrado = cliente.post("/produtos", content="{nome:", headers={"content-type": "application/json"})
    print(quebrado.status_code, quebrado.json()["detail"][0]["type"])
    
    Saída
    422 ['body', 'preco'] missing
    Input should be a valid number, unable to parse string as a number
    422 json_invalid
    

    O loc começa com body, que mostra que o problema está no corpo, seguido do nome do campo.

    O Pydantic converte, quando é seguro

    Por padrão, o Pydantic aceita valores que podem ser convertidos sem perda. Um preço enviado como o texto "19.9" vira o decimal 19.9. Isso é conveniente para formulários, mas, quando você não quer a conversão, existem tipos estritos:

    basico/cap06_corpo_pydantic.pylinhas 46 a 47
    resposta = cliente.post("/produtos", json={"nome": "Lápis", "preco": "19.9"})
    print(resposta.json()["dados"])
    
    Saída
    {'nome': 'Lápis', 'preco': 19.9}
    

    Um erro de entendimento muito comum

    Você pode escrever uma rota POST com parâmetros simples, como def criar(nome: str, idade: int). Funciona, mas esses parâmetros não vão no corpo: como são tipos simples, o FastAPI os trata como parâmetros de consulta. Só um modelo do Pydantic (ou o Body) vai no corpo:

    basico/cap06_corpo_pydantic.pylinhas 52 a 59
    @app.post("/pessoas")
    def criar_pessoa(nome: str, idade: int):
        return {"nome": nome, "idade": idade}
    
    
    print(cliente.post("/pessoas", params={"nome": "Ana", "idade": 30}).status_code)
    resposta = cliente.post("/pessoas", json={"nome": "Ana", "idade": 30})
    print(resposta.status_code, resposta.json()["detail"][0]["loc"])
    
    Saída
    200
    422 ['query', 'nome']
    

    O JSON no corpo foi ignorado, e o FastAPI reclamou que faltavam nome e idade na consulta. Dados de criação devem sempre ir em um modelo.

    Dicionário ou modelo?

    Receber um dict aceita qualquer coisa, sem estrutura nem validação, e você escreve cada checagem à mão. O modelo declara a estrutura uma vez, e ganha validação, conversão e documentação:

    dictModelo Pydantic
    CamposLivresDeclarados
    ValidaçãoManualAutomática
    DocumentaçãoGenéricaDescreve cada campo
    Autocompletar no editorNãoSim

    Usando o modelo como objeto

    O modelo é uma classe comum, com métodos úteis para converter de e para dicionário e JSON:

    basico/cap06_corpo_pydantic.pylinhas 64 a 68
    produto = Produto(nome="Caderno", preco=12)
    print(produto)
    print(produto.model_dump())
    print(produto.model_dump_json())
    print(Produto.model_validate({"nome": "Régua", "preco": 4.5}))
    
    Saída
    nome='Caderno' preco=12.0
    {'nome': 'Caderno', 'preco': 12.0}
    {"nome":"Caderno","preco":12.0}
    nome='Régua' preco=4.5
    

    O preco=12 (inteiro) virou o decimal 12.0, porque o campo é float.

    Exercício 1

    Cadastrar um usuário

    Crie POST /usuarios que receba um modelo Usuario com nome (texto) e idade (inteiro) e devolva {"mensagem": "Usuário criado", "nome": ..., "idade": ...}. Confira um caso válido e um com idade em texto.