Pular para o conteúdo

    Capítulo 9, Básico

    Combinando caminho, consulta e corpo

    Em uma API real, a mesma rota recebe um identificador, uma opção e um conjunto de dados. O FastAPI separa os três pela forma como você declara cada parâmetro.

    Como o FastAPI decide de onde vem cada parâmetro

    A regra é simples e vale para a rota inteira:

    Se o parâmetro...Ele vem de...
    Aparece no caminho ({id})O caminho
    É um tipo simples (int, str, bool) fora do caminhoA consulta (?x=1)
    É um modelo do PydanticO corpo (JSON)
    basico/cap09_combinando_parametros.pylinhas 10 a 36
    from fastapi import FastAPI, HTTPException
    from fastapi.testclient import TestClient
    from pydantic import BaseModel
    
    app = FastAPI()
    
    
    class UsuarioEntrada(BaseModel):
        nome: str
        idade: int
    
    
    usuarios: dict[int, UsuarioEntrada] = {1001: UsuarioEntrada(nome="Mohit", idade=23)}
    
    
    @app.put("/usuarios/{usuario_id}")
    def atualizar(usuario_id: int, usuario: UsuarioEntrada, notificar: bool = False):
        if usuario_id not in usuarios:
            raise HTTPException(status_code=404, detail="Usuário não encontrado")
        usuarios[usuario_id] = usuario
        return {"mensagem": "Usuário atualizado", "notificar": notificar, "dados": usuario}
    
    
    cliente = TestClient(app)
    resposta = cliente.put("/usuarios/1001?notificar=true", json={"nome": "Mohit", "idade": 24})
    print(resposta.status_code, resposta.json())
    print(cliente.put("/usuarios/9", json={"nome": "X", "idade": 1}).json())
    
    Saída
    200 {'mensagem': 'Usuário atualizado', 'notificar': True, 'dados': {'nome': 'Mohit', 'idade': 24}}
    {'detail': 'Usuário não encontrado'}
    

    Os três chegaram ao mesmo tempo: usuario_id do caminho, notificar da consulta e usuario do corpo. Dá para conferir como o FastAPI classificou cada um no documento OpenAPI:

    basico/cap09_combinando_parametros.pylinhas 38 a 40
    operacao = app.openapi()["paths"]["/usuarios/{usuario_id}"]["put"]
    print([(p["name"], p["in"]) for p in operacao["parameters"]])
    print("requestBody" in operacao)
    
    Saída
    [('usuario_id', 'path'), ('notificar', 'query')]
    True
    

    Cada parte tem um papel: o caminho identifica qual recurso, a consulta ajusta como a operação acontece, e o corpo carrega o quê será gravado.

    Vários modelos no corpo

    Se a rota declara dois modelos, o FastAPI espera um JSON com uma chave para cada um, em vez de um corpo "solto":

    basico/cap09_combinando_parametros.pylinhas 45 a 59
    class Item(BaseModel):
        nome: str
    
    
    class Comprador(BaseModel):
        email: str
    
    
    @app.post("/pedidos")
    def criar_pedido(item: Item, comprador: Comprador):
        return {"item": item.nome, "comprador": comprador.email}
    
    
    corpo = {"item": {"nome": "Caneta"}, "comprador": {"email": "a@b.com"}}
    print(cliente.post("/pedidos", json=corpo).json())
    
    Saída
    {'item': 'Caneta', 'comprador': 'a@b.com'}
    

    Um valor simples dentro do corpo

    Um tipo simples vai para a consulta, a não ser que você o marque com Body. Isso é útil para acrescentar um campo avulso ao corpo, ao lado de um modelo:

    basico/cap09_combinando_parametros.pylinhas 64 a 74
    from typing import Annotated
    
    from fastapi import Body
    
    
    @app.post("/avaliacoes")
    def avaliar(item: Item, nota: Annotated[int, Body(ge=1, le=5)]):
        return {"item": item.nome, "nota": nota}
    
    
    print(cliente.post("/avaliacoes", json={"item": {"nome": "Caneta"}, "nota": 5}).json())
    
    Saída
    {'item': 'Caneta', 'nota': 5}
    

    Um corpo com uma única chave

    Com um modelo só, o corpo é o próprio modelo. Se você quiser que ele venha embrulhado em uma chave (para manter o formato quando o corpo cresce depois), use embed=True:

    basico/cap09_combinando_parametros.pylinhas 79 a 84
    @app.post("/itens")
    def criar_item(item: Annotated[Item, Body(embed=True)]):
        return item
    
    
    print(cliente.post("/itens", json={"item": {"nome": "Régua"}}).json())
    
    Saída
    {'nome': 'Régua'}
    

    Quem identifica, quem filtra, quem carrega

    Antes de decidir onde um dado vai, eu me pergunto qual é o papel dele. Se identifica o recurso, é caminho. Se ajusta uma operação ou filtra uma lista, é consulta. Se é o conteúdo que será criado ou alterado, é corpo.

    Exercício 1

    Atualizar um produto com desconto

    Crie PUT /produtos/{produto_id} com corpo ProdutoEntrada (nome, preco) e o parâmetro de consulta aplicar_desconto (padrão False). Se for verdadeiro, grave o preço com 10% de desconto. Devolva 404 para um id inexistente.