Aller au contenu principal

Migrations de Base de Données

Contexte​

Le projet utilise le client Supabase natif pour toutes les opérations de données — pas SQLAlchemy ORM. Alembic gère uniquement les migrations de schéma.

Cette approche hybride signifie :

  • Les modèles SQLAlchemy sont créés progressivement, uniquement pour les tables qu'on veut modifier
  • target_metadata = None dans alembic/env.py — pas de détection automatique des changements
  • L'autogenerate ne fonctionne que si un modèle SQLAlchemy existe pour la table modifiée

Workflow Complet : Comment faire une migration​

Étape 1 : Créer le modèle SQLAlchemy pour la table concernée​

L'autogenerate d'Alembic nécessite un modèle SQLAlchemy qui reflète la table existante.

Créer le modèle dans un fichier de modèles (ex: backend/app/models/schema.py) :

from sqlalchemy import Column, String, Boolean, DateTime
from sqlalchemy.dialects.postgresql import UUID
import uuid
from sqlalchemy.orm import declarative_base

Base = declarative_base()

class University(Base):
__tablename__ = 'universities'

id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
name = Column(String(255), nullable=False)
university_code = Column(String(50), unique=True, nullable=False)
short_name = Column(String(100))
is_active = Column(Boolean, default=True)
created_at = Column(DateTime(timezone=True), server_default="NOW()")
updated_at = Column(DateTime(timezone=True), server_default="NOW()")

Important : Ce modèle n'est nécessaire que pour la table modifiée, pas pour toutes les tables du projet.

Étape 2 : Générer la migration​

alembic revision --autogenerate -m "add_is_verified_to_universities"

Convention de nommage pour les descriptions :

  • add_<colonne>_to_<table> — ajout de colonne
  • create_<table> — création de table
  • drop_<table> — suppression de table
  • alter_<table>_constraints — modification de contraintes

Étape 3 : Vérifier le fichier généré (OBLIGATOIRE)​

Jamais appliquer une migration sans l'avoir lue.

Le fichier généré se trouve dans backend/alembic/versions/.

Vérifications à faire :

  • La table modifiée est bien la bonne (pas de table système Supabase)
  • Les colonnes ajoutées/modifiées correspondent à ce qui est prévu
  • Pas de suppression accidentelle de données
  • La fonction downgrade() est correcte (permettra le rollback)

Exemple de fichier généré :

"""add_is_verified_to_universities

Revision ID: abc123
Revises: 61e524cfcce2
Create Date: 2026-02-21 10:30:00.000000

"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa

revision: str = 'abc123'
down_revision: Union[str, Sequence[str], None] = '61e524cfcce2'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None

def upgrade() -> None:
op.add_column('universities', sa.column('is_verified', sa.Boolean(), server_default='false'))

def downgrade() -> None:
op.drop_column('universities', 'is_verified')

Étape 4 : Appliquer la migration​

alembic upgrade head

Vérifier que la migration s'est appliquée :

alembic current

Étape 5 : Commiter​

Le fichier de migration doit être commité avec le code qui l'utilise dans la même PR. Pas de migration orpheline.


Commandes de Référence​

CommandeDescription
alembic currentAffiche la migration actuellement appliquée
alembic historyAffiche l'historique complet des migrations
alembic upgrade headApplique toutes les migrations en attente
alembic downgrade -1Annule la dernière migration appliquée
alembic revision --autogenerate -m "description"Génère une migration automatique
alembic revision -m "description"Crée une migration vide (manuelle)
alembic stamp headMarque une migration comme appliquée sans l'exécuter

Règles Absolues​

  1. Jamais de modification directe du schéma Supabase sans passer par Alembic

    • Pas de console Supabase pour ajouter des colonnes en prod
    • Toutes les modifications passent par migration
  2. Toujours lire le fichier généré avant d'appliquer

    • L'autogenerate peut faire des erreurs
    • Vérifier le SQL généré manuellement
  3. Le fichier de migration = commité dans la même PR que le code

    • Pas de migration séparément du code qui l'utilise
    • reviewers doivent voir migration + code ensemble
  4. Ne JAMAIS modifier un fichier de migration déjà appliqué en prod

    • Si erreur, créer une nouvelle migration de correction
    • Modifier une migration existante = désynchronisation garantie

Exemple Concret : Migration Manuelle​

Quand on n'a pas de modèle SQLAlchemy, on écrit la migration à la main.

Création​

alembic revision -m "add_country_column_to_companies"

Écrire le SQL dans le fichier généré​

"""add_country_column_to_companies

Revision ID: def456
Revises: abc123
Create Date: 2026-02-21 11:00:00.000000

"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa

revision: str = 'def456'
down_revision: Union[str, Sequence[str], None] = 'abc123'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None

def upgrade() -> None:
op.add_column('companies', sa.column('country', sa.String(100), server_default='Benin'))

def downgrade() -> None:
op.drop_column('companies', 'country')

Appliquer​

alembic upgrade head

En Cas de Problème​

Voir ce qui s'est mal passé​

# État actuel vs attendu
alembic current
alembic history

# Voir le détail d'une migration
alembic show <revision_id>

Rollback​

# Annuler la dernière migration
alembic downgrade -1

# Annuler plusieurs migrations
alembic downgrade -3

Désynchronisation avec prod​

Si alembic current ne correspond pas à la prod :

  1. Vérifier l'état de prod : SELECT * FROM alembic_version;
  2. Si la migration est déjà appliquée en prod mais pas en local :
    alembic stamp <revision_id>
  3. Si la migration n'est pas appliquée en prod mais local est ahead :
    alembic downgrade <revision_id>

Attention : Ces commandes modifient le state Alembic. Utiliser avec précaution et toujours vérifier le résultat.