231 lines
7.0 KiB
Python
Executable File
231 lines
7.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
"""
|
||
Docker-friendly Flask-Migrate initialization.
|
||
No Git required - works with current schema as baseline.
|
||
"""
|
||
|
||
import os
|
||
import sys
|
||
import subprocess
|
||
import shutil
|
||
from datetime import datetime
|
||
|
||
def run_command(cmd, description, check=True):
|
||
"""Run a command and handle errors."""
|
||
print(f"\n➜ {description}")
|
||
print(f" Command: {cmd}")
|
||
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
|
||
|
||
if result.returncode == 0:
|
||
print(f"✓ Success")
|
||
if result.stdout.strip():
|
||
print(f" {result.stdout.strip()}")
|
||
return True
|
||
else:
|
||
print(f"✗ Failed")
|
||
if result.stderr:
|
||
print(f" Error: {result.stderr}")
|
||
if check:
|
||
sys.exit(1)
|
||
return False
|
||
|
||
def check_database_connection():
|
||
"""Check if we can connect to the database."""
|
||
print("\nChecking database connection...")
|
||
try:
|
||
from app import app, db
|
||
with app.app_context():
|
||
# Try a simple query
|
||
db.engine.execute("SELECT 1")
|
||
print("✓ Database connection successful")
|
||
return True
|
||
except Exception as e:
|
||
print(f"✗ Database connection failed: {e}")
|
||
return False
|
||
|
||
def check_existing_tables():
|
||
"""Check what tables exist in the database."""
|
||
print("\nChecking existing tables...")
|
||
try:
|
||
from app import app, db
|
||
with app.app_context():
|
||
# Get table names
|
||
inspector = db.inspect(db.engine)
|
||
tables = inspector.get_table_names()
|
||
|
||
if tables:
|
||
print(f"✓ Found {len(tables)} existing tables:")
|
||
for table in sorted(tables):
|
||
if table != 'alembic_version':
|
||
print(f" - {table}")
|
||
return True
|
||
else:
|
||
print("ℹ️ No tables found (empty database)")
|
||
return False
|
||
except Exception as e:
|
||
print(f"✗ Error checking tables: {e}")
|
||
return False
|
||
|
||
def main():
|
||
"""Main initialization function."""
|
||
print("=== Flask-Migrate Docker Initialization ===")
|
||
print("\nThis script will set up Flask-Migrate for your Docker deployment.")
|
||
print("It uses your CURRENT schema as the baseline (no Git required).")
|
||
|
||
# Set environment
|
||
os.environ['FLASK_APP'] = 'app.py'
|
||
|
||
# Check prerequisites
|
||
if not check_database_connection():
|
||
print("\n❌ Cannot connect to database. Check your DATABASE_URL.")
|
||
return 1
|
||
|
||
has_tables = check_existing_tables()
|
||
|
||
print("\n" + "="*50)
|
||
if has_tables:
|
||
print("SCENARIO: Existing database with tables")
|
||
print("="*50)
|
||
print("\nYour database already has tables. We'll create a baseline")
|
||
print("migration and mark it as already applied.")
|
||
else:
|
||
print("SCENARIO: Empty database")
|
||
print("="*50)
|
||
print("\nYour database is empty. We'll create a baseline")
|
||
print("migration that can be applied to create all tables.")
|
||
|
||
response = input("\nContinue? (y/N): ")
|
||
if response.lower() != 'y':
|
||
print("Aborting...")
|
||
return 1
|
||
|
||
# Step 1: Clean up any existing migrations
|
||
if os.path.exists('migrations'):
|
||
print("\n⚠️ Removing existing migrations directory...")
|
||
shutil.rmtree('migrations')
|
||
|
||
# Step 2: Initialize Flask-Migrate
|
||
print("\nInitializing Flask-Migrate...")
|
||
if not run_command("flask db init", "Creating migrations directory"):
|
||
return 1
|
||
|
||
# Step 3: Create baseline migration
|
||
print("\nCreating baseline migration from current models...")
|
||
baseline_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||
|
||
if not run_command(
|
||
f'flask db migrate -m "Docker baseline migration - {baseline_date}"',
|
||
"Generating migration"
|
||
):
|
||
return 1
|
||
|
||
# Step 4: Add documentation to the migration
|
||
print("\nDocumenting the migration...")
|
||
try:
|
||
import glob
|
||
migration_files = glob.glob('migrations/versions/*.py')
|
||
if migration_files:
|
||
latest = max(migration_files, key=os.path.getctime)
|
||
|
||
with open(latest, 'r') as f:
|
||
content = f.read()
|
||
|
||
note = f'''"""DOCKER BASELINE MIGRATION
|
||
Generated: {baseline_date}
|
||
|
||
This migration represents the current state of your models.
|
||
It serves as the baseline for all future migrations.
|
||
|
||
For existing databases with tables:
|
||
flask db stamp head # Mark as current without running
|
||
|
||
For new empty databases:
|
||
flask db upgrade # Create all tables
|
||
|
||
DO NOT MODIFY THIS MIGRATION
|
||
"""
|
||
|
||
'''
|
||
with open(latest, 'w') as f:
|
||
f.write(note + content)
|
||
|
||
print(f"✓ Documented {os.path.basename(latest)}")
|
||
except Exception as e:
|
||
print(f"⚠️ Could not document migration: {e}")
|
||
|
||
# Step 5: Handle based on database state
|
||
print("\n" + "="*50)
|
||
print("NEXT STEPS")
|
||
print("="*50)
|
||
|
||
if has_tables:
|
||
print("\nYour database already has tables. Run this command to")
|
||
print("mark it as up-to-date WITHOUT running the migration:")
|
||
print("\n flask db stamp head")
|
||
print("\nThen you can create new migrations normally:")
|
||
print(" flask db migrate -m 'Add new feature'")
|
||
print(" flask db upgrade")
|
||
else:
|
||
print("\nYour database is empty. Run this command to")
|
||
print("create all tables from the baseline migration:")
|
||
print("\n flask db upgrade")
|
||
print("\nThen you can create new migrations normally:")
|
||
print(" flask db migrate -m 'Add new feature'")
|
||
print(" flask db upgrade")
|
||
|
||
# Create a helper script
|
||
helper_content = f"""#!/bin/bash
|
||
# Flask-Migrate helper for Docker
|
||
# Generated: {baseline_date}
|
||
|
||
export FLASK_APP=app.py
|
||
|
||
case "$1" in
|
||
status)
|
||
echo "Current migration status:"
|
||
flask db current
|
||
;;
|
||
|
||
apply)
|
||
echo "Applying pending migrations..."
|
||
flask db upgrade
|
||
;;
|
||
|
||
create)
|
||
if [ -z "$2" ]; then
|
||
echo "Usage: $0 create 'Migration message'"
|
||
exit 1
|
||
fi
|
||
echo "Creating new migration: $2"
|
||
flask db migrate -m "$2"
|
||
echo "Review the migration, then run: $0 apply"
|
||
;;
|
||
|
||
mark-current)
|
||
echo "Marking database as current (no changes)..."
|
||
flask db stamp head
|
||
;;
|
||
|
||
*)
|
||
echo "Flask-Migrate Docker Helper"
|
||
echo "Usage:"
|
||
echo " $0 status - Show current migration status"
|
||
echo " $0 apply - Apply pending migrations"
|
||
echo " $0 create 'msg' - Create new migration"
|
||
echo " $0 mark-current - Mark DB as current (existing DBs)"
|
||
;;
|
||
esac
|
||
"""
|
||
|
||
with open('migrate.sh', 'w') as f:
|
||
f.write(helper_content)
|
||
|
||
os.chmod('migrate.sh', 0o755)
|
||
print("\n✓ Created migrate.sh helper script")
|
||
|
||
print("\n✨ Initialization complete!")
|
||
|
||
return 0
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main()) |