Outils pour utilisateurs

Outils du site


dev:python:flask:extensions:flask_migrate

Différences

Ci-dessous, les différences entre deux révisions de la page.

Lien vers cette vue comparative

Les deux révisions précédentesRévision précédente
dev:python:flask:extensions:flask_migrate [2025/09/10 13:07] – supprimée - modification externe (Date inconnue) 127.0.0.1dev:python:flask:extensions:flask_migrate [2025/09/10 13:07] (Version actuelle) – ↷ Page déplacée de dev:python:flask:flask_migrate à dev:python:flask:extensions:flask_migrate yoann
Ligne 1: Ligne 1:
 +{{tag>dev python flask extension db migrate migration}}
  
 +
 +====== Flask : Gérer les migrations avec l'extension Flask-Migrate ======
 +
 +
 +===== Présentation =====
 +
 +Flask-Migrate s'appuie sur Alembic pour gérer les migrations de la base de données gérée par l'ORM (SQLAlchemy). Ainsi le modèle de données définit dans l'application Flask peut être amendé/corrigé, c'est l'extension Flask-Migrate qui se chargera de créer les script de migrations capables de modifier le schéma de la base de données préexistante pour que le nouveau modèle de données soit exploitable sans perte de données.
 +
 +
 +Pour illustrer le fonctionnement de l'extension **Flask-Migrate** nous allons utiliser une application Flask minimale en configuration monolithique avec une classe de modèle simple : ''Product''.
 + 
 +
 +===== Installation =====
 +
 +Création d'un environnement virtuel Python pour le projet Flask et installation des packages via pip :
 +
 +<code bash>
 +#création d'un répertoire dédié à l'application
 +mkdir tuto-migrate && cd tuto-migrate
 +
 +# création /activation de l'environnement virtuel Python
 +python3 -m venv .venv
 +source .venv/bin/activate 
 +
 +# Installation de Flask et de l'extension Flask-Migrate
 +pip install Flask Flask-SQLAlchemy Flask-Migrate
 +</code>
 +
 +
 +===== Création de l'application Flask =====
 +
 +Création de l'application Flask dans le module Python ''app.py''
 +
 +<code python app.py>
 +from flask import Flask
 +from flask_sqlalchemy import SQLAlchemy
 +from flask_migrate import Migrate
 +
 +app = Flask(__name__)
 +app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///database.db"
 +db = SQLAlchemy(app)
 +migrate = Migrate(app, db)
 +
 +
 +class Product(db.Model):
 +    __tablename__ = "products"
 +    id = db.Column(db.Integer, primary_key=True)
 +    name = db.Column(db.String(50))
 +
 +    def __repr__(self):
 +        return f"<Product id={self.id} name='{self.name}'>"
 +
 +
 +@app.route("/")
 +def index():
 +    return "<h1>Hello, World!</h1>"
 +</code>
 +
 +Le module Python app.py définit la classe ''Product'' avec une clé primaire ''id'' et un champ ''name''.
 +
 +Pour tester rapidement le code :
 +
 +<code bash>
 +# Afficher les routes existantes
 +flask routes
 +
 +# Pour tester la création d'une instance de Product
 +flask shell
 +</code>
 +
 +<code python>
 +>>> mandarine = Product(name='mandarine')
 +>>> mandarine
 +<Product id=None name='mandarine'>
 +>>> quit()
 +</code>
 +
 +===== Initialisation de Flask-Migrate =====
 +
 +Les commandes proposées par l'extension Flask-Migrate sont regroupées sous ''flask db''. On commence par initialiser l’environnement nécessaire à l'extension puis on génère le premier script de migration :
 +
 +<code bash>
 +# Création de l'environnement nécessaire à Flask-Migrate
 +flask db init
 +
 +# Création du premier script
 +flask db migrate -m "Initial DB Model Product"
 +</code>
 +
 +La commande retourne quelques messages de la forme :
 +<file>
 +INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
 +INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
 +INFO  [alembic.autogenerate.compare] Detected added table 'products'
 +  Generating /home/username/tuto-migrate/migrations/versions/a09681708867_initial_db_model_product.py ...  done
 +</file>
 +
 +L'outil analyse les modifications apportées au modèle de données et crée un script de migration. Ce script est créé dans le sous dossier ''./migrations/versions'' du projet.
 +
 +<note>
 +Le dossier ''./migrations'' doit être ajouté à votre outil de gestion de révision comme les autres sources de votre projet.
 +</note>
 +
 +A ce stade, le script existe mais n'a pas été appliqué. Il est intéressant de le relire :
 +
 +<code bash>
 +less migrations/versions/a09681708867_initial_db_model_product.py
 +</code>
 +
 +On peut y voir deux fonctions : ''upgrade()'' et ''downgrade()''.
 +
 +<note warning>
 +Les scripts de migration sont générés automatiquement et peuvent comporter des erreurs ou des imperfections en fonction de la complexité et de la nature des changements apportés au modèle de données. Il est donc recommandé de vérifier le code généré.
 +</note>
 +
 +Si on affiche le contenu de la base on peut vérifier qu'elle est encore vide pour le moment :
 +<code bash>
 +sqlite3 instance/database.db .tables
 +alembic_version
 +
 +# Une seule table existe nommée alembic_version, elle est vide pour le moment
 +sqlite3 instance/database.db 'SELECT * FROM alembic_version;'
 +</code>
 +
 +Pour appliquer les modifications décrites dans le script de migration à la base de données du projet :
 +
 +<code bash>
 +flask db upgrade
 +</code>
 +
 +La commande retourne quelques messages du type :
 +<file>
 +INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
 +INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
 +INFO  [alembic.runtime.migration] Running upgrade  -> a09681708867, Initial DB Model Product
 +</file>
 +
 +Si on inspecte à nouveau la base, on peut voir que la table ''products'' existe à présent et qu'une entrée existe dans la table alembic_version :
 +
 +<code bash>
 +# Lister les tables existantes
 +sqlite3 instance/database.db .tables
 +alembic_version  products
 +
 +# Schéma des tables
 +sqlite3 instance/database.db '.schema'
 +CREATE TABLE alembic_version (
 + version_num VARCHAR(32) NOT NULL, 
 + CONSTRAINT alembic_version_pkc PRIMARY KEY (version_num)
 +);
 +CREATE TABLE products (
 + id INTEGER NOT NULL, 
 + name VARCHAR(50), 
 + PRIMARY KEY (id)
 +);
 +
 +# Afficher le contenu de la table alembic_version
 +sqlite3 instance/database.db 'SELECT * FROM alembic_version;'
 +a09681708867
 +</code>
 +
 +<note>
 +Le premier script de migration équivaut à l'exécution de la fonction ''db.create_all()'' depuis ''flask shell''.
 +</note>
 +
 +<note warning>
 +Lorsqu'on ajoute l'extension Flask-Migrate sur un projet préexistant, la commande ''flask db upgrade'' retourne une erreur car la base existe déjà. Dans ce cas il faut utiliser la commande ''flask db stamp'' pour marquer la base comme déjà migrée.
 +</note>
 +
 +A chaque modification du modèle de données, il faudra répéter les opérations ''migrate''(alias de ''revision'') et ''upgrade''.
 +
 +
 +===== Peuplement de la base de données =====
 +
 +Pour illustrer le bon fonctionnement de Flask-Migrate nous allons :
 +  - Introduire des données dans la base ;
 +  - Modifier le modèle de données ;
 +  - Créer et appliquer une migration afin de vérifier que la base existante peut utiliser notre nouveau modèle de données.
 +
 +Pour ajouter des données dans la base, on utilise ''flask shell'' :
 +<code python>
 +>>> apple = Product(name="Apple")
 +>>> orange = Product(name="Orange")
 +>>> banana = Product(name="Banana")
 +>>> db.session.add_all([apple, orange, banana])
 +>>> db.session.commit()
 +>>>
 +>>> # Supprime les instances et affiche les valeurs présentes dans la base
 +>>> del apple,orange,banana
 +>>> Product.query.all()
 +[<Product id=1 name='Apple'>, <Product id=2 name='Orange'>, <Product id=3 name='Banana'>]
 +>>> quit()
 +</code>
 +
 +===== Modification du modèle de données =====
 +
 +
 +On modifie la classe ''Product'' en y ajoutant un attribut ''price''
 +
 +<code python app.py>
 +
 +</code>
 +
 +On regénère un script de migration :
 +
 +<code bash>
 +flask db migrate -m "Ajout attribut price"
 +</code>
 +
 +La commande retourne des messages indiquant que des modifications ont été identifiées et qu'un nouveau script est produit :
 +<file>
 +INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
 +INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
 +INFO  [alembic.autogenerate.compare] Detected added column 'products.price'
 +  Generating /home/username/tuto-migrate/migrations/versions/7d38ed81182c_ajout_attribut_price.py ...  done
 +</file>
 +
 +
 +===== Mise à niveau de la base de données =====
 +
 +On peut consulter ce nouveau script puis l'exécuter :
 +<code bash>
 +less migrations/versions/7d38ed81182c_ajout_attribut_price.py 
 +flask db upgrade
 +</code>
 +
 +Pour vérifier que le nouveau modèle s'applique sans retourner d'erreurs, on utilise ''flask shell'' :
 +
 +<code python>
 +>>> Product.query.all()
 +[<Product id=1 name='Apple' price=None>, <Product id=2 name='Orange' price=None>, <Product id=3 name='Banana' price=None>]
 +>>> quit()
 +</code>
 +
 +Même si aucune valeur n'est renseignée, l'attribut price existe bien.
 +
 +
 +===== Rétrogradation de la base de données =====
 +
 +Version actuelle de la base :
 +<code bash>
 +sqlite3 instance/database.db 'SELECT * FROM alembic_version;'
 +7d38ed81182c
 +
 +sqlite3 instance/database.db '.schema products'
 +CREATE TABLE products (
 + id INTEGER NOT NULL, 
 + name VARCHAR(50), price INTEGER, 
 + PRIMARY KEY (id)
 +);
 +</code>
 +
 +Pour rétrograder la base à la version précédente :
 +<code bash>
 +flask db dowgrade
 +</code>
 +
 +Etat de la base :
 +<code bash>
 +sqlite3 instance/database.db 'SELECT * FROM alembic_version;'
 +a09681708867
 +
 +sqlite3 instance/database.db '.schema products'
 +CREATE TABLE IF NOT EXISTS "products" (
 + id INTEGER NOT NULL, 
 + name VARCHAR(50), 
 + PRIMARY KEY (id)
 +);
 +
 +</code>
 +
 +On peut voir que la version a bien été modifiée et que la table ne comporte plus de colonne ''price''.
 +
 +Mais notre modèle de données n'est plus en adéquation avec le schéma de la base de données : son utilisation provoquera une erreur :
 +
 +<code python>
 +>>> # Depuis flask shell
 +>>> ananas = Product(name="Ananas")
 +>>> db.session.add(ananas)
 +>>>
 +>>> # La sauvegarde en base provoquera une erreur
 +>>> db.session.commit()
 +</code>
 +
 +Le commit retournera un ensemble de  messages de la forme :
 +
 +<file>
 +Traceback (most recent call last):
 +  File "/home/username/tuto-migrate/.venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1967, in _exec_single_context
 +    self.dialect.do_execute(
 +  File "/home/username/tuto-migrate/.venv/lib/python3.12/site-packages/sqlalchemy/engine/default.py", line 951, in do_execute
 +    cursor.execute(statement, parameters)
 +sqlite3.OperationalError: table products has no column named price
 +
 +...
 +</file>
 +
 +Il faudra retirer l'attribut ''price'' de la classe ''Product'' pour que les écritures en base puisse se faire normalement.
 +
 +
 +===== Renommer les commandes de l'extension =====
 +
 +Le groupe de commandes par défaut de Flask-Migrate est nommé "db". Il peut être renommé lors de l'instanciation de l'extension dans l'application Flask s'il ne convient pas au projet :
 +
 +<code python app.py>
 +migrate = Migrate(app, db, command='db-tools')
 +</code>
 +
 +Au sein du projet, on peut à présent appeler les commandes de Flask-Migrate :
 +<code bash>
 +flask db-tools show
 +flask db-tools --help
 +</code>
 +
 +
 +
 +===== Résumé =====
 +
 +On a créé une application Flask minimale et utilisé l'extension Flask-Migrate pour géré les migrations de la base de données après avoir modifier les classes de modèle de données.
 +
 +En général on peut suivre les étapes suivantes lors du developpement d'une application Flask :
 +  - Modifier les modèles ;
 +  - Générer un script de migration via la commande ''flask db migrate'' ;
 +  - Vérifier le script généré et le compléter si nécessaire ;
 +  - Appliquer les changements sur la base de données via la commande ''flask db upgrade'' ;
 +
 +La documentation complète de l'extension Flask-Migrate est fournie dans les références.
 +
 +
 +===== Références =====
 +
 +  * [[https://flask-migrate.readthedocs.io/en/latest/index.html|Documentation de l'extension Flask-Migrate (readthedocs.io)]]
 +  * [[https://alembic.sqlalchemy.org/en/latest/tutorial.html| Tutoriels d'introduction à Alembic (sqlalchemy.org)]]
 +  * [[https://www.digitalocean.com/community/tutorials/how-to-perform-flask-sqlalchemy-migrations-using-flask-migrate|Comment réaliser des migrations avec l'extension Flask-Migrate (digitalocean.com)]]