#!/usr/bin/env python3 """ Script de gestion des migrations de base de données VWB Usage: python manage.py init # Initialiser les migrations (une seule fois) python manage.py migrate # Créer une nouvelle migration python manage.py upgrade # Appliquer les migrations pendantes python manage.py downgrade # Annuler la dernière migration python manage.py stamp # Marquer une révision comme appliquée python manage.py current # Afficher la révision actuelle python manage.py history # Afficher l'historique des migrations python manage.py backup # Sauvegarder la base de données python manage.py status # Afficher le status complet Exemple de workflow de migration: 1. Modifier les modèles dans db/models.py 2. python manage.py migrate "Description du changement" 3. Vérifier le fichier de migration généré dans migrations/versions/ 4. python manage.py upgrade """ import sys import os import shutil import subprocess from datetime import datetime # Chemin vers le backend BACKEND_DIR = os.path.dirname(os.path.abspath(__file__)) VENV_PYTHON = '/home/dom/ai/rpa_vision_v3/venv_v3/bin/python' VENV_FLASK = '/home/dom/ai/rpa_vision_v3/venv_v3/bin/flask' # Variables d'environnement pour Flask ENV = os.environ.copy() ENV['FLASK_APP'] = 'app.py' def run_flask_db(args): """Exécute une commande flask db""" cmd = [VENV_FLASK, 'db'] + args result = subprocess.run(cmd, cwd=BACKEND_DIR, env=ENV, capture_output=True, text=True) # Filtrer les warnings connus output = result.stdout + result.stderr for line in output.split('\n'): if line.strip() and not any(skip in line for skip in [ 'FutureWarning', 'pynvml', 'use_fast', 'Workflow ignoré', 'ScreenState non disponible', 'Server initialized' ]): print(line) return result.returncode == 0 def find_database(): """Trouve le fichier de base de données SQLite""" instance_dir = os.path.join(BACKEND_DIR, 'instance') if os.path.exists(instance_dir): for f in os.listdir(instance_dir): if f.endswith('.db') and not f.startswith('.'): return os.path.join(instance_dir, f) return None def backup_database(): """Sauvegarde la base de données avant une migration""" instance_dir = os.path.join(BACKEND_DIR, 'instance') db_path = find_database() if db_path and os.path.exists(db_path): db_name = os.path.basename(db_path).replace('.db', '') timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') backup_dir = os.path.join(instance_dir, 'backups') os.makedirs(backup_dir, exist_ok=True) backup_path = os.path.join(backup_dir, f'{db_name}_{timestamp}.db') shutil.copy2(db_path, backup_path) print(f"✅ Base de données sauvegardée: {backup_path}") # Garder seulement les 10 dernières sauvegardes backups = sorted([f for f in os.listdir(backup_dir) if f.endswith('.db')]) if len(backups) > 10: for old_backup in backups[:-10]: os.remove(os.path.join(backup_dir, old_backup)) print(f" 🗑️ Ancienne sauvegarde supprimée: {old_backup}") return backup_path else: print("⚠️ Pas de base de données existante à sauvegarder") return None def show_status(): """Affiche le status complet des migrations""" migrations_dir = os.path.join(BACKEND_DIR, 'migrations') instance_dir = os.path.join(BACKEND_DIR, 'instance') db_path = find_database() print("=" * 50) print("Status des Migrations VWB") print("=" * 50) print(f"Dossier backend: {BACKEND_DIR}") print(f"Dossier migrations: {'✅ Existe' if os.path.exists(migrations_dir) else '❌ Non initialisé'}") print(f"Base de données: {'✅ ' + db_path if db_path else '❌ Non créée'}") if os.path.exists(migrations_dir): versions_dir = os.path.join(migrations_dir, 'versions') if os.path.exists(versions_dir): migrations = sorted([f for f in os.listdir(versions_dir) if f.endswith('.py') and not f.startswith('__')]) print(f"\nMigrations disponibles ({len(migrations)}):") for m in migrations: print(f" 📄 {m}") # Backups backup_dir = os.path.join(instance_dir, 'backups') if os.path.exists(backup_dir): backups = sorted([f for f in os.listdir(backup_dir) if f.endswith('.db')]) if backups: print(f"\nSauvegardes ({len(backups)}):") for b in backups[-5:]: # Afficher les 5 dernières print(f" 💾 {b}") print("\nRévision actuelle de la base:") run_flask_db(['current']) def main(): if len(sys.argv) < 2: print(__doc__) sys.exit(1) command = sys.argv[1].lower() if command == 'init': print("🔧 Initialisation des migrations...") if run_flask_db(['init']): print("✅ Migrations initialisées") print(" Prochaine étape: python manage.py migrate 'Initial migration'") else: print("❌ Échec de l'initialisation") elif command == 'migrate': message = ' '.join(sys.argv[2:]) if len(sys.argv) > 2 else f"Migration {datetime.now().strftime('%Y%m%d_%H%M%S')}" print(f"📝 Création de la migration: {message}") backup_database() if run_flask_db(['migrate', '-m', message]): print("✅ Migration créée") print(" Prochaine étape: python manage.py upgrade") else: print("❌ Échec de la création de migration") elif command == 'upgrade': revision = sys.argv[2] if len(sys.argv) > 2 else 'head' print(f"⬆️ Application des migrations jusqu'à: {revision}") backup_database() if run_flask_db(['upgrade', revision]): print("✅ Migrations appliquées") else: print("❌ Échec de l'upgrade") elif command == 'downgrade': revision = sys.argv[2] if len(sys.argv) > 2 else '-1' print(f"⬇️ Annulation des migrations: {revision}") backup_database() if run_flask_db(['downgrade', revision]): print("✅ Downgrade effectué") else: print("❌ Échec du downgrade") elif command == 'stamp': revision = sys.argv[2] if len(sys.argv) > 2 else 'head' print(f"🏷️ Marquage de la révision: {revision}") if run_flask_db(['stamp', revision]): print("✅ Révision marquée") else: print("❌ Échec du stamp") elif command == 'current': print("📍 Révision actuelle:") run_flask_db(['current']) elif command == 'history': print("📜 Historique des migrations:") run_flask_db(['history']) elif command == 'backup': backup_database() elif command == 'status': show_status() else: print(f"❌ Commande inconnue: {command}") print(__doc__) sys.exit(1) if __name__ == '__main__': main()