O que é Alembic e Por Que Você Precisa Dele
Alembic é uma ferramenta de versionamento de banco de dados leve e poderosa, desenvolvida pelo criador do SQLAlchemy (Mike Bayer). Ela resolve um problema fundamental em desenvolvimento: como gerenciar mudanças no esquema do banco de dados de forma segura, rastreável e colaborativa. Sem uma ferramenta assim, você provavelmente está escrevendo scripts SQL manualmente ou, pior ainda, aplicando alterações diretamente no banco sem controle de versão.
A essência do Alembic é permitir que você crie "migrations" — arquivos de código Python que descrevem transformações no banco de dados. Cada migration é versionada e pode ser revertida, reproduzida em diferentes ambientes e rastreada no Git como qualquer outro código. Isso é particularmente importante quando você trabalha em equipe: todos saem do estado X para o estado Y da mesma forma, sem ambiguidades ou surpresas.
Configuração Inicial e Estrutura do Projeto
Instalação e Inicialização
Comece instalando Alembic e SQLAlchemy:
pip install alembic sqlalchemy
Após a instalação, inicialize um novo projeto Alembic no diretório raiz da sua aplicação:
alembic init migrations
Isso cria uma estrutura de diretórios assim:
projeto/
├── migrations/
│ ├── versions/ # Aqui ficam as migrations
│ ├── env.py # Configuração do ambiente
│ ├── script.py.mako # Template para novas migrations
│ └── alembic.ini # Arquivo de configuração
├── app.py
└── models.py
Configuração da String de Conexão
Abra alembic.ini e localize a linha sqlalchemy.url. Esta é a string de conexão que o Alembic usará. Para desenvolvimento local com SQLite:
sqlalchemy.url = sqlite:///./app.db
Para PostgreSQL em produção:
sqlalchemy.url = postgresql://user:password@localhost:5432/meudb
Você pode usar variáveis de ambiente para não expor credenciais:
# Em alembic/env.py
import os
from sqlalchemy import engine_from_config, pool
from logging.config import fileConfig
config = context.config
db_url = os.getenv('DATABASE_URL', 'sqlite:///./app.db')
config.set_main_option('sqlalchemy.url', db_url)
Entendendo o Fluxo de Migrations
O Ciclo de Vida de uma Migration
Uma migration é um arquivo Python que contém duas funções: upgrade() e downgrade(). A função upgrade() aplica as mudanças ao banco; downgrade() as reverte. Este design permite voltar para qualquer ponto anterior do esquema — um recurso fundamental para correção de bugs e experimentos seguros.
Quando você executa alembic upgrade head, o Alembic verifica qual é a última migration aplicada no banco (através de uma tabela de controle chamada alembic_version) e aplica apenas as novas migrations que vieram depois. Isso garante idempotência: rodar o comando duas vezes não duplica alterações.
Criando Sua Primeira Migration
Primeiro, defina seus modelos SQLAlchemy:
# models.py
from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.orm import declarative_base
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String(100), nullable=False)
email = Column(String(120), unique=True, nullable=False)
Agora configure Alembic para reconhecer seus modelos. Abra alembic/env.py e adicione:
# alembic/env.py
from app.models import Base # Importe seus modelos
target_metadata = Base.metadata
Com os modelos configurados, gere a migration automaticamente:
alembic revision --autogenerate -m "Create users table"
Alembic detectou que você criou a tabela users e gerou um arquivo em migrations/versions/. O arquivo terá um nome como 001_create_users_table.py com conteúdo similar a:
# migrations/versions/001_create_users_table.py
from alembic import op
import sqlalchemy as sa
def upgrade():
op.create_table(
'users',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=100), nullable=False),
sa.Column('email', sa.String(length=120), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('email')
)
def downgrade():
op.drop_table('users')
Aplique a migration:
alembic upgrade head
Se precisar reverter:
alembic downgrade -1
Operações Avançadas e Boas Práticas
Alterações Estruturais Complexas
Nem sempre o autogenerate consegue detectar ou gerar migrations perfectas. Para alterações mais complexas, crie migrations manuais:
alembic revision -m "Add phone to users"
Edite o arquivo gerado:
# migrations/versions/002_add_phone_to_users.py
from alembic import op
import sqlalchemy as sa
def upgrade():
op.add_column('users', sa.Column('phone', sa.String(20)))
op.create_index(op.f('ix_users_phone'), 'users', ['phone'])
def downgrade():
op.drop_index(op.f('ix_users_phone'), table_name='users')
op.drop_column('users', 'phone')
Para renomear colunas com segurança (importante em bancos com dados):
def upgrade():
# Diferentes bancos têm sintaxes diferentes
op.alter_column('users', 'phone', new_column_name='phone_number')
def downgrade():
op.alter_column('users', 'phone_number', new_column_name='phone')
Migrations com Dados
Às vezes você precisa preencher dados durante uma migration, não apenas alterar estrutura:
from alembic import op
import sqlalchemy as sa
from sqlalchemy.sql import text
def upgrade():
# Criar coluna com valor padrão temporário
op.add_column('users', sa.Column('status', sa.String(20), server_default='active'))
# Executar SQL bruto para operações complexas
connection = op.get_bind()
connection.execute(text("""
UPDATE users SET status = 'inactive'
WHERE created_at < NOW() - INTERVAL 1 YEAR
"""))
# Remover o padrão de servidor após preencher dados
op.alter_column('users', 'status', server_default=None)
def downgrade():
op.drop_column('users', 'status')
Inspeção e Histórico
Para visualizar todas as migrations e seu estado:
alembic history
Para ver qual migration está atualmente aplicada:
alembic current
Para visualizar as mudanças de uma migration específica sem aplicá-la (útil em code review):
alembic show <revision_id>
Conclusão
Os três pontos-chave que você deve levar deste artigo são: primeiro, Alembic fornece versionamento confiável e reversível do esquema do banco, permitindo que você colabore com segurança e mantenha um histórico auditável de todas as mudanças estruturais; segundo, o autogenerate é uma ferramenta poderosa mas exige revisão manual, especialmente para operações complexas ou migrações de dados, então nunca confie cegamente em uma migration gerada; terceiro, o design de upgrade/downgrade do Alembic força você a pensar em reversibilidade, o que resulta em código mais robusto e equipes capazes de lidar com falhas em produção sem pânico.