quartermaster/alembic/env.py
archeious a09860dc61 feat(ops): alembic env hook invokes backup-db.sh before migrations
Runs at env module load so the backup fires ahead of offline and
online migration paths, as well as alembic current / revision. A
failing backup does not stop the migration: this is defense in depth,
not a hard prerequisite, and the common failure case is "DB does not
exist yet" on a fresh checkout.

Refs #5

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 11:51:49 -06:00

75 lines
1.9 KiB
Python

import subprocess
from logging.config import fileConfig
from pathlib import Path
from sqlalchemy import engine_from_config, pool
from alembic import context
from quartermaster.config import DB_URL
from quartermaster.models import Base
def _backup_before_migrations() -> None:
script = Path(__file__).resolve().parents[1] / "scripts" / "backup-db.sh"
if not script.is_file():
return
result = subprocess.run(
[str(script), "alembic"],
check=False,
capture_output=True,
text=True,
)
if result.stdout:
print(result.stdout, end="")
if result.returncode != 0 and result.stderr:
print(
f"alembic: backup-db.sh returned {result.returncode}; "
f"continuing without a fresh backup.\n{result.stderr}",
)
_backup_before_migrations()
config = context.config
config.set_main_option("sqlalchemy.url", DB_URL)
if config.config_file_name is not None:
fileConfig(config.config_file_name)
target_metadata = Base.metadata
def run_migrations_offline() -> None:
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
render_as_batch=True,
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online() -> None:
connectable = engine_from_config(
config.get_section(config.config_ini_section, {}),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata,
render_as_batch=True,
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()