Alembic com SQLAlchemy e FastAPI: migrações sem susto
Aprenda a configurar Alembic em projetos Python com SQLAlchemy e FastAPI para criar, revisar e aplicar migrações de banco com segurança.
Mudar tabela no banco de dados é uma das tarefas que mais separam um projeto de estudo de uma aplicação profissional. Enquanto o sistema está pequeno, parece aceitável apagar o arquivo SQLite, recriar a tabela ou rodar um CREATE TABLE manual. Em uma API usada por clientes, por um time interno ou por um teste técnico mais sério, esse improviso vira risco: dados somem, ambientes ficam diferentes e ninguém sabe exatamente qual alteração foi aplicada.
O Alembic é a ferramenta de migrações mais comum para projetos Python que usam SQLAlchemy. Ele registra mudanças no schema do banco em arquivos versionados, permite aplicar e desfazer alterações e ajuda a manter desenvolvimento, homologação e produção seguindo a mesma sequência. Para quem trabalha com FastAPI, Flask ou serviços backend em Python, dominar esse fluxo aumenta muito a confiança na hora de evoluir tabelas, índices e relacionamentos.
Neste guia, vamos configurar Alembic em uma API simples com SQLAlchemy 2.0, criar a primeira migração, revisar o arquivo gerado, aplicar no banco e organizar cuidados para produção. Se você ainda está montando a base da API, leia também criando API com FastAPI, SQLAlchemy 2.0 moderno e PostgreSQL com Python. Para transformar isso em portfólio, combine com testes com pytest e projetos de portfólio Python. Em times que também usam Go, vale comparar o mesmo problema com stacks de backend em Golang Brasil.
Quando usar Alembic
Use Alembic quando seu projeto tem banco relacional e o schema precisa evoluir sem perder dados. Alguns exemplos comuns:
- adicionar uma coluna
statusem pedidos existentes; - criar índice para melhorar uma busca frequente;
- renomear uma tabela sem quebrar a API;
- criar relacionamento entre usuário e organização;
- aplicar a mesma mudança em desenvolvimento, staging e produção;
- deixar claro no Git quando e por que o banco mudou.
Se o projeto é um script descartável, uma prova rápida ou um notebook temporário, talvez Alembic seja excesso. Mas se existe API, autenticação, dados de usuários, deploy contínuo ou mais de uma pessoa trabalhando, migrações deixam de ser luxo e viram parte básica da engenharia.
Estrutura do projeto
Comece com uma estrutura simples:
api-tarefas/
alembic.ini
pyproject.toml
app/
__init__.py
database.py
models.py
main.py
migrations/
env.py
script.py.mako
versions/
Instale as dependências:
python -m venv .venv
source .venv/bin/activate
pip install fastapi uvicorn sqlalchemy alembic psycopg[binary]
Para desenvolvimento local, você pode começar com SQLite. Para simular um cenário mais próximo do mercado brasileiro de backend, PostgreSQL é melhor porque aparece com frequência em vagas, testes técnicos e sistemas internos.
Modelo SQLAlchemy
Um modelo mínimo de tarefa pode ficar em app/models.py:
from datetime import datetime
from sqlalchemy import Boolean, DateTime, String
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class Base(DeclarativeBase):
pass
class Tarefa(Base):
__tablename__ = "tarefas"
id: Mapped[int] = mapped_column(primary_key=True)
titulo: Mapped[str] = mapped_column(String(160), nullable=False)
concluida: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
criada_em: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False)
O ponto mais importante para o Alembic é ter uma Base declarativa que concentra o metadata dos modelos. É esse metadata que a geração automática compara com o banco para descobrir tabelas, colunas e índices.
Em app/database.py, deixe a URL do banco centralizada:
import os
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql+psycopg://postgres:postgres@localhost:5432/app")
engine = create_engine(DATABASE_URL, pool_pre_ping=True)
SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False)
Em produção, não coloque senha real no código. Use variável de ambiente, secret manager ou configuração segura do provedor. O arquivo .env.example pode mostrar o formato sem expor credenciais.
Inicializando o Alembic
Rode o comando na raiz do projeto:
alembic init migrations
Isso cria alembic.ini e a pasta migrations/. Em seguida, ajuste migrations/env.py para importar os modelos e usar a URL do projeto:
from alembic import context
from sqlalchemy import engine_from_config, pool
from app.database import DATABASE_URL
from app.models import Base
config = context.config
config.set_main_option("sqlalchemy.url", DATABASE_URL)
target_metadata = Base.metadata
O arquivo completo gerado pelo Alembic tem funções para modo offline e online. Você não precisa reescrever tudo: normalmente basta importar Base, definir target_metadata e garantir que a connection string venha da mesma fonte usada pela aplicação.
Primeira migração
Com o modelo criado e o env.py configurado, gere a migração inicial:
alembic revision --autogenerate -m "cria tabela de tarefas"
O Alembic criará um arquivo em migrations/versions/, com nome parecido com:
20260604_1015_cria_tabela_de_tarefas.py
Abra o arquivo antes de aplicar. Uma migração inicial típica terá algo assim:
def upgrade() -> None:
op.create_table(
"tarefas",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("titulo", sa.String(length=160), nullable=False),
sa.Column("concluida", sa.Boolean(), nullable=False),
sa.Column("criada_em", sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint("id"),
)
def downgrade() -> None:
op.drop_table("tarefas")
Nunca trate --autogenerate como piloto automático. Ele ajuda muito, mas não entende intenção de negócio. Se você renomeia uma coluna, por exemplo, ele pode interpretar como “remove coluna antiga e cria coluna nova”, o que apagaria dados. Nesses casos, edite a migração manualmente com op.rename_column ou comandos equivalentes.
Aplicando no banco
Para aplicar todas as migrações pendentes:
alembic upgrade head
Para ver o histórico:
alembic history
Para ver a versão atual do banco:
alembic current
O Alembic cria uma tabela chamada alembic_version. Ela guarda qual revisão está aplicada naquele banco. Não apague essa tabela manualmente, porque ela é o controle de versão do schema.
Alterando o schema com segurança
Imagine que agora a tarefa precisa de prioridade. Você altera o modelo:
prioridade: Mapped[str] = mapped_column(String(20), default="media", nullable=False)
Depois gera nova migração:
alembic revision --autogenerate -m "adiciona prioridade em tarefas"
O arquivo gerado deve incluir algo como:
def upgrade() -> None:
op.add_column("tarefas", sa.Column("prioridade", sa.String(length=20), nullable=False))
def downgrade() -> None:
op.drop_column("tarefas", "prioridade")
Aqui existe uma armadilha: se a tabela já tem linhas em produção, adicionar uma coluna nullable=False sem valor padrão pode falhar. Uma alternativa segura é aplicar em etapas:
def upgrade() -> None:
op.add_column("tarefas", sa.Column("prioridade", sa.String(length=20), nullable=True))
op.execute("UPDATE tarefas SET prioridade = 'media' WHERE prioridade IS NULL")
op.alter_column("tarefas", "prioridade", nullable=False)
Esse cuidado mostra maturidade em teste técnico e no trabalho real. Você não está apenas “fazendo rodar”; está protegendo dados existentes.
Integração com FastAPI
O FastAPI não precisa criar tabelas automaticamente se você usa Alembic. Evite colocar Base.metadata.create_all(engine) no startup da aplicação em projetos versionados. Esse comando é útil em exemplos pequenos, mas em produção ele mistura responsabilidades e pula revisão de migração.
Um fluxo mais profissional é:
- modelos SQLAlchemy descrevem o schema desejado;
- Alembic registra mudanças versionadas;
- CI ou deploy roda
alembic upgrade headantes de subir a nova versão; - a aplicação assume que o banco já está na versão correta.
Se você usa Docker, pode ter um comando separado para migração:
docker compose run --rm api alembic upgrade head
docker compose up -d api
Em ambientes maiores, a migração pode ser um job de deploy. O importante é evitar que várias instâncias da API tentem migrar o banco ao mesmo tempo sem coordenação.
Boas práticas para produção
Antes de aplicar migrações em produção, revise alguns pontos:
- a migração foi lida manualmente por alguém;
- existe backup ou snapshot recente;
- mudanças destrutivas foram evitadas ou planejadas;
- índices grandes foram avaliados para não travar o banco em horário crítico;
- o deploy da aplicação é compatível com o schema antigo e novo, quando necessário;
- o downgrade existe, mesmo que o plano real seja corrigir para frente.
Nem todo rollback de banco é simples. Apagar uma coluna e depois tentar desfazer não recupera os dados perdidos. Por isso, em sistemas importantes, prefira migrações reversíveis, deploys em duas etapas e alterações compatíveis com a versão anterior da aplicação.
Erros comuns
Um erro frequente é esquecer de importar todos os modelos no env.py. Se o Alembic não conhece uma classe, ele não gera tabela para ela. Em projetos maiores, crie um módulo que importa todos os modelos ou garanta que o pacote de modelos seja carregado antes de definir target_metadata.
Outro erro é editar o banco manualmente e depois tentar “sincronizar” o Alembic. Isso cria divergência entre Git, ambientes e produção. Se uma correção manual foi inevitável, registre uma migração equivalente ou use comandos como alembic stamp apenas com entendimento claro do histórico.
Também evite nomes genéricos como fix, update ou migration. Uma mensagem como adiciona_indice_em_tarefas_status ajuda revisores, colegas e você mesmo daqui a três meses.
Checklist de portfólio
Para mostrar Alembic em um projeto público, inclua no README:
- comando para subir PostgreSQL local;
- exemplo de
DATABASE_URLsem senha real; - comando
alembic upgrade head; - explicação de onde ficam os modelos;
- pelo menos duas migrações no histórico;
- teste ou script que confirma que a API roda depois da migração.
Esse tipo de detalhe conversa diretamente com vagas backend, engenharia de dados leve, RevOps técnico e automação corporativa. Muitas pessoas sabem criar endpoint; menos pessoas demonstram cuidado com schema, dados existentes e deploy.
Conclusão
Alembic não é apenas mais uma dependência. Ele é o contrato entre o código Python e o banco relacional. Quando você versiona migrações, revisa arquivos gerados e aplica mudanças em uma ordem previsível, o projeto deixa de depender de memória, comandos manuais e sorte.
Para começar bem, configure target_metadata, gere a migração inicial, leia o arquivo antes de aplicar e trate mudanças em produção com cuidado. Depois, evolua para CI, Docker, testes e deploys em etapas. Esse fluxo torna sua API FastAPI mais confiável e também deixa seu portfólio mais próximo do que equipes profissionais esperam de uma pessoa desenvolvedora Python.