Capítulo 03: Parâmetros de Rota (Path), Consulta (Query), Headers e Body
Especialização em Backend com Python & FastAPI • FastAPI & Python 3.12+ • Pydantic v2, SQLModel, Async/Await e Microsserviços
🗺️ Mapa Conceitual do Tópico
flowchart TD
A["Requisição HTTP"] --> B{"Onde está o dado?"}
B -->|"na URL fixa"| C["Path (obrigatório)"]
B -->|"filtro opcional na URL"| D["Query"]
B -->|"metadado de protocolo"| E["Header / Cookie"]
B -->|"payload estruturado"| F["Body (Pydantic Model)"]
B -->|"arquivo binário"| G["UploadFile (multipart)"]
C & D & E & F & G --> H["FastAPI infere a origem\npela assinatura da função"]
style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style B fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style C fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style D fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style E fill:#fce4ec,stroke:#e91e63,stroke-width:2px
style F fill:#f3e5f5,stroke:#9c27b0,stroke-width:2px
style G fill:#fffde7,stroke:#fbc02d,stroke-width:2px
🏛️ 1. Como o FastAPI decide de onde vem cada parâmetro
FastAPI não exige decoradores separados para cada tipo de entrada: ele inspeciona a assinatura da função e as anotações de tipo para decidir a origem de cada parâmetro. Um int ou str que aparece na string da rota ({item_id}) é tratado como path parameter automaticamente; um parâmetro com o mesmo nome que não aparece na rota vira query parameter por padrão; um BaseModel do Pydantic é interpretado como body. Essa inferência implícita é conveniente, mas para exercer controle explícito (validações extras, valores padrão, descrição no OpenAPI) usam-se os marcadores Path, Query, Header, Cookie e Body.
from typing import Optional
from fastapi import FastAPI, Path, Query
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(
item_id: int = Path(..., ge=1, description="ID do item, sempre positivo"),
q: Optional[str] = Query(None, min_length=3, max_length=50),
):
return {"item_id": item_id, "filtro": q}
Body com múltiplos modelos no mesmo payload
Quando mais de um parâmetro é anotado como BaseModel (explícita ou implicitamente), o FastAPI aninha automaticamente cada um sob uma chave com seu próprio nome no JSON esperado — não é preciso criar um modelo “wrapper” manualmente.
from fastapi import FastAPI, Body, status
from pydantic import BaseModel
class ClienteRef(BaseModel):
cliente_id: int
nome: str
class ItemPedido(BaseModel):
produto_id: int
quantidade: int
preco_unitario: float
app = FastAPI()
@app.post("/pedidos", status_code=status.HTTP_201_CREATED)
async def criar_pedido(cliente: ClienteRef = Body(...), itens: list[ItemPedido] = Body(...)):
# Body esperado: {"cliente": {...}, "itens": [{...}, ...]}
total = sum(item.quantidade * item.preco_unitario for item in itens)
return {"cliente": cliente.nome, "total": total, "qtd_itens": len(itens)}
Headers e cookies: nomes com hífen viram snake_case
HTTP headers tradicionalmente usam kebab-case (X-Tenant-ID), o que não é um identificador Python válido. O FastAPI faz a conversão automática: um parâmetro x_tenant_id é mapeado para o header X-Tenant-ID na requisição. Cookie(...) segue a mesma lógica de declaração de Query/Header, incluindo suporte a valor padrão e validação de formato.
from typing import Optional
from fastapi import FastAPI, Header, Cookie, HTTPException
app = FastAPI()
@app.get("/tenant-info")
async def obter_tenant(
x_tenant_id: str = Header(..., description="ID da organização"),
session_id: Optional[str] = Cookie(None),
):
if not x_tenant_id.startswith("corp_"):
raise HTTPException(status_code=400, detail="Tenant ID inválido")
return {"tenant": x_tenant_id, "session_active": session_id is not None}
Upload de arquivos com UploadFile
UploadFile (diferente de bytes puro) faz streaming do conteúdo em disco/memória sem carregar o arquivo inteiro de uma vez, o que é essencial para uploads grandes. Ele expõe .filename, .content_type e um objeto assíncrono .read(). Validar content_type e tamanho manualmente é necessário porque o cliente pode mentir sobre o Content-Type — a validação real de conteúdo (magic bytes) fica a cargo de bibliotecas especializadas em cenários de maior risco.
from fastapi import FastAPI, UploadFile, File, HTTPException
app = FastAPI()
MAX_FILE_SIZE = 5 * 1024 * 1024 # 5 MB
@app.post("/upload-documento")
async def upload_documento(arquivo: UploadFile = File(...)):
if arquivo.content_type not in {"application/pdf", "image/png"}:
raise HTTPException(status_code=415, detail="Tipo de mídia não suportado")
conteudo = await arquivo.read()
if len(conteudo) > MAX_FILE_SIZE:
raise HTTPException(status_code=413, detail="Arquivo excede o limite de 5MB")
return {"filename": arquivo.filename, "tamanho_bytes": len(conteudo)}