Guia Completo de Alembic: Gerenciamento de Migrações
O Alembic é a ferramenta oficial de controle e migração de esquemas de banco de dados para o ecossistema do SQLAlchemy. Ele funciona como um "sistema de versionamento de código" (semelhante ao Git), mas voltado para a estrutura das tabelas, índices, colunas e restrições do seu banco de dados.
1. Por que usar o Alembic?
Em aplicações reais, os modelos de dados evoluem constantemente: novas colunas surgem, tipos mudam e tabelas são criadas ou removidas.
- Sem o Alembic: Você precisaria executar comandos
ALTER TABLEmanualmente no banco de produção ou depender de recriações completas (Base.metadata.create_all()), o que não altera colunas existentes nem preserva o histórico de modificações. - Com o Alembic:
- Histórico versionado de cada alteração de esquema em arquivos Python (
revisions). - Automação de migração com comandos para avançar (
upgrade) e reverter (downgrade). - Geração automática de scripts de migração a partir da comparação entre os modelos SQLAlchemy e o banco real (
autogenerate). - Compatibilidade com pipelines de CI/CD para migrações seguras em produção.
- Histórico versionado de cada alteração de esquema em arquivos Python (
2. Instalação e Inicialização
Instalação
Instale o Alembic e o driver de conexão com o banco de dados desejado (como PostgreSQL, SQLite, MySQL):
# Com pip
pip install alembic sqlalchemy psycopg2-binary
# Ou com uv / poetry
uv add alembic sqlalchemy psycopg2-binary
Inicializando o Diretório do Alembic
No diretório raiz do seu projeto, execute:
alembic init alembic
Isso gerará a estrutura básica de migração:
meu_projeto/
├── alembic.ini # Configurações globais e string de conexão padrão
├── alembic/ # Diretório do ambiente Alembic
│ ├── env.py # Script executado a cada comando do Alembic
│ ├── README
│ ├── script.py.mako # Template Jinja/Mako para gerar as migrações
│ └── versions/ # Pasta onde ficam os scripts de migração gerados
├── src/
│ └── ...
└── pyproject.toml
3. Configuração Inicial Essencial
Para o Alembic funcionar perfeitamente com os seus modelos SQLAlchemy e detectar alterações automaticamente, precisamos configurar dois arquivos:
3.1 alembic.ini (ou Variável de Ambiente)
No alembic.ini, defina a URL do banco de dados na chave sqlalchemy.url:
# alembic.ini
sqlalchemy.url = postgresql+psycopg2://usuario:senha@localhost:5432/meubanco
Dica de Produção: Evite senhas em texto puro no
.ini. O padrão recomendado é ler a URL a partir de variáveis de ambiente diretamente dentro doenv.py.
3.2 alembic/env.py (Conectando aos Modelos)
O arquivo env.py é o cérebro das migrações. Por padrão, a variável target_metadata vem como None. Para habilitar o modo autogenerate, aponte-a para o Base.metadata do seu projeto.
Exemplo de configuração dinâmica com variáveis de ambiente:
import os
import sys
from logging.config import fileConfig
from sqlalchemy import engine_from_config, pool
from alembic import context
# 1. Garanta que o diretório raiz do projeto esteja no PYTHONPATH
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
# 2. Importe o Base com os metadados dos seus modelos
from src.infrastructure.database import Base # noqa
import src.infrastructure.models # Garante que todos os modelos foram carregados
# Configurações de logging do Alembic
config = context.config
# 3. Sobrescreva a URL do banco com variável de ambiente (se existir)
db_url = os.getenv("DATABASE_URL")
if db_url:
config.set_main_option("sqlalchemy.url", db_url)
if config.config_file_name is not None:
fileConfig(config.config_file_name)
# 4. Vincule os metadados para que o autogenerate funcione!
target_metadata = Base.metadata
def run_migrations_offline() -> None:
"""Modo offline: gera o SQL puro sem conectar diretamente ao banco."""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online() -> None:
"""Modo online: conecta ao banco e aplica as migrações."""
connectable = engine_from_config(
config.get_section(config.config_ini_section, {}),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata,
compare_type=True, # Opcional: detecta alterações de tipo de coluna
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()
4. O Fluxo de Trabalho de Migrações (Workflow Diário)
O ciclo de vida de desenvolvimento com Alembic resume-se a três passos simples:
Alterar Modelos Python ➔ Gerar Revisão (alembic revision) ➔ Aplicar no Banco (alembic upgrade)
Passo 1: Criar ou Modificar um Modelo
Exemplo em src/infrastructure/models.py:
from sqlalchemy import Column, Integer, String, DateTime, func
from src.infrastructure.database import Base
class Usuario(Base):
__tablename__ = "usuarios"
id = Column(Integer, primary_key=True)
nome = Column(String(100), nullable=False)
email = Column(String(255), unique=True, nullable=False, index=True)
criado_em = Column(DateTime, server_default=func.now(), nullable=False)
Passo 2: Gerar o Arquivo de Migração Automaticamente
Execute o comando revision com a flag --autogenerate:
alembic revision --autogenerate -m "criar tabela de usuarios"
O Alembic comparará o estado do banco atual com os modelos SQLAlchemy e criará um arquivo em alembic/versions/ (ex: 9a8b7c6d5e4f_criar_tabela_de_usuarios.py):
"""criar tabela de usuarios
Revision ID: 9a8b7c6d5e4f
Revises:
Create Date: 2025-01-15 10:30:00.000000
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
revision: str = '9a8b7c6d5e4f'
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.create_table(
'usuarios',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('nome', sa.String(length=100), nullable=False),
sa.Column('email', sa.String(length=255), nullable=False),
sa.Column('criado_em', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_usuarios_email'), 'usuarios', ['email'], unique=True)
def downgrade() -> None:
op.drop_index(op.f('ix_usuarios_email'), table_name='usuarios')
op.drop_table('usuarios')
Sempre revise o arquivo gerado! O recurso de
autogenerateé excelente, mas algumas operações complexas (como renomeação de colunas) podem ser interpretadas como uma remoção (drop) seguida de adição (add).
Passo 3: Aplicar a Migração ao Banco
Para executar todas as migrações pendentes até a versão mais recente:
alembic upgrade head
Para aplicar apenas a próxima migração:
alembic upgrade +1
5. Revertendo e Inspecionando Migrações
Revertendo Migrações (downgrade)
Para desfazer a última migração aplicada:
alembic downgrade -1
Para voltar o banco ao estado inicial (remover todas as migrações):
alembic downgrade base
Para voltar a uma revisão específica:
alembic downgrade <revision_id>
Comandos de Inspeção e Diagnóstico
- Ver a revisão atual aplicada no banco:
alembic current - Ver o histórico de todas as migrações criadas:
alembic history --verbose
- Ver quais migrações ainda não foram aplicadas:
alembic heads
6. Onde o Alembic fica na Estrutura do Projeto?
A convenção recomendada é manter o diretório alembic/ e o arquivo alembic.ini na raiz do repositório, desacoplados do código de aplicação:
meu_projeto/
├── alembic.ini # Configuração do Alembic na raiz
├── alembic/ # Migrações separadas do código fonte
│ ├── env.py # Script de execução
│ ├── script.py.mako # Template das revisões
│ └── versions/ # Scripts de migração (versionados no Git)
│ ├── 20250115_01_init.py
│ └── 20250120_02_add_status.py
│
├── src/ # Código da aplicação
│ ├── core/
│ │ └── config.py # Lê configs (incluindo DATABASE_URL)
│ ├── domain/
│ │ └── enums/
│ └── infrastructure/
│ ├── database.py # engine, sessionmaker, Base
│ └── models/ # Modelos SQLAlchemy mapeados
│ ├── __init__.py
│ ├── usuario.py
│ └── pedido.py
│
├── tests/
├── pyproject.toml
└── README.md
7. O que o autogenerate Detecta (e o que NÃO detecta)
O que o Autogenerate detecta automaticamente:
- Criação e remoção de tabelas (
create_table,drop_table). - Adição e remoção de colunas (
add_column,drop_column). - Alteração de nulabilidade (
nullable=True/False). - Criação de chaves primárias e estrangeiras básicas.
- Índices e restrições
UNIQUE.
O que NÃO detecta automaticamente (ou requer atenção):
- Renomeação de colunas/tabelas: O Alembic enxergará como um
drop_columnda coluna antiga e umadd_columnda nova, causando perda de dados se aplicado sem ajuste manual. (Useop.alter_column(..., new_column_name=...)). - Alterações de tipo de coluna: Por padrão fica desativado. Requer habilitar
compare_type=Truena chamada decontext.configure()noenv.py. - Nomes de Constraints não identificados: Restrições sem nomes explícitos podem gerar scripts inconsistentes em bancos como PostgreSQL ou Oracle.
8. Boas Práticas em Ambientes de Produção
- Nunca edite uma migração já enviada para produção ou para a branch principal: Sempre crie uma nova revisão com
alembic revisionpara corrigir ou alterar o esquema. - Versionar a pasta
versions/no Git: Toda a equipe e o pipeline de CI/CD devem compartilhar o mesmo histórico de migrações. - Execute migrações em CI/CD antes do deploy da aplicação: No pipeline de deploy, rode
alembic upgrade headantes de subir os contêineres que consom a nova estrutura de tabelas. - Utilize Naming Conventions no SQLAlchemy: Adicione convenções de nomenclatura explícitas aos metadados do SQLAlchemy para evitar problemas com geração de nomes de índices e chaves estrangeiras:
from sqlalchemy import MetaData convention = { "ix": "ix_%(column_0_label)s", "uq": "uq_%(table_name)s_%(column_0_name)s", "ck": "ck_%(table_name)s_%(constraint_name)s", "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s", "pk": "pk_%(table_name)s" } metadata = MetaData(naming_convention=convention) Base = declarative_base(metadata=metadata)