Dominando o SQLAlchemy 2.0: Mapeamento ORM e Padrões de Persistência

O framework SQLAlchemy consolidou-se como a solução padrão da indústria para mapeamento objeto-relacional (ORM) e gerenciamento de bancos de dados em Python. A versão 2.0 entroduziu uma abordagem orientada a tipagem estática e consultas baseadas em expressões, oferecendo maior perofrmance e segurança de tipos.

Configuração do Ambiente

A instalação do núcleo do framework é realizada via pip. Dependendo do SGBD alvo, drivers específicos devem ser incluídos.


# Instalação do núcleo
pip install sqlalchemy

# Drivers opcionais para SGBDs específicos
pip install psycopg2-binary  # PostgreSQL
pip install pymysql          # MySQL

Arquitetura e Conceitos Fundamentais

  • Engine: Ponto de partida para a comunicação com o banco, gerenciando o pool de conexões e a emissão de instruções SQL.
  • Session: Workspace que rastreia alterações em objetos e coordena transações com o banco de dados.
  • Mapper: Componente responsável por traduzir classes Python para tabelas relacionais e vice-versa.

Estabelecendo a Conexão

A engine é configurada utilizando uma string de conexão (URL). O parâmetro echo pode ser ativado para depuração de SQL.


from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

# Configuração para SQLite em memória (ideal para testes)
engine = create_engine("sqlite+pysqlite:///:memory:", echo=False, future=True)

# Configuração para PostgreSQL (exemplo)
# engine = create_engine("postgresql+psycopg2://user:pass@localhost/dbname", pool_size=10)

SessionFactory = sessionmaker(bind=engine, expire_on_commit=False)

Definição de Modelos com Tipagem Estática (Estilo 2.0)

A nova API utiliza anotações de tipo do Python para definir colunas, eliminando a necessidade do construtor Column explícito e melhorando a integração com mypy.


from sqlalchemy import String, Integer, ForeignKey, Table
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship

class Base(DeclarativeBase):
    pass

class Cliente(Base):
    __tablename__ = "clientes"

    id: Mapped[int] = mapped_column(primary_key=True)
    nome: Mapped[str] = mapped_column(String(120), nullable=False)
    email: Mapped[str] = mapped_column(String(150), unique=True, index=True)
    
    pedidos: Mapped[list["Pedido"]] = relationship(back_populates="comprador", cascade="all, delete-orphan")

class Pedido(Base):
    __tablename__ = "pedidos"

    id: Mapped[int] = mapped_column(primary_key=True)
    descricao: Mapped[str] = mapped_column(String(255))
    valor_total: Mapped[float] = mapped_column(default=0.0)
    cliente_id: Mapped[int] = mapped_column(ForeignKey("clientes.id"))
    
    comprador: Mapped["Cliente"] = relationship(back_populates="pedidos")
    etiquetas: Mapped[list["Etiqueta"]] = relationship(secondary="pedido_etiquetas", back_populates="pedidos")

class Etiqueta(Base):
    __tablename__ = "etiquetas"

    id: Mapped[int] = mapped_column(primary_key=True)
    nome: Mapped[str] = mapped_column(String(50), unique=True)
    
    pedidos: Mapped[list["Pedido"]] = relationship(secondary="pedido_etiquetas", back_populates="etiquetas")

# Tabela de associação para relacionamento N:N
pedido_etiquetas = Table(
    "pedido_etiquetas",
    Base.metadata,
    mapped_column("pedido_id", ForeignKey("pedidos.id"), primary_key=True),
    mapped_column("etiqueta_id", ForeignKey("etiquetas.id"), primary_key=True)
)

Sincronização do Esquema

A criação das tabelas no banco de dados é executada através do metadados da classe base.


Base.metadata.create_all(bind=engine)

Operações CRUD com a API 2.0

Inserção de Registros


from sqlalchemy import select

with SessionFactory() as session:
    novo_cliente = Cliente(nome="Ana Souza", email="ana.souza@dominio.com")
    session.add(novo_cliente)
    session.commit()
    
    # Inserção em lote
    clientes_lote = [
        Cliente(nome="Carlos Lima", email="carlos@dominio.com"),
        Cliente(nome="Beatriz Costa", email="bia@dominio.com")
    ]
    session.add_all(clientes_lote)
    session.commit()

Lietura e Consultas

A API 2.0 substitui o legado session.query() por session.execute(select()).


with SessionFactory() as session:
    # Recuperar todos os registros
    stmt = select(Cliente)
    clientes = session.scalars(stmt).all()

    # Recuperar por chave primária
    stmt = select(Cliente).where(Cliente.id == 1)
    cliente_alvo = session.scalar(stmt)

    # Projeção de colunas específicas
    stmt = select(Cliente.nome, Cliente.email).where(Cliente.nome.like("A%"))
    resultados = session.execute(stmt).all()

Atualização e Remoção


from sqlalchemy import update, delete

with SessionFactory() as session:
    # Atualização via objeto
    stmt = select(Cliente).where(Cliente.id == 1)
    cliente = session.scalar(stmt)
    if cliente:
        cliente.nome = "Ana Souza Lima"
        session.commit()

    # Atualização em lote (Bulk)
    stmt = update(Cliente).where(Cliente.nome.like("C%")).values(nome="Cliente Atualizado")
    session.execute(stmt)
    session.commit()

    # Remoção em lote
    stmt = delete(Cliente).where(Cliente.email == "bia@dominio.com")
    session.execute(stmt)
    session.commit()

Consultas Avançadas e Agregações


from sqlalchemy import func, or_

with SessionFactory() as session:
    # Filtros complexos e OR
    stmt = select(Cliente).where(
        or_(Cliente.nome == "Ana Souza Lima", Cliente.email.like("%@dominio.com"))
    )
    
    # Joins e Agregações
    stmt = (
        select(Cliente.nome, func.count(Pedido.id).label("total_pedidos"))
        .join(Pedido, Cliente.id == Pedido.cliente_id)
        .group_by(Cliente.nome)
        .having(func.count(Pedido.id) > 0)
    )
    resumo = session.execute(stmt).all()
    
    # Ordenação e Paginação
    stmt = select(Cliente).order_by(Cliente.nome.desc()).offset(10).limit(5)
    pagina = session.scalars(stmt).all()

Gerenciamento de Transações e Savepoints

O controle transacional garante a integridade dos dados, permitindo aninhamento através de savepoints.


with SessionFactory() as session:
    try:
        with session.begin():
            cliente = Cliente(nome="Teste Transacional", email="teste@dominio.com")
            session.add(cliente)
            
            # Savepoint aninhado
            with session.begin_nested():
                pedido = Pedido(descricao="Pedido de Teste", valor_total=150.0, comprador=cliente)
                session.add(pedido)
                # Se falhar aqui, apenas o pedido é revertido, o cliente é mantido
                
    except Exception as e:
        print(f"Falha na transação: {e}")
        # O rollback é automático ao sair do bloco 'with session.begin()' em caso de exceção

Padrões de Projeto e Ciclo de Vida da Sessão

Para aplicações web ou APIs, a sessão deve ter um escopo bem definido, geralmente vinculado ao ciclo de vida de uma requisição.


from contextlib import contextmanager

@contextmanager
def get_transactional_session():
    session = SessionFactory()
    try:
        yield session
        session.commit()
    except Exception:
        session.rollback()
        raise
    finally:
        session.close()

# Utilização em camadas de serviço
def processar_venda(dados_cliente, dados_pedido):
    with get_transactional_session() as db:
        cliente = Cliente(**dados_cliente)
        db.add(cliente)
        db.flush() # Garante o ID do cliente sem fechar a transação
        
        pedido = Pedido(**dados_pedido, cliente_id=cliente.id)
        db.add(pedido)

Para mitigar o problema de consultas N+1, utilize a estratégia de carregamento antecipado (eager loading) através de selectinload ou joinedload nas relações.

Tags: SQLAlchemy python-orm database-mapping sqlalchemy-2.0 python-backend

Publicado em 7-7 00:56