Transações no SQLAlchemy mantêm um conjunto de alterações como uma única unidade: ou todas são confirmadas com commit, ou todas são desfeitas com rollback. A Session acompanha objetos, executa o padrão unit of work e obtém uma conexão quando precisa falar com o banco.

Antes deste tema, vale revisar o guia completo de SQLAlchemy. O ponto central é separar o escopo da transação do tempo de vida arbitrário da aplicação.

Use context manager para delimitar a transação

from sqlalchemy import create_engine
from sqlalchemy.orm import Session

engine = create_engine("postgresql+psycopg://app:senha@localhost/app")

def transferir(origem, destino, valor):
    with Session(engine) as session:
        with session.begin():
            origem.saldo -= valor
            destino.saldo += valor
            session.add_all([origem, destino])

Se o bloco terminar normalmente, session.begin() confirma. Se surgir uma exceção, ele reverte e propaga o erro. O bloco externo fecha a Session e devolve recursos ao pool. Não capture uma exceção apenas para ignorá-la, pois o chamador precisa saber que a transferência falhou.

Também é possível criar uma fábrica:

from sqlalchemy.orm import sessionmaker

SessionLocal = sessionmaker(bind=engine)

with SessionLocal.begin() as session:
    session.add(novo_pedido)

flush não é commit

flush() envia INSERT, UPDATE e DELETE pendentes ao banco, mas continua dentro da transação. Ele é útil para obter uma chave gerada antes de criar outro objeto:

with SessionLocal.begin() as session:
    pedido = Pedido(cliente_id=42)
    session.add(pedido)
    session.flush()
    session.add(Item(pedido_id=pedido.id, sku="PY-01"))

Uma consulta ou commit() pode provocar flush automático. Se uma restrição falhar durante o flush, a Session fica inativa para aquela transação. Faça rollback() ou deixe o context manager cuidar disso antes de continuar.

Escopo de Session em aplicações web

Uma prática comum é abrir uma Session por requisição, executar a regra de negócio, confirmar operações de escrita e fechar ao final. Não compartilhe a mesma instância entre threads ou tarefas assíncronas. Para código assíncrono, use uma AsyncSession por tarefa e consulte o guia de SQLAlchemy assíncrono.

Evite chamar commit() dentro de cada função de repositório. Isso impede que uma operação de negócio com várias etapas seja atômica. Prefira deixar a camada que conhece a unidade de trabalho decidir quando confirmar.

Savepoint para recuperar apenas uma etapa

begin_nested() cria um savepoint quando o banco oferece suporte:

with SessionLocal.begin() as session:
    session.add(lote)
    try:
        with session.begin_nested():
            session.add(item_opcional)
            session.flush()
    except IntegrityError:
        registrar_item_rejeitado()

O erro reverte o savepoint, não necessariamente a transação externa. Use esse recurso com intenção clara; ele não substitui a definição correta dos limites da transação.

Testes e erros comuns

Teste sucesso e falha no meio da operação, verificando que não restou estado parcial. Evite Session global, transações abertas durante chamadas HTTP lentas e tratamento genérico que oculta erros. Mantenha a transação curta, mas longa o suficiente para representar uma regra de negócio indivisível.

A documentação oficial de transações do SQLAlchemy, consultada em 28 de julho de 2026, detalha autobegin, context managers, savepoints e níveis de isolamento.