Python Admin

O que Todo Dev Deve Saber sobre Alembic em Python: Migrations Versionadas com SQLAlchemy Já leu

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: Após a instalação, inicialize

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.

Referências


Artigos relacionados