Saltar para o conteúdo

← Blog

Pydantic + SQLAlchemy: A Stack Real

O FastAPI leva o crédito. Pydantic v2 e SQLAlchemy 2.0 fazem o trabalho pesado.

FastAPI gets the credit. Pydantic v2 and SQLAlchemy 2.0 do the heavy lifting.

O FastAPI leva o crédito. Pydantic v2 e SQLAlchemy 2.0 fazem o trabalho pesado.

O framework é um wrapper ASGI fino à volta dessas duas bibliotecas. Se você entende elas, transita entre frameworks sem perder a stack.

Pydantic v2 — a camada de contrato

1. `BaseModel` com config strict: `extra="forbid", strict=True, str_strip_whitespace=True`. Campos desconhecidos e tipos errados são rejeitados à entrada, não no meio de um handler.
2. Tipos reutilizáveis com `Annotated[str, Field(min_length=1, max_length=255)]` declarados uma vez e partilhados em `BookCreate`, `BookReplace`, `BookPatch`. O formato do wire deixa de ser ad hoc.
3. Schemas separados para input e output. `BookCreate` rejeita campos desconhecidos. `BookOut` expõe `id`, `created_at`, `updated_at`, `status` — campos que o cliente nunca deve enviar na entrada.
4. `ConfigDict(from_attributes=True)` no `BookOut`. O repository devolve um `Book` ORM; o router faz `BookOut.model_validate(book)` e a serialização é uma linha.
5. O OpenAPI é um side-effect grátis. Cada schema vira um componente em `openapi.json`. O Swagger UI em `/docs` está sempre sincronizado com o código.

SQLAlchemy 2.0 async — a camada de dados

1. Mapeamento declarativo tipado: `Mapped[int] = mapped_column(Integer, primary_key=True)`. Auto-complete no IDE, mypy-friendly, sem referências de coluna por string.
2. `AsyncSession` + driver asyncpg. `select(Book).order_by(Book.id)` composto uma vez, executado com `await session.scalars(...)`. A API `query()` do 1.x foi-se — bem-vinda a partida.
3. O gotcha que morde primeiro: devolver um objecto ORM do contexto da sessão **desliga-o**. Tentar aceder a relações lazy-loaded depois explode. Para colunas escalares funciona; para o resto, eager-load com `selectinload` ou converte para um modelo Pydantic dentro do escopo da sessão.
4. Um engine, descartado no `lifespan`. O pool fica quente entre requests; no shutdown, `await engine.dispose()` fecha as conexões limpamente. Sem sockets vazados.
5. Migrações Alembic funcionam exactamente como no sync. O engine async só importa em runtime; a geração offline da migração não muda.

Quando async ajuda mesmo:
endpoints com múltiplas chamadas I/O em paralelo (DB + API externa + cache), alta concorrência num só processo, streaming e WebSocket.

Quando async é teatro:
CRUD interno de baixa concorrência onde o piso de latência é dominado por um round-trip; trabalho CPU-bound que bloqueia o event loop; projecto hobby pequeno onde o custo ergonómico não se paga.

Escolhe primeiro as bibliotecas. O framework segue.

Qual é o gotcha que mais te mordeu com SQLAlchemy async em produção?

P.S. Código: https://github.com/bilouro/FastAPIProject

P.S. Novo post tech toda a quarta-feira.

#FastAPI #SQLAlchemy #PydanticV2