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.