Files
2026-07-13 13:22:34 +08:00

79 lines
2.9 KiB
Python

from pathlib import Path
from alembic.command import stamp, upgrade
from alembic.config import Config
from alembic.migration import MigrationContext
from alembic.script import ScriptDirectory
from sqlalchemy import inspect
from sqlalchemy.engine.base import Engine
INITIAL_REVISION = "8606fa83a998"
def _get_alembic_dir() -> str:
return Path(__file__).parent / "migrations"
def _get_alembic_config(url: str) -> Config:
alembic_dir = _get_alembic_dir()
alembic_ini_path = alembic_dir / "alembic.ini"
alembic_cfg = Config(alembic_ini_path)
alembic_cfg.set_main_option("script_location", str(alembic_dir))
url = url.replace("%", "%%") # Same as here: https://github.com/mlflow/mlflow/issues/1487
alembic_cfg.set_main_option("sqlalchemy.url", url)
return alembic_cfg
def _is_legacy_database(engine: Engine) -> bool:
"""Check if this is a pre-3.6.0 auth database that needs version table migration.
Returns True if:
- Auth tables (users, experiment_permissions, etc.) exist
- alembic_version_auth table does NOT exist
This indicates a database from MLflow < 3.6.0 that was using the old
initialization method without Alembic version tracking.
"""
inspector = inspect(engine)
existing_tables = inspector.get_table_names()
has_auth_tables = "users" in existing_tables
has_version_table = "alembic_version_auth" in existing_tables
return has_auth_tables and not has_version_table
def _stamp_legacy_database(engine: Engine, revision: str) -> None:
"""Stamp a legacy database with the initial Alembic revision.
This creates the alembic_version_auth table and marks the database
as being at the specified revision, avoiding the need to re-run
the initial migration on databases that already have the tables.
"""
alembic_cfg = _get_alembic_config(engine.url.render_as_string(hide_password=False))
with engine.begin() as conn:
alembic_cfg.attributes["connection"] = conn
stamp(alembic_cfg, revision)
def migrate(engine: Engine, revision: str) -> None:
if _is_legacy_database(engine):
_stamp_legacy_database(engine, INITIAL_REVISION)
alembic_cfg = _get_alembic_config(engine.url.render_as_string(hide_password=False))
with engine.begin() as conn:
alembic_cfg.attributes["connection"] = conn
upgrade(alembic_cfg, revision)
def migrate_if_needed(engine: Engine, revision: str) -> None:
if _is_legacy_database(engine):
_stamp_legacy_database(engine, INITIAL_REVISION)
alembic_cfg = _get_alembic_config(engine.url.render_as_string(hide_password=False))
script_dir = ScriptDirectory.from_config(alembic_cfg)
with engine.begin() as conn:
context = MigrationContext.configure(conn, opts={"version_table": "alembic_version_auth"})
if context.get_current_revision() != script_dir.get_current_head():
upgrade(alembic_cfg, revision)