alembic fix

This commit is contained in:
2026-06-01 16:34:07 +03:00
parent e12a9ddfce
commit 4c5da7e4d9
15 changed files with 346 additions and 90 deletions

View File

@@ -0,0 +1,114 @@
# #region check_migration_chain [C:3] [TYPE Module] [SEMANTICS alembic,migration,validation,ci]
# @BRIEF Validates Alembic migration chain integrity — checks for multiple heads,
# broken chains, and missing down_revision references.
# @RATIONALE Merge migrations (e.g., f0e9d8c7b6a5) are fragile — a new migration
# with wrong down_revision can create multiple heads and break `alembic upgrade head`.
# This script catches the issue before deployment.
# @REJECTED Relying on `alembic upgrade head` to fail at deploy time was rejected —
# it's better to catch migration chain issues in CI or pre-commit.
import sys
from pathlib import Path
# Ensure backend/src is importable
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
# #region check_migration_heads [C:2] [TYPE Function]
# @BRIEF Checks that there is exactly one migration head.
# @POST Prints error and exits 1 if multiple heads found.
def check_migration_heads() -> None:
from alembic.config import Config as AlembicConfig
from alembic.script import ScriptDirectory
alembic_cfg = AlembicConfig("alembic.ini")
alembic_cfg.set_main_option("script_location", "alembic")
script_dir = ScriptDirectory.from_config(alembic_cfg)
heads = list(script_dir.get_heads())
if len(heads) == 0:
print("[migration-check] ERROR: No migration heads found — chain is empty")
sys.exit(1)
if len(heads) > 1:
print(f"[migration-check] ERROR: Multiple migration heads detected ({len(heads)}):")
for h in heads:
rev = script_dir.get_revision(h)
print(f" - {h}: {rev.doc}")
print("[migration-check] Fix: ensure new migrations reference the correct parent")
sys.exit(1)
print(f"[migration-check] OK: Single migration head — {heads[0]}")
# #endregion check_migration_heads
# #region check_migration_chain_integrity [C:2] [TYPE Function]
# @BRIEF Walks the migration chain from head to root, checking all down_revision links exist.
# @POST Prints error and exits 1 if a broken link is found.
def check_migration_chain_integrity() -> None:
from alembic.config import Config as AlembicConfig
from alembic.script import ScriptDirectory
alembic_cfg = AlembicConfig("alembic.ini")
alembic_cfg.set_main_option("script_location", "alembic")
script_dir = ScriptDirectory.from_config(alembic_cfg)
heads = list(script_dir.get_heads())
if not heads:
return # already checked above
visited = set()
current_revs = heads
while current_revs:
next_revs = []
for rev_id in current_revs:
if rev_id in visited:
continue
visited.add(rev_id)
rev = script_dir.get_revision(rev_id)
down = rev.down_revision
if down is None:
continue # root migration
if isinstance(down, str):
try:
script_dir.get_revision(down)
except Exception:
print(f"[migration-check] ERROR: Broken link — {rev_id} -> {down} (not found)")
sys.exit(1)
next_revs.append(down)
elif isinstance(down, tuple):
# Merge migration — check all parents exist
for parent in down:
try:
script_dir.get_revision(parent)
except Exception:
print(f"[migration-check] ERROR: Broken merge link — {rev_id} -> {parent} (not found)")
sys.exit(1)
next_revs.append(parent)
current_revs = next_revs
print(f"[migration-check] OK: Chain integrity verified — {len(visited)} migrations traversed")
# #endregion check_migration_chain_integrity
# #region check_migration_main [C:2] [TYPE Function]
# @BRIEF Entry point — runs all migration chain checks.
def main() -> None:
print("[migration-check] === Alembic Migration Chain Validation ===")
check_migration_heads()
check_migration_chain_integrity()
print("[migration-check] === All checks passed ===")
# #endregion check_migration_main
if __name__ == "__main__":
main()