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 ➡️