Refactor Alembic setup and add migration helper script.
Updated Alembic configuration and folder structure to reference the `app` module. Introduced a new `migrate.py` script to manage migrations efficiently with commands for generating, applying, and inspecting migrations. Adjusted `env.py` to ensure proper model imports and use environment-driven database URLs.
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
[alembic]
|
[alembic]
|
||||||
script_location = alembic
|
script_location = app/alembic
|
||||||
sqlalchemy.url = postgresql://postgres:postgres@db:5432/eventspace
|
sqlalchemy.url = postgresql://postgres:postgres@db:5432/eventspace
|
||||||
|
|
||||||
[loggers]
|
[loggers]
|
||||||
|
|||||||
@@ -1,10 +1,24 @@
|
|||||||
|
import sys
|
||||||
from logging.config import fileConfig
|
from logging.config import fileConfig
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from sqlalchemy import engine_from_config
|
from sqlalchemy import engine_from_config
|
||||||
from sqlalchemy import pool
|
from sqlalchemy import pool
|
||||||
|
|
||||||
from alembic import context
|
from alembic import context
|
||||||
|
|
||||||
|
# Get the path to the app directory (parent of 'alembic')
|
||||||
|
app_dir = Path(__file__).resolve().parent.parent
|
||||||
|
# Add the app directory to Python path
|
||||||
|
sys.path.append(str(app_dir.parent))
|
||||||
|
|
||||||
|
# Import Core modules
|
||||||
|
from app.core.config import settings
|
||||||
|
from app.core.database import Base
|
||||||
|
|
||||||
|
# Import all models to ensure they're registered with SQLAlchemy
|
||||||
|
from app.models import *
|
||||||
|
|
||||||
# this is the Alembic Config object, which provides
|
# this is the Alembic Config object, which provides
|
||||||
# access to the values within the .ini file in use.
|
# access to the values within the .ini file in use.
|
||||||
config = context.config
|
config = context.config
|
||||||
@@ -16,14 +30,10 @@ if config.config_file_name is not None:
|
|||||||
|
|
||||||
# add your model's MetaData object here
|
# add your model's MetaData object here
|
||||||
# for 'autogenerate' support
|
# for 'autogenerate' support
|
||||||
# from myapp import mymodel
|
target_metadata = Base.metadata
|
||||||
# target_metadata = mymodel.Base.metadata
|
|
||||||
target_metadata = None
|
|
||||||
|
|
||||||
# other values from the config, defined by the needs of env.py,
|
# Override the SQLAlchemy URL with the one from settings
|
||||||
# can be acquired:
|
config.set_main_option("sqlalchemy.url", settings.database_url)
|
||||||
# my_important_option = config.get_main_option("my_important_option")
|
|
||||||
# ... etc.
|
|
||||||
|
|
||||||
|
|
||||||
def run_migrations_offline() -> None:
|
def run_migrations_offline() -> None:
|
||||||
@@ -75,4 +85,4 @@ def run_migrations_online() -> None:
|
|||||||
if context.is_offline_mode():
|
if context.is_offline_mode():
|
||||||
run_migrations_offline()
|
run_migrations_offline()
|
||||||
else:
|
else:
|
||||||
run_migrations_online()
|
run_migrations_online()
|
||||||
@@ -2,7 +2,7 @@ from fastapi import FastAPI
|
|||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from fastapi.responses import HTMLResponse
|
from fastapi.responses import HTMLResponse
|
||||||
|
|
||||||
from app.config import settings
|
from app.core.config import settings
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
|||||||
200
backend/migrate.py
Normal file
200
backend/migrate.py
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
Migration script for EventSpace.
|
||||||
|
Helps with generating and applying Alembic migrations.
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Ensure the project root is in the Python path
|
||||||
|
project_root = Path(__file__).resolve().parent
|
||||||
|
if str(project_root) not in sys.path:
|
||||||
|
sys.path.append(str(project_root))
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Import settings to check if configuration is working
|
||||||
|
from app.core.config import settings
|
||||||
|
|
||||||
|
print(f"Using database URL: {settings.database_url}")
|
||||||
|
except ImportError as e:
|
||||||
|
print(f"Error importing settings: {e}")
|
||||||
|
print("Make sure your Python path includes the project root.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def check_models():
|
||||||
|
"""Check if all models are properly imported"""
|
||||||
|
print("Checking model imports...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Import all models through the models package
|
||||||
|
from app.models import __all__ as all_models
|
||||||
|
print(f"Found {len(all_models)} model(s):")
|
||||||
|
for model in all_models:
|
||||||
|
print(f" - {model}")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error checking models: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def generate_migration(message):
|
||||||
|
"""Generate an Alembic migration with the given message"""
|
||||||
|
print(f"Generating migration: {message}")
|
||||||
|
|
||||||
|
cmd = ["alembic", "revision", "--autogenerate", "-m", message]
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
|
||||||
|
print(result.stdout)
|
||||||
|
if result.returncode != 0:
|
||||||
|
print("Error generating migration:", file=sys.stderr)
|
||||||
|
print(result.stderr, file=sys.stderr)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Extract revision ID if possible
|
||||||
|
revision = None
|
||||||
|
for line in result.stdout.split("\n"):
|
||||||
|
if "Generating" in line and "..." in line:
|
||||||
|
try:
|
||||||
|
# Look for the revision ID, which is typically 12 hex characters
|
||||||
|
parts = line.split()
|
||||||
|
for part in parts:
|
||||||
|
if len(part) >= 12 and all(c in "0123456789abcdef" for c in part[:12]):
|
||||||
|
revision = part[:12]
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if revision:
|
||||||
|
print(f"Generated revision: {revision}")
|
||||||
|
else:
|
||||||
|
print("Generated migration (revision ID not identified)")
|
||||||
|
|
||||||
|
return revision or True
|
||||||
|
|
||||||
|
|
||||||
|
def apply_migration(revision=None):
|
||||||
|
"""Apply migrations up to the specified revision or head"""
|
||||||
|
target = revision or "head"
|
||||||
|
print(f"Applying migration(s) to: {target}")
|
||||||
|
|
||||||
|
cmd = ["alembic", "upgrade", target]
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
|
||||||
|
print(result.stdout)
|
||||||
|
if result.returncode != 0:
|
||||||
|
print("Error applying migration:", file=sys.stderr)
|
||||||
|
print(result.stderr, file=sys.stderr)
|
||||||
|
return False
|
||||||
|
|
||||||
|
print("Migration(s) applied successfully")
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def show_current():
|
||||||
|
"""Show current revision"""
|
||||||
|
print("Current database revision:")
|
||||||
|
|
||||||
|
cmd = ["alembic", "current"]
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
|
||||||
|
print(result.stdout)
|
||||||
|
if result.returncode != 0:
|
||||||
|
print("Error getting current revision:", file=sys.stderr)
|
||||||
|
print(result.stderr, file=sys.stderr)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def list_migrations():
|
||||||
|
"""List all migrations and their status"""
|
||||||
|
print("Listing migrations:")
|
||||||
|
|
||||||
|
cmd = ["alembic", "history", "--verbose"]
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
|
||||||
|
print(result.stdout)
|
||||||
|
if result.returncode != 0:
|
||||||
|
print("Error listing migrations:", file=sys.stderr)
|
||||||
|
print(result.stderr, file=sys.stderr)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def check_database_connection():
|
||||||
|
"""Check if database is accessible"""
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
|
|
||||||
|
try:
|
||||||
|
engine = create_engine(settings.database_url)
|
||||||
|
with engine.connect() as conn:
|
||||||
|
print("Database connection successful!")
|
||||||
|
return True
|
||||||
|
except SQLAlchemyError as e:
|
||||||
|
print(f"Error connecting to database: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main function"""
|
||||||
|
parser = argparse.ArgumentParser(description='EventSpace database migration helper')
|
||||||
|
subparsers = parser.add_subparsers(dest='command', help='Command to run')
|
||||||
|
|
||||||
|
# Generate command
|
||||||
|
generate_parser = subparsers.add_parser('generate', help='Generate a migration')
|
||||||
|
generate_parser.add_argument('message', help='Migration message')
|
||||||
|
|
||||||
|
# Apply command
|
||||||
|
apply_parser = subparsers.add_parser('apply', help='Apply migrations')
|
||||||
|
apply_parser.add_argument('--revision', help='Specific revision to apply to')
|
||||||
|
|
||||||
|
# List command
|
||||||
|
subparsers.add_parser('list', help='List migrations')
|
||||||
|
|
||||||
|
# Current command
|
||||||
|
subparsers.add_parser('current', help='Show current revision')
|
||||||
|
|
||||||
|
# Check command
|
||||||
|
subparsers.add_parser('check', help='Check database connection and models')
|
||||||
|
|
||||||
|
# Auto command (generate and apply)
|
||||||
|
auto_parser = subparsers.add_parser('auto', help='Generate and apply migration')
|
||||||
|
auto_parser.add_argument('message', help='Migration message')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.command == 'generate':
|
||||||
|
check_models()
|
||||||
|
generate_migration(args.message)
|
||||||
|
|
||||||
|
elif args.command == 'apply':
|
||||||
|
apply_migration(args.revision)
|
||||||
|
|
||||||
|
elif args.command == 'list':
|
||||||
|
list_migrations()
|
||||||
|
|
||||||
|
elif args.command == 'current':
|
||||||
|
show_current()
|
||||||
|
|
||||||
|
elif args.command == 'check':
|
||||||
|
check_database_connection()
|
||||||
|
check_models()
|
||||||
|
|
||||||
|
elif args.command == 'auto':
|
||||||
|
check_models()
|
||||||
|
revision = generate_migration(args.message)
|
||||||
|
if revision:
|
||||||
|
proceed = input("Press Enter to apply migration or Ctrl+C to abort... ")
|
||||||
|
apply_migration()
|
||||||
|
|
||||||
|
else:
|
||||||
|
parser.print_help()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user