Cambiar modelos SQLAlchemy no actualiza con seguridad una base existente. Alembic registra el esquema como revisiones ordenadas para aplicar la misma secuencia en desarrollo, pruebas y producción.

Este contenido supone que conoces SQLAlchemy.

python -m pip install alembic
alembic init migrations

Versiona alembic.ini, migrations/env.py y migrations/versions. Mantén credenciales fuera del repositorio e importa la metadata en env.py:

from app.database import Base
target_metadata = Base.metadata

Consulta el tutorial oficial de Alembic.

Generar y revisar

alembic revision --autogenerate -m "añade estado al pedido"
alembic upgrade head
alembic current

Revisa nombres, tipos, defaults, nulabilidad, índices, upgrade() y downgrade(). Un cambio de nombre puede aparecer como eliminar y crear, con pérdida de datos.

Añadir una columna obligatoria a una tabla poblada suele requerir etapas: crearla opcional, completar datos y luego aplicar la restricción. Autogenerate es un asistente; su documentación oficial explica límites.

  • Crea una revisión por cambio lógico.
  • Prueba el SQL en una base descartable.
  • No reescribas revisiones ya desplegadas.
  • Ejecuta migraciones una vez por despliegue.
  • Valida upgrade head en CI.

La práctica esencial es revisar el impacto sobre datos antes de producción.

Comprender el grafo de revisiones

Cada archivo en migrations/versions tiene un identificador y normalmente apunta a una revisión anterior. Esos vínculos forman un grafo, no una lista ordenada por nombres. alembic heads muestra las puntas, alembic current indica la revisión registrada en una base y alembic history presenta los caminos.

Alembic guarda la posición en alembic_version. No edites esa tabla manualmente para ocultar un error. Una divergencia puede indicar una migración parcial, un cambio realizado fuera de Alembic o un stamp incorrecto. Averigua qué operaciones ocurrieron realmente antes de modificar el estado registrado.

Dos ramas paralelas pueden crear revisiones con el mismo down_revision. Después de unir el código habrá varias heads. No elimines una ni cambies su padre arbitrariamente:

alembic heads
alembic merge heads -m "une ramas de migracion"

Revisa la revisión de merge y prueba el grafo desde el ancestro común. Integrar pronto reduce migraciones que suponen estados incompatibles.

Configurar la conexión sin publicar secretos

El ejemplo inicial usa sqlalchemy.url en alembic.ini, pero una aplicación suele leer la URL desde configuración segura. env.py puede usar la misma fuente que la aplicación. Evita escribir la URL completa en logs porque puede contener credenciales.

Importa todos los modelos antes de asignar target_metadata. Si un módulo nunca se carga, su tabla no aparece en la metadata y autogenerate puede omitirla o proponer una eliminación inesperada. Centralizar el registro ayuda, pero no sustituye la revisión.

Los proyectos con varias colecciones de metadata o bases necesitan una estrategia documentada. La documentación de la API de autogenerate explica comparaciones y hooks; el orden del despliegue sigue siendo una decisión del proyecto.

Escribir una revisión manual

No toda operación nace de comparar modelos:

alembic revision -m "normaliza estado de pedidos"

Una revisión estructural pequeña podría incluir:

from alembic import op
import sqlalchemy as sa

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

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

La columna es opcional deliberadamente. En una tabla poblada, un despliegue compatible puede añadirla, publicar código que escriba la representación nueva, completar registros antiguos y aplicar nullable=False después. La estrategia expandir, migrar y contraer evita concentrar un cambio incompatible en un único momento.

Un backfill necesita planificación. Un UPDATE enorme puede mantener locks y una transacción costosa. En tablas grandes, usa lotes observables mediante un proceso controlado y verifica que no queden nulos antes de imponer la restricción. La revisión de esquema y la migración de datos pueden necesitar mecanismos distintos.

Diferenciar defaults de Python y del servidor

default= en SQLAlchemy suele representar un valor aportado por la aplicación. server_default= crea comportamiento en la base. Una migración ejecutada por el motor no llama automáticamente al default Python del modelo.

Un default temporal del servidor puede ayudar al añadir una columna obligatoria, pero decide si debe permanecer. Eliminarlo obliga a cada escritor a proporcionar el valor; conservarlo crea un contrato permanente. Revisa el SQL para confirmar comillas, tipos y funciones del dialecto.

Enums, timestamps y constraints también varían entre PostgreSQL, MySQL y SQLite. Probar solo con SQLite en memoria no valida una migración destinada a PostgreSQL.

Inspeccionar SQL y modo offline

Puedes generar SQL sin ejecutarlo:

alembic upgrade head --sql

El modo offline sirve cuando un equipo de base aplica scripts aprobados. Las operaciones que consultan datos o dependen de una conexión activa pueden no renderizarse correctamente.

El SQL generado es específico del dialecto. Prodúcelo para la base destino, revisa locks y prueba con volumen representativo. El plan debe definir durante cuánto tiempo pueden convivir versiones antigua y nueva de la aplicación.

Downgrade no equivale a recuperar

downgrade() transforma el esquema, pero no viaja en el tiempo. Recrear una columna eliminada no recupera sus datos. Si el código nuevo ya escribió otro formato, el binario anterior quizá no pueda interpretarlo.

Antes de una operación destructiva, define backup verificado, criterio para detener el despliegue, restauración y compatibilidad de la versión anterior. A veces una corrección hacia delante es más segura que un downgrade. Debe ser una decisión planificada.

alembic stamp marca una revisión sin ejecutar operaciones. Úsalo solo cuando el esquema fue creado o verificado por otro proceso y necesitas alinear el historial. Compara primero el esquema y documenta la razón.

Probar migraciones como código de producción

Realiza dos pruebas. Primero, crea una base vacía y ejecuta alembic upgrade head para demostrar que todo el historial sigue funcionando. Después, parte de la revisión anterior con datos representativos, aplica la nueva y valida estructura y contenido.

Si soportas downgrade, prueba upgrade, downgrade y otro upgrade en una base descartable. Esto encuentra operaciones inversas incompletas y nombres de constraints inestables. No prometas reversibilidad para datos que no pueden reconstruirse.

Las pruebas de aplicación deben comenzar después de migrar. Con varias réplicas, selecciona un único job y espera su finalización. Ejecutar Alembic al iniciar cada worker crea carreras.

Lista de revisión y despliegue

Confirma el down_revision, una sola modificación lógica y ausencia de eliminaciones inesperadas. Revisa nulabilidad, defaults, índices, claves foráneas, constraints y dialecto. Mide backfills y operaciones que bloquean tablas.

Prueba desde una base vacía y desde la revisión de producción, verifica con alembic current, ejecuta las pruebas y registra backup y recuperación. Entrega la revisión junto al código que la utiliza, en un orden compatible.

Alembic vuelve ejecutable el historial, pero la seguridad depende del proceso: fases compatibles, operaciones revisadas, datos representativos y un único ejecutor observable. Tratar las revisiones como código de producción convierte una herramienta cómoda en una práctica fiable.