SQLAlchemy: O que É e Como Funciona | Python Brasil
Guia completo sobre SQLAlchemy em Python: ORM, modelos, consultas, relacionamentos, migrations e boas praticas para trabalhar com bancos de dados.
O que e SQLAlchemy?
SQLAlchemy e a biblioteca de banco de dados mais popular e poderosa do ecossistema Python. Ela oferece duas interfaces: o Core, que fornece uma camada de abstracao SQL expressiva, e o ORM (Object-Relational Mapping), que permite mapear tabelas do banco de dados para classes Python. Com SQLAlchemy, voce trabalha com objetos Python em vez de escrever SQL bruto, mantendo ao mesmo tempo controle total sobre as consultas geradas.
SQLAlchemy suporta diversos bancos de dados, incluindo PostgreSQL, MySQL, SQLite, Oracle e Microsoft SQL Server, permitindo trocar de banco com minimas alteracoes no codigo.
Instalacao e Configuracao
# Instalacao
# pip install sqlalchemy
# Para PostgreSQL
# pip install sqlalchemy psycopg2-binary
# Para MySQL
# pip install sqlalchemy pymysql
from sqlalchemy import create_engine
from sqlalchemy.orm import DeclarativeBase, Session
# Criando a engine (conexao com o banco)
engine = create_engine('sqlite:///meubanco.db', echo=True)
# Para PostgreSQL
# engine = create_engine('postgresql://usuario:senha@localhost:5432/meubanco')
# Classe base para modelos
class Base(DeclarativeBase):
pass
Definindo Modelos
from sqlalchemy import String, Integer, Float, ForeignKey, DateTime
from sqlalchemy.orm import Mapped, mapped_column, relationship
from datetime import datetime
class Usuario(Base):
__tablename__ = 'usuarios'
id: Mapped[int] = mapped_column(primary_key=True)
nome: Mapped[str] = mapped_column(String(100))
email: Mapped[str] = mapped_column(String(200), unique=True)
ativo: Mapped[bool] = mapped_column(default=True)
criado_em: Mapped[datetime] = mapped_column(
DateTime, default=datetime.utcnow
)
# Relacionamento um-para-muitos
pedidos: Mapped[list['Pedido']] = relationship(back_populates='usuario')
def __repr__(self):
return f'<Usuario(id={self.id}, nome={self.nome})>'
class Pedido(Base):
__tablename__ = 'pedidos'
id: Mapped[int] = mapped_column(primary_key=True)
usuario_id: Mapped[int] = mapped_column(ForeignKey('usuarios.id'))
produto: Mapped[str] = mapped_column(String(200))
valor: Mapped[float] = mapped_column(Float)
criado_em: Mapped[datetime] = mapped_column(
DateTime, default=datetime.utcnow
)
# Relacionamento reverso
usuario: Mapped['Usuario'] = relationship(back_populates='pedidos')
def __repr__(self):
return f'<Pedido(id={self.id}, produto={self.produto})>'
# Criando as tabelas no banco
Base.metadata.create_all(engine)
Operacoes CRUD
from sqlalchemy.orm import Session
# CREATE — inserindo dados
with Session(engine) as session:
novo_usuario = Usuario(nome='Ana Silva', email='ana@email.com')
session.add(novo_usuario)
# Inserindo varios de uma vez
session.add_all([
Usuario(nome='Bruno Costa', email='bruno@email.com'),
Usuario(nome='Carla Lima', email='carla@email.com'),
])
session.commit()
# READ — consultando dados
with Session(engine) as session:
# Busca por chave primaria
usuario = session.get(Usuario, 1)
print(usuario.nome)
# Busca com filtro
from sqlalchemy import select
stmt = select(Usuario).where(Usuario.nome == 'Ana Silva')
resultado = session.execute(stmt).scalar_one_or_none()
# Todos os usuarios ativos
stmt = select(Usuario).where(Usuario.ativo == True).order_by(Usuario.nome)
usuarios = session.execute(stmt).scalars().all()
# UPDATE — atualizando dados
with Session(engine) as session:
usuario = session.get(Usuario, 1)
usuario.nome = 'Ana Maria Silva'
session.commit()
# DELETE — removendo dados
with Session(engine) as session:
usuario = session.get(Usuario, 3)
session.delete(usuario)
session.commit()
Consultas Avancadas
from sqlalchemy import select, func, and_, or_, desc
with Session(engine) as session:
# Filtros compostos
stmt = select(Usuario).where(
and_(
Usuario.ativo == True,
Usuario.nome.like('A%')
)
)
# OR
stmt = select(Usuario).where(
or_(
Usuario.email.contains('gmail'),
Usuario.email.contains('hotmail')
)
)
# Agregacoes
stmt = select(func.count(Usuario.id)).where(Usuario.ativo == True)
total = session.execute(stmt).scalar()
# JOIN com relacionamentos
stmt = (
select(Usuario, Pedido)
.join(Pedido)
.where(Pedido.valor > 100)
.order_by(desc(Pedido.criado_em))
)
# Agrupamento
stmt = (
select(Usuario.nome, func.sum(Pedido.valor).label('total'))
.join(Pedido)
.group_by(Usuario.nome)
.having(func.sum(Pedido.valor) > 500)
)
# Paginacao
stmt = select(Usuario).order_by(Usuario.id).offset(0).limit(10)
pagina = session.execute(stmt).scalars().all()
Gerenciamento de Sessao
from sqlalchemy.orm import sessionmaker
# Fabrica de sessoes
SessionLocal = sessionmaker(bind=engine)
# Usando como context manager
def criar_usuario(nome: str, email: str) -> Usuario:
with SessionLocal() as session:
with session.begin(): # commit automatico ou rollback em caso de erro
usuario = Usuario(nome=nome, email=email)
session.add(usuario)
return usuario
# Pattern para FastAPI / Flask
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
Migrations com Alembic
# Instalacao: pip install alembic
# Inicializar: alembic init alembic
# Gerar migration: alembic revision --autogenerate -m "criar tabela usuarios"
# Aplicar: alembic upgrade head
# Reverter: alembic downgrade -1
# Exemplo de migration gerada (alembic/versions/xxx_criar_tabela.py)
"""
def upgrade():
op.create_table(
'usuarios',
sa.Column('id', sa.Integer(), primary_key=True),
sa.Column('nome', sa.String(100), nullable=False),
sa.Column('email', sa.String(200), unique=True),
)
def downgrade():
op.drop_table('usuarios')
"""
Erros Comuns
O erro mais frequente e nao fechar sessoes corretamente, causando vazamento de conexoes. Sempre use context managers (with). Outro erro e acessar atributos de relacionamento fora da sessao, o que causa DetachedInstanceError. Tambem e comum esquecer de chamar session.commit() apos modificacoes, ou nao tratar excecoes que exigem session.rollback(). O uso de echo=True em producao e outro erro que polui logs com SQL desnecessario.
Boas Praticas
Use o estilo moderno com Mapped e mapped_column em vez da API classica Column. Sempre use context managers para sessoes. Configure Alembic para migrations desde o inicio do projeto. Use sessionmaker para criar uma fabrica de sessoes. Separe a definicao de modelos da logica de negocio. Em aplicacoes web, use uma sessao por requisicao. Evite carregar relacionamentos desnecessarios — configure lazy='select' ou use selectinload para otimizar consultas.
Quando Usar
SQLAlchemy e ideal para qualquer projeto Python que interaja com bancos de dados relacionais. O ORM e excelente para aplicacoes com logica de dominio complexa, enquanto o Core e preferivel para scripts de migracao de dados ou consultas que exigem controle fino sobre o SQL gerado.