feat: run Alembic migrations automatically on startup

- Added alembic==1.18.4 to requirements.txt (was missing, transitive only)
- Added run_alembic_migrations() to startup_event in app.py — runs
   before init_db() to apply all pending migrations
- Combined approach: Alembic for complex migrations first, then inline
  _ensure_*() additive safety net in init_db()
- This ensures DB schema is always up to date on container startup
  without manual intervention
This commit is contained in:
2026-05-26 18:56:29 +03:00
parent ce0c0cf235
commit 81fc7e0902
2 changed files with 36 additions and 4 deletions

View File

@@ -37,6 +37,7 @@ referencing==0.37.0
requests==2.32.5
rpds-py==0.30.0
SecretStorage==3.5.0
alembic==1.18.4
SQLAlchemy==2.0.45
starlette==0.50.0
typing-inspection==0.4.2

View File

@@ -50,6 +50,9 @@ from .api.routes import (
translate,
validation,
)
from alembic.config import Config as AlembicConfig
from alembic import command as alembic_command
from .core.auth.security import get_password_hash
from .core.cot_logger import seed_trace_id
from .core.database import AuthSessionLocal, init_db
@@ -123,17 +126,45 @@ def ensure_initial_admin_user() -> None:
finally:
db.close()
# #endregion ensure_initial_admin_user
# #region run_alembic_migrations [C:2] [TYPE Function]
# @BRIEF Applies pending Alembic migrations before application initialization.
# @POST All Alembic migrations up to 'head' are applied to DATABASE_URL.
# @SIDE_EFFECT Executes ALTER TABLE / CREATE TABLE / etc. via Alembic migration chain.
# @RATIONALE Keeps DB schema in sync automatically at container startup —
# eliminates manual `alembic upgrade head` and drift between
# inline _ensure_*() migrations and Alembic migration files.
def run_alembic_migrations() -> None:
with belief_scope("run_alembic_migrations"):
try:
alembic_cfg = AlembicConfig("alembic.ini")
alembic_cfg.set_main_option("script_location", "alembic")
alembic_command.upgrade(alembic_cfg, "head")
logger.reason("Alembic migrations applied up to head")
except Exception as exc:
logger.explore(
"Alembic migration failed — app may have schema drift",
extra={"error": str(exc)},
)
# Don't crash — init_db() + _ensure_*() serve as safety net
# #endregion run_alembic_migrations
# #region startup_event [C:3] [TYPE Function]
# @BRIEF Handles application startup tasks, such as starting the scheduler.
# @RELATION CALLS -> [AppDependencies]
# @BRIEF Handles application startup tasks: Alembic migrations, schema init, admin bootstrap, scheduler.
# @RELATION CALLS -> [run_alembic_migrations]
# @RELATION CALLS -> [init_db]
# @PRE None.
# @POST Scheduler is started.
# Startup event
# @POST Schema is up-to-date (Alembic + inline safety net), admin user exists if configured, scheduler is started.
@app.on_event("startup")
async def startup_event():
seed_trace_id()
with belief_scope("startup_event"):
ensure_encryption_key()
# 1. Alembic — applies complex migrations (add/drop/rename columns, data migrations)
run_alembic_migrations()
# 2. init_db — creates missing tables + inline _ensure_*() additive safety net
init_db()
ensure_initial_admin_user()
scheduler = get_scheduler_service()