Capítulo 06: Sessões Assíncronas (AsyncSession) e Operações CRUD
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: Sessões Assíncronas (AsyncSession) e Operações CRUD"]
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. Por que AsyncSession em vez de Session
Uma API FastAPI síncrona que usa psycopg2 bloqueia a thread do worker inteira enquanto espera a resposta do banco — nenhuma outra requisição é processada por aquele worker durante esse tempo. Com um driver assíncrono (asyncpg para PostgreSQL, aiosqlite para SQLite) e AsyncSession, o await na chamada de I/O devolve o controle à event loop do asyncio, que aproveita esse intervalo para atender outras requisições concorrentes na mesma thread. O ganho não é velocidade por requisição individual — é vazão (throughput) sob carga concorrente de I/O.
from sqlmodel import SQLModel, Field
from sqlmodel.ext.asyncio.session import AsyncSession
from sqlalchemy.ext.asyncio import create_async_engine, AsyncEngine
from typing import Optional
class Produto(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
nome: str
preco: float
engine: AsyncEngine = create_async_engine("sqlite+aiosqlite:///./app.db")
async def criar_tabelas() -> None:
async with engine.begin() as conn:
await conn.run_sync(SQLModel.metadata.create_all)
Note o conn.run_sync(...): create_all é uma operação síncrona do SQLAlchemy Core que não tem equivalente assíncrono nativo, então ela é despachada para rodar dentro da conexão assíncrona via essa ponte — um padrão recorrente sempre que uma API legada do SQLAlchemy precisa ser chamada a partir de código async.
CRUD com select() e session.exec()
Desde o SQLAlchemy 2.0 (que o SQLModel adota), consultas usam a construção declarativa select() em vez do antigo session.query(). Com AsyncSession, cada chamada que toca o banco precisa de await:
from sqlmodel import select
async def buscar_caros(session: AsyncSession, limite: float) -> list[Produto]:
stmt = select(Produto).where(Produto.preco > limite).order_by(Produto.preco)
resultado = await session.exec(stmt)
return list(resultado.all())
async def criar_produto(session: AsyncSession, nome: str, preco: float) -> Produto:
produto = Produto(nome=nome, preco=preco)
session.add(produto)
await session.commit()
await session.refresh(produto) # recarrega o ID gerado pelo banco
return produto
session.add() não grava nada — apenas marca o objeto como pendente na sessão. Só await session.commit() executa o INSERT/UPDATE e finaliza a transação; session.refresh() é necessário depois porque, após o commit, o SQLAlchemy expira os atributos do objeto em memória por padrão, e um acesso a produto.id sem refresh dispararia uma nova consulta implícita (ou falharia, fora do contexto assíncrono correto).
Atualização parcial e soft delete
Para PATCH, o padrão idiomático usa model_dump(exclude_unset=True), que retorna apenas os campos explicitamente enviados no payload — distinguindo “campo omitido” de “campo enviado como None”:
class ProdutoUpdate(SQLModel):
nome: Optional[str] = None
preco: Optional[float] = None
async def aplicar_patch(session: AsyncSession, produto: Produto, dados: ProdutoUpdate) -> Produto:
for campo, valor in dados.model_dump(exclude_unset=True).items():
setattr(produto, campo, valor)
session.add(produto)
await session.commit()
await session.refresh(produto)
return produto
Exclusão física (DELETE) quebra integridade referencial e histórico de auditoria em sistemas corporativos; por isso muitas tabelas preferem soft delete — uma flag is_deleted e um deleted_at, filtrados nas consultas normais, preservando o registro para relatórios e rastreabilidade.
🔗 Recursos Pedagógicos do Capítulo 06
| 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 ➡️ |