Files
orion/scripts/squash_migrations.py
Samir Boulahtit f20266167d
Some checks failed
CI / ruff (push) Failing after 7s
CI / pytest (push) Failing after 1s
CI / architecture (push) Failing after 9s
CI / dependency-scanning (push) Successful in 27s
CI / audit (push) Successful in 8s
CI / docs (push) Has been skipped
fix(lint): auto-fix ruff violations and tune lint rules
- Auto-fixed 4,496 lint issues (import sorting, modern syntax, etc.)
- Added ignore rules for patterns intentional in this codebase:
  E402 (late imports), E712 (SQLAlchemy filters), B904 (raise from),
  SIM108/SIM105/SIM117 (readability preferences)
- Added per-file ignores for tests and scripts
- Excluded broken scripts/rename_terminology.py (has curly quotes)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 23:10:42 +01:00

221 lines
6.6 KiB
Python

#!/usr/bin/env python3
"""
Migration Squash Script
This script squashes all existing migrations into fresh per-module initial migrations.
Run this after setting up PostgreSQL to simplify the migration history.
Prerequisites:
- PostgreSQL must be running: make docker-up
- DATABASE_URL environment variable must be set to PostgreSQL
Usage:
python scripts/squash_migrations.py
What this script does:
1. Backs up existing migrations from all version_locations to a timestamped backup
2. Creates a fresh initial migration from current models
3. Stamps the database as being at the new migration
After running:
1. Review the new migration files
2. Test with: make db-reset (drops schema, runs all migrations, seeds data)
3. If satisfied, delete the backup directory
"""
import os
import shutil
import subprocess
import sys
from datetime import datetime
from pathlib import Path
# Add project root to path
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root))
VERSIONS_DIR = project_root / "alembic" / "versions"
# All migration version directories (core + modules)
MODULE_MIGRATION_DIRS = [
project_root / "alembic" / "versions",
project_root / "app" / "modules" / "billing" / "migrations" / "versions",
project_root / "app" / "modules" / "cart" / "migrations" / "versions",
project_root / "app" / "modules" / "catalog" / "migrations" / "versions",
project_root / "app" / "modules" / "cms" / "migrations" / "versions",
project_root / "app" / "modules" / "customers" / "migrations" / "versions",
project_root / "app" / "modules" / "dev_tools" / "migrations" / "versions",
project_root / "app" / "modules" / "inventory" / "migrations" / "versions",
project_root / "app" / "modules" / "loyalty" / "migrations" / "versions",
project_root / "app" / "modules" / "marketplace" / "migrations" / "versions",
project_root / "app" / "modules" / "messaging" / "migrations" / "versions",
project_root / "app" / "modules" / "orders" / "migrations" / "versions",
]
def check_prerequisites():
"""Verify PostgreSQL is configured."""
database_url = os.getenv("DATABASE_URL", "")
if not database_url.startswith("postgresql"):
print("ERROR: DATABASE_URL must be a PostgreSQL URL")
print(f"Current: {database_url[:50]}...")
print("")
print("Set DATABASE_URL or start PostgreSQL with: make docker-up")
sys.exit(1)
print(f"Database: {database_url.split('@')[0]}@...")
return True
def backup_migrations():
"""Backup existing migrations from all version locations."""
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_dir = project_root / "alembic" / f"versions_backup_{timestamp}"
backup_dir.mkdir(parents=True, exist_ok=True)
total_backed_up = 0
for versions_dir in MODULE_MIGRATION_DIRS:
if not versions_dir.exists():
continue
migration_files = [f for f in versions_dir.glob("*.py") if f.name != "__init__.py"]
if not migration_files:
continue
# Create a subdirectory in backup matching the source path
rel_path = versions_dir.relative_to(project_root)
target_dir = backup_dir / str(rel_path).replace("/", "_")
target_dir.mkdir(parents=True, exist_ok=True)
for f in migration_files:
shutil.copy2(f, target_dir / f.name)
total_backed_up += 1
# Remove migration files from source (keep __init__.py)
for f in migration_files:
f.unlink()
if total_backed_up > 0:
print(f"Backed up {total_backed_up} migrations to {backup_dir.name}/")
return backup_dir
print("No migration files found to backup")
backup_dir.rmdir()
return None
def create_fresh_migration():
"""Generate fresh initial migration from models."""
print("Generating fresh initial migration...")
result = subprocess.run(
[
sys.executable, "-m", "alembic", "revision",
"--autogenerate", "-m", "initial_postgresql_schema"
],
cwd=project_root,
capture_output=True,
text=True
)
if result.returncode != 0:
print("ERROR: Failed to generate migration")
print(result.stderr)
sys.exit(1)
print(result.stdout)
# Find the new migration file
new_migrations = list(VERSIONS_DIR.glob("*initial_postgresql_schema*.py"))
if new_migrations:
print(f"Created: {new_migrations[0].name}")
return new_migrations[0]
return None
def clean_migration_file(migration_path: Path):
"""Remove SQLite-specific patterns from migration."""
if not migration_path:
return
content = migration_path.read_text()
# Remove batch_alter_table references (not needed for PostgreSQL)
if "batch_alter_table" in content:
print("Note: Migration contains batch_alter_table - this is not needed for PostgreSQL")
print(f"Review migration at: {migration_path}")
def stamp_database():
"""Stamp the database as being at the new migration."""
print("Stamping database with new migration...")
result = subprocess.run(
[sys.executable, "-m", "alembic", "stamp", "head"],
cwd=project_root,
capture_output=True,
text=True
)
if result.returncode != 0:
print("WARNING: Could not stamp database (may need to run migrate-up first)")
print(result.stderr)
else:
print("Database stamped at head")
def main():
print("=" * 60)
print("MIGRATION SQUASH SCRIPT")
print("=" * 60)
print("")
# Check prerequisites
check_prerequisites()
print("")
# Confirm with user
response = input("This will backup and replace all migrations. Continue? [y/N] ")
if response.lower() != "y":
print("Aborted")
sys.exit(0)
print("")
# Backup existing migrations
backup_dir = backup_migrations()
print("")
# Create fresh migration
new_migration = create_fresh_migration()
print("")
# Clean up the migration file
clean_migration_file(new_migration)
print("")
# Summary
print("=" * 60)
print("SQUASH COMPLETE")
print("=" * 60)
print("")
if backup_dir:
print(f"Backup location: {backup_dir}")
print("")
print("Next steps:")
print("1. Review the new migration files")
print("2. On a fresh database, run: make db-reset")
print("3. Verify all tables are created correctly")
print("4. If satisfied, delete the backup directory")
print("")
if backup_dir:
print("To restore from backup:")
print(f" Check {backup_dir}/ for backed up migration files")
if __name__ == "__main__":
main()