- Move app/, worker/, websocket_server/, migrations/, tests and the services to core/, which becomes a self-contained root - .gitmodules path for the novnc submodule rewritten by git mv. - Initial Step to split into core and cloud/community
153 lines
4.8 KiB
Python
153 lines
4.8 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
xCloudify management commands.
|
||
|
||
Provides Click-based helpers around Flask-Migrate so you can initialise,
|
||
autogenerate and apply migrations without dragging every model into the
|
||
production WSGI process.
|
||
|
||
Usage examples
|
||
--------------
|
||
$ python manage.py migrations:init
|
||
$ python manage.py migrations:generate -m "add bar column"
|
||
$ python manage.py migrations:apply
|
||
$ python3 bootstrap_db.py
|
||
|
||
|
||
# Starting from scratch
|
||
rm -rf migrations/
|
||
python manage.py migrations:init
|
||
python manage.py migrations:generate -m "First"
|
||
python manage.py migrations:apply
|
||
python3 bootstrap_db.py
|
||
"""
|
||
|
||
import logging
|
||
from pathlib import Path
|
||
import pkgutil
|
||
from importlib import import_module
|
||
import click
|
||
from flask.cli import with_appcontext
|
||
from flask_migrate import (init as alembic_init,
|
||
migrate as alembic_migrate,
|
||
upgrade as alembic_upgrade,
|
||
downgrade as alembic_downgrade,
|
||
current as alembic_current,
|
||
history as alembic_history,
|
||
stamp as alembic_stamp)
|
||
|
||
# Importing the Flask application *after* logging is configured
|
||
from app import app, db # pylint: disable=wrong-import-position
|
||
|
||
logging.basicConfig(level=logging.INFO)
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
# --------------------------------------------------------------------------- #
|
||
# Helper – import all model modules only when a migration command runs #
|
||
# --------------------------------------------------------------------------- #
|
||
def _import_models() -> None:
|
||
"""
|
||
Dynamically import every module in ``app.models`` so that
|
||
``db.metadata`` is fully populated for Alembic.
|
||
|
||
This is **not** executed by the production WSGI server – only by
|
||
the CLI commands below, keeping live workers lightweight.
|
||
"""
|
||
models_directory: Path = Path("app") / "models"
|
||
logger.info(f"Models dir: {models_directory}")
|
||
for module_info in pkgutil.iter_modules([str(models_directory)]):
|
||
import_module(f"app.models.{module_info.name}")
|
||
logger.info(f"Imported {module_info.name}")
|
||
logger.info("All model modules imported for migration command.")
|
||
|
||
|
||
# --------------------------------------------------------------------------- #
|
||
# CLI setup with Click #
|
||
# --------------------------------------------------------------------------- #
|
||
@click.group()
|
||
def command_line_interface():
|
||
"""Root group for xCloudify helper commands."""
|
||
pass
|
||
|
||
|
||
@command_line_interface.command("migrations:init")
|
||
@with_appcontext
|
||
def init_migrations() -> None:
|
||
"""
|
||
Create the *migrations* directory and stamp the current head.
|
||
|
||
Safe to call repeatedly – it no-ops if the directory exists.
|
||
"""
|
||
_import_models()
|
||
migrations_directory: Path = Path("migrations")
|
||
if migrations_directory.exists():
|
||
logger.info("Migrations directory already exists at %s", migrations_directory)
|
||
else:
|
||
logger.info("Creating migrations directory at %s", migrations_directory)
|
||
alembic_init()
|
||
alembic_stamp(revision="head")
|
||
logger.info("Directory initialised and stamped to current head.")
|
||
|
||
|
||
@command_line_interface.command("migrations:generate")
|
||
@click.option(
|
||
"--message", "-m",
|
||
default="schema update",
|
||
help="Message to attach to the migration revision."
|
||
)
|
||
@with_appcontext
|
||
def generate_migration(message: str) -> None:
|
||
"""
|
||
Autogenerate a migration script from model changes.
|
||
"""
|
||
_import_models()
|
||
logger.info("Generating migration: %s", message)
|
||
alembic_migrate(message=message)
|
||
logger.info("Migration script created.")
|
||
|
||
|
||
@command_line_interface.command("migrations:apply")
|
||
@with_appcontext
|
||
def apply_migrations() -> None:
|
||
"""
|
||
Apply all unapplied migrations.
|
||
"""
|
||
logger.info("Applying migrations to database …")
|
||
alembic_upgrade()
|
||
logger.info("Database schema is now up-to-date.")
|
||
|
||
|
||
@command_line_interface.command("migrations:current")
|
||
@with_appcontext
|
||
def current_migration() -> None:
|
||
"""
|
||
Show the current applied migration revision.
|
||
"""
|
||
alembic_current(verbose=True)
|
||
|
||
|
||
@command_line_interface.command("migrations:history")
|
||
@with_appcontext
|
||
def migration_history() -> None:
|
||
"""
|
||
Show the full migration history.
|
||
"""
|
||
alembic_history(verbose=True)
|
||
|
||
|
||
@command_line_interface.command("migrations:downgrade")
|
||
@click.option("--revision", "-r", required=True, help="Revision to downgrade to, e.g. -r -1 (one step back) or -r <revision-id>")
|
||
@with_appcontext
|
||
def downgrade_migration(revision: str) -> None:
|
||
"""
|
||
Downgrade to a previous migration revision.
|
||
"""
|
||
logger.info("Downgrading to revision: %s", revision)
|
||
alembic_downgrade(revision=revision)
|
||
logger.info("Downgrade complete.")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
command_line_interface()
|