Alembic è lo strumento di riferimento per gestire in modo smart versionamento e migrazioni di database in Python. Alembic è una soluzione standalone indipendente dal framework, un tool perfetto per progetti di qualsiasi natura (che usano SQLAlchemy).
Alembic non è perfetto in qualsiasi contesto. Per esempio Django ha già un sistema di migrazioni integrato, strettamente accoppiato alla sua ORM e ai suoi comandi (makemigrations per generare i file e migrate per applicarli). Alembic ha senso quando lo strato dati è SQLAlchemy-centric (FastAPI/Flask/CLI/ETL con SQLAlchemy, SQLModel, ecc.), mentre perde senso quando l’ORM e il framework portano già un sistema di migrazioni ufficiale e usato dall’ecosistema
Cosa fa Alembic
In sostanza con Alembic posso tracciare facilmente le modifiche dello schema DB nel tempo.
Con Alembic posso:
- Generare migrazioni automaticamente dal codice dei modelli
- Avanzare e fare rollback delle modifiche al database in modo controllato
- Tracciare la storia di tutte le trasformazioni dello schema
- Gestire branch di migrazioni complesse in progetti multi-team
Permette quindi di versionare il database esattamente come versioniamo il codice con Git.
Installazione e Inizializzazione
In questa guida uso uv come gestore di pacchetti e come runner dei comandi (ad esempio uv run …), perché rende più semplice mantenere ambiente e dipendenze allineati al progetto. I passaggi sono concettualmente identici con qualunque altro package manager in Python: basta sostituire i comandi di installazione/sync e il prefisso di esecuzione (es. uv run) con l’equivalente nel tuo stack (virtualenv + pip, Poetry, PDM, ecc.), lasciando invariati i comandi di Alembic e la logica del workflow.
Il primo passo è installare Alembic nel progetto. Con uv, il moderno package manager Python, il comando è:
uv add alembic
Questo aggiungerà Alembic alle dipendenze del progetto (nel file pyproject.toml).
Quindi, si comincia inizializzando Alembic:
uv run alembic init migrations
Questo crea una cartella migrations/ con la seguente struttura standard:
migrations/
├── alembic.ini # Configurazione principale
├── env.py # Ambiente di esecuzione
├── script.py.mako # Template per le migrazioni
└── versions/ # Cartella con i file di migrazione
Configurazione di Alembic con PostgreSQL
Nel file alembic.ini, occorre configurare la connessione al database. La variabile più importante è sqlalchemy.url. Nel workflow si può configurare attraverso l’uso di variabili d’ambiente:
sqlalchemy.url = driver://user:password@localhost/dbname
È anche possibile configurare il tutto attraverso il file env.py:
import os
from sqlalchemy import engine_from_config
from sqlalchemy import pool
# Leggi da variabili d'ambiente
db_host = os.getenv("POSTGRES_HOST", "localhost")
db_port = os.getenv("POSTGRES_PORT", "5432")
db_name = os.getenv("POSTGRES_DB", "mydb")
db_user = os.getenv("POSTGRES_USER", "postgres")
db_password = os.getenv("POSTGRES_PASSWORD", "")
config.set_main_option(
"sqlalchemy.url",
f"postgresql://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}"
)
Comandi Principali
Ecco i comandi Alembic più comuni.
1. Controllare lo stato attuale
uv run alembic current
Mostra quale migrazione è attualmente applicata al database. Utile per capire dove ci si trova nel flusso di migrazioni.
uv run alembic heads
Mostra il l’head della sequenza di migrazioni (e gli altri head se si gestiscono branch).
Di solito si dovrebbe avere un solo head (il punto più recente).
2. Verificare le modifiche non migrate
uv run alembic check
Confronta il modello SQLModel/SQLAlchemy con lo schema del database.
Se le colonne o le tabelle nel codice non corrispondono al database, Alembic lo segnala.
Questo è fondamentale prima di generare una nuova migrazione.
Se non si sono impostate le variabili di ambiente è possibile lanciare i comandi includendole all’inizio: Per esempio:
POSTGRES_HOST=localhost POSTGRES_PORT=5433 POSTGRES_DB=miodb uv run alembic check
3. Generare una migrazione automatica
uv run alembic revision --autogenerate -m "Descrizione della modifica"
Questo è il comando che si finisce per utilizzare più frequentemente. È il corrispettivo del commit Git. Quando lanciato Alembic analizza i modelli e il database, e genera automaticamente un file di migrazione.
Ad esempio:
POSTGRES_HOST=localhost POSTGRES_PORT=5433 POSTGRES_DB=miodb \
uv run alembic revision --autogenerate -m "Add vel field on table measures"
La migrazione viene salvata in migrations/versions/ con un nome come abcd1234ef56_add_vel_field_on_table_measures.py.
Un file di migrazione Alembic (per intenderci, quello appena generato in migrations/versions/…py) è uno script Python “versionato” che descrive cosa cambia nello schema e come tornare indietro. In genere contiene:
- Metadati di revisione:
revision(ID univoco della migration),down_revision(ID della migration precedente), più eventualibranch_labelsedepends_on, che permettono ad Alembic di costruire il grafo delle dipendenze tra migrazioni. - Import e contesto: import di
alembic.op(l’helper per emettere operazioni DDL) e disqlalchemy as saper tipi/colonne/vincoli; c’è anche un docstring con descrizione e timestamp. - Due funzioni chiave:
upgrade(): applica la modifica (es.op.add_column,op.drop_column,op.create_table,op.alter_column, ecc.).downgrade(): definisce l’operazione inversa per fare rollback (idealmente il mirror dell’upgrade).
- Operazioni generate: nel caso di
--autogenerateci si troverà di mezzo anche blocchi commentati tipo# ### commands auto generated by Alembic ###che indicano le istruzioni create automaticamente, da rivedere/raffinare prima di applicarle.
4. Applicare le migrazioni
uv run alembic upgrade head
Applica tutte le migrazioni in pending fino all’ultima. Questo aggiorna il database alla versione più recente del codice.
Se si vuole applicare solo una specifica migrazione:
uv run alembic upgrade <revision_id>
5. Rollback di una migrazione
OK, supponiamo di aver cambiato idea e di voler tornare indietro di n passaggi:
uv run alembic downgrade -1
Torna indietro di una migrazione.
Volendo si può anche indicare un id specifico:
uv run alembic downgrade <revision_id>
Questo è un aspetto notevole da usare in produzione perché permette di fare rollback senza perdere dati (se la migrazione è stata scritta correttamente prevedendo le logiche di gestione dei dati nella stessa funzione di rollback).
Gestire Migrazioni
Nella mia piccola esperienza mi sono trovato a gestire e affrontare migrazioni non troppo complesse: rinominare colonne, aggiungerlo o rimuoverle, gestire chiavi esterne, etc.
Ecco alcune situazioni che ho affrontato e incontrato che credo siano da ricordare:
- Rinominare campi con preservazione dati: Quando Alembic genera una migrazione per rinominare una colonna, di solito lo fa correttamente con
ALTER TABLE ... RENAME COLUMN. Però, prima di applicare la migrazione, è bene verificare che i dati siano compatibili.
- Se si aggiunge una colonna
NOT NULLsu una tabella popolata, ci sono due strade:
1) Aggiungere la colonna nullable, popolare i valori per le righe esistenti, poi renderlaNOT NULL.
2) Aggiungere la colonna NOT NULL con undefault, così il DB può assegnare un valore alle righe esistenti; poi, se non si vuole mantenere il default assegnato a livello DB per le nuove insert, si può rimuoverlo con unalter_column(..., server_default=None).
Esempio opzione 2, con rimozione del default (approccio preferibile quando si vuole evitare un update massivo manuale e basta un valore default per le righe già esistenti.):
op.add_column(
"my_table",
sa.Column("new_column", sa.String(), nullable=False, server_default="default_value"),
)
op.alter_column("my_table", "new_column", server_default=None)
- Gestire chiavi esterne: Quando si aggiunge o modifica foreign key, assicurarsi che i dati nel database rispettino il vincolo. Alembic potrebbe fallire se i dati sono inconsistenti.
- Errore: “Target database is not up to date”. Dopo l’errore provare a lanciare:
uv run alembic current
uv run alembic heads
Banalmente se il current è diverso da heads, significa che il database è dietro rispetto alle migrazioni disponibili. Occorre applicare le migrazioni pending:
uv run alembic upgrade head
- Errore: “Multiple heads detected”
Questo accade quando due branch di sviluppo hanno creato migrazioni divergenti. Occorre risolvere il conflitto manualmente facendo merge delle migrazioni o creando una nuova migrazione che dipende da entrambi gli head.
- Perché si verifica:
Due o più sviluppatori hanno creato migrazioni partendo dallo stesso punto, oppure si è passato tra rami git senza sincronizzare le migrazioni.
Questo genera più head nel repository delle migrazioni e Alembic non può generare una nuova revisione finché non si risolve la divergenza. - Come risolvere:
Identificare gli heads divergenti: eseguire comandi comealembic headsoalembic historyper vedere gli heads attuali. - Risolvere manualmente la divergenza:
- Opzione 1: merge gli heads esistenti, creando una migrazione di merge che dipenda da entrambe le teste (
merge head) e poi continuare a generare nuove revisioni. - Opzione 2: ricollegare le revisioni duplicando o ri-lavorando i file di migrazione per creare una sequenza lineare, aggiornando i campi down_revision di una o più migrazioni per puntare alla testa corretta (soluzione comune, ma va fatta con cautela per le modifiche effettive al DB).
- Opzione 1: merge gli heads esistenti, creando una migrazione di merge che dipenda da entrambe le teste (
- Dopo il merge o la correzione, eseguire alembic upgrade head per applicare le migrazioni corrette al database.
- Buone pratiche per prevenire:
- Pull prima di creare nuove migrazioni, per allineare i rami e ridurre divergenze.
- Coordinare le modifiche allo schema tra sviluppatori, soprattutto su tabelle comuni.
- Evitare branch lunghi e confusi: integrare regolarmente main into feature branches e completare una migrazione prima di iniziare la successiva.
- Errore: “No changes detected”
Se si genera una migrazione… ma poi Alembic dice che non ci sono cambiamenti.
Controllare che:
- I modelli SQLModel siano importati correttamente in
env.py - Che
target_metadatapunti ai modelli del progetto - Che il database sia realmente diverso dai modelli
Immagine copertina: Iconographie descriptive des cactées, ou, Essais systématiques et raisonnés sur l’histoire naturelle, la classification et la culture des plantes de cette famille