From 81fc7e0902489315181af3ff3eb20b27e389e376 Mon Sep 17 00:00:00 2001 From: busya Date: Tue, 26 May 2026 18:56:29 +0300 Subject: [PATCH] feat: run Alembic migrations automatically on startup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- backend/requirements.txt | 1 + backend/src/app.py | 39 +++++++++++++++++++++++++++++++++++---- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/backend/requirements.txt b/backend/requirements.txt index 0903d848..92bc298d 100755 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -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 diff --git a/backend/src/app.py b/backend/src/app.py index 60228db6..3fbb822f 100755 --- a/backend/src/app.py +++ b/backend/src/app.py @@ -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()