Capítulo 18: Documentação Automática OpenAPI/Swagger, Redoc e Healthchecks
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["Cliente HTTP / Frontend"] --> B["API Gateway / Router"]
B --> C["Controller / Handler"]
C --> D["Service Layer (Regras de Negócio)"]
D --> E["Repository / ORM (Persistência)"]
E --> F["Banco de Dados / Cache"]
subgraph ARQ["Arquitetura do Capítulo"]
G["Conceito: Documentação Automática OpenAPI/Swagger, Redoc e Healthchecks"]
H["Segurança, Validação e Resiliência"]
I["Alta Performance e Escalabilidade"]
end
D --> ARQ
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
🏛️ 1. OpenAPI não é escrito — é inferido do código
O FastAPI não exige (nem aceita, de forma direta) que você escreva um arquivo openapi.yaml manualmente. Em vez disso, a cada inicialização da aplicação, o framework percorre o router inteiro — cada @app.get/post/put/delete, cada parâmetro de rota, cada modelo Pydantic usado como corpo de requisição ou response_model, cada dependência declarada via Depends() — e monta dinamicamente um dicionário Python que é a especificação OpenAPI 3.1 completa, exposta em /openapi.json. O Swagger UI (/docs) e o ReDoc (/redoc) são apenas visualizadores estáticos (JavaScript) que consomem esse JSON; eles não têm nenhum conhecimento do seu código, só do schema gerado.
Isso tem uma implicação prática importante: a documentação nunca fica dessincronizada do comportamento real, porque ela é derivada da mesma fonte de verdade que valida as requisições — os Type Hints e os modelos Pydantic. Se você renomear um campo em um BaseModel, o Swagger UI reflete isso no próximo restart, sem nenhuma ação manual.
Metadados globais e agrupamento por tags
from fastapi import FastAPI
tags_metadata = [
{"name": "Pedidos", "description": "Criação, consulta e cancelamento de pedidos."},
{"name": "Faturamento", "description": "Emissão de notas fiscais e cobrança."},
]
app = FastAPI(
title="Plataforma de Pedidos API",
description="API corporativa para gestão do ciclo de vida de pedidos.",
version="2.1.0",
openapi_tags=tags_metadata,
)
tags=["Pedidos"] no decorator de cada rota associa aquele endpoint ao grupo correspondente no Swagger UI, permitindo que APIs com centenas de rotas sejam navegáveis por domínio de negócio em vez de uma lista plana.
Documentando os múltiplos formatos de resposta com responses=
Por padrão, o FastAPI só documenta automaticamente o status 200/201 (a partir de response_model) e o 422 (erro de validação do Pydantic, sempre implícito quando há corpo de requisição). Qualquer outro código de saída — 404 para recurso inexistente, 409 para conflito de negócio — precisa ser declarado explicitamente com responses=, ou ele simplesmente não aparece no contrato publicado, mesmo que o código de fato o retorne:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()
class ErroNegocio(BaseModel):
detail: str
class Pedido(BaseModel):
id: int
total: float
@app.get(
"/pedidos/{pedido_id}",
response_model=Pedido,
responses={404: {"model": ErroNegocio, "description": "Pedido não encontrado"}},
tags=["Pedidos"],
)
async def obter_pedido(pedido_id: int) -> Pedido:
if pedido_id != 1:
raise HTTPException(status_code=404, detail="Pedido não encontrado")
return Pedido(id=1, total=199.90)
Sem esse responses={404: ...}, um cliente que gera um SDK a partir do openapi.json (via openapi-generator ou orval) nunca saberia, só olhando o contrato, que aquele endpoint pode retornar 404 — descobriria isso em produção, na marra.
Exemplos ricos de payload no corpo da requisição
Field(examples=[...]) do Pydantic v2 injeta valores de exemplo diretamente no schema OpenAPI, e o Swagger UI os usa para pré-preencher o formulário de teste “Try it out”:
from pydantic import BaseModel, Field
class PedidoRequest(BaseModel):
item: str = Field(..., examples=["Mouse Ergonômico"])
quantidade: int = Field(..., ge=1, examples=[2])
valor_unitario: float = Field(..., gt=0, examples=[199.90])
Para múltiplos cenários de exemplo no mesmo campo (ex.: um payload “válido” e um “com desconto aplicado”), usa-se openapi_examples diretamente no parâmetro Body() da rota, que aceita um dicionário nomeado de exemplos — recurso que examples sozinho no Field não cobre.
Ocultando a documentação em produção
Expor /docs, /redoc e /openapi.json publicamente revela a superfície de ataque inteira da API (todas as rotas, parâmetros e schemas) para qualquer scanner automatizado. Em ambientes com requisitos de segurança mais estritos, desativa-se tudo isso na instância:
import os
app = FastAPI(
docs_url=None if os.getenv("ENV") == "production" else "/docs",
redoc_url=None if os.getenv("ENV") == "production" else "/redoc",
openapi_url=None if os.getenv("ENV") == "production" else "/openapi.json",
)
Healthchecks: liveness raso vs. readiness profundo
Um healthcheck bem projetado distingue dois níveis. Um /health raso apenas confirma que o processo Python está vivo e respondendo — útil para o Kubernetes decidir se deve reiniciar o pod (liveness probe). Um /health/deep verifica conectividade real com as dependências externas (banco, cache) antes de declarar a instância pronta para receber tráfego (readiness probe):
from fastapi import APIRouter, status
from fastapi.responses import JSONResponse
router = APIRouter()
async def checar_banco() -> bool:
... # SELECT 1 com timeout curto
async def checar_redis() -> bool:
... # PING com timeout curto
@router.get("/health/deep", tags=["Infra"])
async def health_deep():
db_ok, redis_ok = await checar_banco(), await checar_redis()
saudavel = db_ok and redis_ok
corpo = {"status": "HEALTHY" if saudavel else "UNHEALTHY",
"checks": {"database": db_ok, "redis": redis_ok}}
return JSONResponse(corpo, status_code=status.HTTP_200_OK if saudavel else 503)
Misturar os dois níveis em um único endpoint é um erro comum: se /health verificasse o banco e o banco ficasse temporariamente lento, o orquestrador reiniciaria o pod desnecessariamente (o processo em si estava saudável, só uma dependência estava degradada) — um loop de crash-restart que piora a disponibilidade em vez de melhorá-la.
Exportando o schema para geração de SDKs
app.openapi() retorna o dicionário completo do schema em tempo de execução (sem precisar de um servidor rodando, se instanciado isoladamente), permitindo exportar openapi.json como artefato de build em um pipeline de CI/CD, consumido depois por geradores de cliente TypeScript/Java:
import json
def exportar_schema(app) -> str:
return json.dumps(app.openapi(), indent=2)
🔗 Recursos Pedagógicos do Capítulo 18
| Recurso Didático | Finalidade | Link de Acesso |
|---|---|---|
| 📊 Slides de Aula | Apresentação visual interativa com Dark Mode e suporte a teclado | Ver Slides |
| 🧠 Quiz Formativo | Teste interativo de fixação com feedback imediato por alternativa | Fazer Quiz |
| 💻 Exemplos de Código | Demonstrações funcionais com código executável | Ver Exemplos |
| 🧩 Exercícios em 4 Níveis | Lista progressiva de fixação com gabarito em bloco colapsável | Resolver Exercícios |
| ⬅️ Capítulo Anterior | 📚 Sumário de Tópicos | Próximo Capítulo ➡️ |