Alterar modelos SQLAlchemy não atualiza com segurança um banco já existente. Alembic registra cada mudança de esquema em uma revisão ordenada, permitindo aplicar a mesma sequência no desenvolvimento, teste e produção.

Este guia pressupõe familiaridade com SQLAlchemy em Python. Migração não substitui backup, teste nem plano de implantação.

Iniciar o ambiente

Instale Alembic no mesmo ambiente da aplicação e execute:

python -m pip install alembic
alembic init migrations

O comando cria alembic.ini, migrations/env.py e migrations/versions/. Versione esses arquivos. Configure a URL do banco por variável de ambiente, evitando credenciais no repositório.

Em env.py, importe a metadata dos modelos:

from app.database import Base

target_metadata = Base.metadata

A estrutura completa é explicada no tutorial oficial do Alembic.

Gerar e revisar uma migração

Depois de alterar um modelo:

alembic revision --autogenerate -m "adiciona status ao pedido"

Abra o arquivo criado. Confirme nomes, tipos, valores padrão, nulabilidade, índices e funções upgrade() e downgrade(). Renomear uma coluna pode ser interpretado como remover uma e criar outra, o que perderia dados se fosse aplicado sem correção manual.

Antes de executar em produção, gere o SQL ou aplique a revisão em um banco descartável:

alembic upgrade head
alembic current
alembic history

Mudanças seguras

Adicionar uma coluna obrigatória a uma tabela populada costuma exigir etapas: criar como opcional, preencher dados, depois aplicar a restrição. Remover coluna ou reduzir tipo pode bloquear a tabela e destruir informação. Para mudanças grandes, meça duração e conheça o comportamento específico do PostgreSQL, MySQL ou SQLite usado pelo projeto.

O autogenerate é um assistente, não uma auditoria. Consulte a documentação de autogeração para entender o que ele detecta e suas limitações.

Fluxo de equipe

  • Gere uma revisão por mudança lógica.
  • Use mensagens descritivas e revise o SQL.
  • Teste upgrade em uma cópia representativa.
  • Não edite revisões que já chegaram à produção; crie outra.
  • Faça backup antes de operações destrutivas.
  • Execute migrações uma vez durante o deploy, não em cada worker da aplicação.

Integre o teste da sequência de migrações ao fluxo de CI com GitHub Actions. Um bom pipeline cria o banco vazio, aplica upgrade head e inicia testes. Assim, a estrutura necessária pela aplicação é verificada antes da implantação.

Como o histórico de revisões funciona

Cada arquivo em migrations/versions possui um identificador de revisão e aponta para uma revisão anterior. Esses vínculos formam um grafo, não apenas uma lista de nomes de arquivo. O comando alembic heads mostra as pontas atuais; alembic current informa a revisão registrada no banco; alembic history exibe o caminho.

Alembic mantém essa posição em uma tabela chamada alembic_version. Não altere a tabela manualmente para “resolver” uma falha. Se o esquema e o identificador divergirem, descubra primeiro se uma migração foi parcialmente aplicada, executada fora do Alembic ou marcada incorretamente.

Branches longas podem criar duas revisões com o mesmo down_revision. Ao juntar o código, haverá múltiplas heads. Isso não significa escolher um arquivo e apagar o outro. Crie uma revisão de merge:

alembic heads
alembic merge heads -m "une branches de migracao"

Revise o grafo resultante e teste a sequência a partir do ancestral comum. Em equipes com mudanças frequentes de esquema, integrar cedo reduz conflitos e migrações que pressupõem estados incompatíveis.

Configurar a conexão sem versionar segredos

O exemplo padrão coloca sqlalchemy.url em alembic.ini, mas uma aplicação real geralmente obtém a URL de configuração segura. env.py pode ler a mesma fonte usada pela aplicação e passá-la à configuração. Evite imprimir a URL completa em logs, pois ela pode conter usuário e senha.

Também é essencial importar todos os modelos antes de definir target_metadata. Se um módulo de modelo nunca é carregado, sua tabela pode não aparecer na metadata e o autogenerate concluirá que ela deve ser removida ou ignorada. Centralize o registro dos modelos e confira a migração produzida.

Projetos com mais de uma metadata ou banco precisam de uma estratégia explícita. Não improvise um único comando que conecta aleatoriamente conforme variáveis locais. A documentação da API de autogeração descreve comparações e múltiplas coleções, mas a topologia de deploy continua sendo uma decisão do projeto.

Escrever uma revisão manual

Nem toda mudança vem de uma comparação de modelos. Para criar uma revisão vazia:

alembic revision -m "normaliza status de pedidos"

Uma migração típica usa operações do Alembic:

from alembic import op
import sqlalchemy as sa

def upgrade() -> None:
    op.add_column(
        "pedido",
        sa.Column("status", sa.String(length=20), nullable=True),
    )

def downgrade() -> None:
    op.drop_column("pedido", "status")

O exemplo é estrutural e ainda não torna a coluna obrigatória. Em tabela com dados, uma implantação compatível pode adicionar a coluna anulável, publicar código que escreva o novo campo, preencher registros antigos e somente depois aplicar nullable=False. Essa estratégia expandir, migrar e contrair reduz a necessidade de coordenar uma alteração incompatível em um único instante.

Para preencher dados, considere volume e duração. Um UPDATE único pode manter locks e gerar transações enormes. Em bases grandes, faça lotes observáveis por um processo controlado e só avance à restrição quando uma consulta comprovar que não restam nulos. A revisão estrutural e o backfill podem precisar de mecanismos diferentes.

Defaults de Python e defaults do servidor

default= em um Column do SQLAlchemy normalmente representa um default aplicado pelo código, enquanto server_default= gera comportamento no banco. Uma migração executada diretamente pelo banco não usa automaticamente uma função Python do modelo.

Ao adicionar uma coluna obrigatória, um default temporário no servidor pode ajudar os registros existentes, mas decida se ele deve permanecer. Removê-lo depois preserva a obrigação de a aplicação fornecer o valor; mantê-lo cria um contrato permanente no banco. Revise o SQL para confirmar aspas, tipos e funções específicas do dialeto.

O mesmo cuidado vale para enums, timestamps e expressões SQL. PostgreSQL, MySQL e SQLite não implementam todas as alterações da mesma forma. Testar apenas com SQLite em memória não valida uma migração destinada a PostgreSQL.

Inspecionar SQL e trabalhar offline

Antes de produção, você pode gerar SQL sem executar a migração:

alembic upgrade head --sql

O modo offline é útil para revisão ou para ambientes em que uma equipe de banco executa scripts aprovados. Verifique se operações escritas em Python conseguem ser renderizadas sem consultar dados. Migrações dependentes do estado atual podem não funcionar corretamente offline.

Não considere o SQL gerado como universal. Gere-o para o dialeto de destino, examine locks esperados e teste em uma cópia representativa. O plano também precisa indicar quanto tempo a aplicação tolera versões antiga e nova do esquema simultaneamente.

Downgrade, rollback e recuperação

downgrade() descreve uma transformação de esquema, não uma máquina do tempo. Se upgrade() remove uma coluna, o downgrade pode recriá-la vazia, mas não recuperar conteúdo. Se o novo código já escreveu dados em um formato diferente, voltar o binário pode não ser compatível.

Para mudanças destrutivas, defina antes do deploy: backup verificável, critério de interrupção, maneira de restaurar dados e compatibilidade da versão anterior da aplicação. Em muitos incidentes, concluir uma correção para frente é mais seguro que executar um downgrade destrutivo. Essa escolha deve ser planejada, não tomada automaticamente porque a função existe.

Nunca use alembic stamp como atalho para aplicar mudanças. O comando apenas marca uma revisão como presente, sem executar suas operações. Ele é apropriado quando o esquema já foi criado ou verificado por outro processo e a equipe precisa alinhar o histórico. Compare o esquema antes e documente a razão.

Testar migrações de verdade

Há dois testes complementares. O primeiro cria um banco vazio e executa alembic upgrade head, garantindo que todo o histórico continua aplicável. O segundo parte de uma cópia ou fixture da versão anterior, insere dados representativos, executa a nova revisão e valida estrutura e conteúdo.

Quando o downgrade é suportado, teste ao menos upgrade, downgrade e novo upgrade em ambiente descartável. Isso encontra funções incompletas e nomes de constraints difíceis de reconstruir. Não prometa reversibilidade para operações cujo conteúdo não pode ser restaurado.

O teste de aplicação deve iniciar somente depois das migrações. Em deploys com várias réplicas, escolha um job único para migrar e aguarde sua conclusão antes de liberar código que dependa do novo esquema. Executar Alembic no startup de cada worker cria concorrência e torna falhas mais difíceis de controlar.

Checklist para revisão e deploy

Confirme que a revisão aponta para o down_revision correto, contém apenas uma mudança lógica e não inclui remoções inesperadas. Revise nulabilidade, defaults, índices, chaves estrangeiras, nomes de constraints e comportamento do dialeto. Meça backfills e alterações que travam tabela.

Depois, valide upgrade head desde um banco vazio e desde a versão em produção, confirme o estado com alembic current, execute testes e registre backup e rollback. A migração deve entrar no mesmo commit ou pull request do código que a utiliza, na ordem compatível com o deploy.

Alembic torna o histórico executável, mas segurança vem do processo ao redor: mudanças compatíveis, revisão do arquivo, dados representativos e execução única e observável. Tratar a revisão como código de produção é o que transforma um comando conveniente em uma estratégia confiável de evolução do banco.