refactor: remove _ensure_*() safety net, keep only Alembic
- Simplified init_db() to only call Base.metadata.create_all() on all
three engines — no more _ensure_*() inline additive migrations
- run_alembic_migrations() now raises on failure (no safety net to
fall back to)
- All schema changes (add/drop/rename columns, data migrations) must
go through Alembic. create_all() handles new tables only.
@REJECTED _ensure_*() inline migrations removed because they:
- Duplicated Alembic logic without versioning
- Created hidden schema drift (columns existed in DB but had no
corresponding Alembic migration)
- Made audits and fresh DB provisioning unreliable
This commit is contained in:
@@ -127,12 +127,13 @@ def ensure_initial_admin_user() -> None:
|
||||
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.
|
||||
# @BRIEF Applies all pending Alembic migrations against DATABASE_URL.
|
||||
# @POST All Alembic migrations up to 'head' are applied.
|
||||
# @SIDE_EFFECT Executes ALTER TABLE / CREATE TABLE via Alembic chain.
|
||||
# @RATIONALE Single source of truth for schema changes. All column additions,
|
||||
# drops, renames, and data migrations go through Alembic.
|
||||
# init_db() runs after to create any tables that don't have
|
||||
# a standalone Alembic migration yet.
|
||||
def run_alembic_migrations() -> None:
|
||||
with belief_scope("run_alembic_migrations"):
|
||||
try:
|
||||
@@ -142,29 +143,28 @@ def run_alembic_migrations() -> None:
|
||||
logger.reason("Alembic migrations applied up to head")
|
||||
except Exception as exc:
|
||||
logger.explore(
|
||||
"Alembic migration failed — app may have schema drift",
|
||||
"Alembic migration failed — check migration chain or alembic.ini",
|
||||
extra={"error": str(exc)},
|
||||
)
|
||||
# Don't crash — init_db() + _ensure_*() serve as safety net
|
||||
raise
|
||||
|
||||
|
||||
# #endregion run_alembic_migrations
|
||||
|
||||
|
||||
# #region startup_event [C:3] [TYPE Function]
|
||||
# @BRIEF Handles application startup tasks: Alembic migrations, schema init, admin bootstrap, scheduler.
|
||||
# @BRIEF Handles application startup: Alembic → create_all → admin bootstrap → scheduler.
|
||||
# @RELATION CALLS -> [run_alembic_migrations]
|
||||
# @RELATION CALLS -> [init_db]
|
||||
# @PRE None.
|
||||
# @POST Schema is up-to-date (Alembic + inline safety net), admin user exists if configured, scheduler is started.
|
||||
# @POST Schema is up-to-date via Alembic, missing tables created, admin exists, scheduler 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)
|
||||
# 1. Alembic — applies ALL pending migrations (add/drop/rename columns, data migrations)
|
||||
run_alembic_migrations()
|
||||
# 2. init_db — creates missing tables + inline _ensure_*() additive safety net
|
||||
# 2. init_db — creates new tables via create_all() (does NOT alter existing ones)
|
||||
init_db()
|
||||
ensure_initial_admin_user()
|
||||
scheduler = get_scheduler_service()
|
||||
|
||||
@@ -760,29 +760,23 @@ def _ensure_dictionary_entries_columns(bind_engine):
|
||||
|
||||
|
||||
# #region init_db [C:3] [TYPE Function]
|
||||
# @BRIEF Initializes the database by creating all tables.
|
||||
# @BRIEF Creates any missing tables via create_all().
|
||||
# @PRE engine, tasks_engine and auth_engine are initialized.
|
||||
# @POST Database tables created in all databases.
|
||||
# @SIDE_EFFECT Creates physical database files if they don't exist.
|
||||
# @RELATION CALLS -> [_ensure_filter_source_enum_values]
|
||||
# @RELATION CALLS -> [_ensure_dataset_review_session_columns]
|
||||
# @RELATION CALLS -> [_ensure_dictionary_entries_columns]
|
||||
# Alembic migrations (run_alembic_migrations) have already been applied.
|
||||
# @POST All tables from SQLAlchemy models exist in all databases.
|
||||
# @SIDE_EFFECT Creates physical database tables if they don't exist.
|
||||
# @RATIONALE Schema migrations are handled by Alembic (run_alembic_migrations runs
|
||||
# before init_db in startup_event). create_all() only creates NEW tables
|
||||
# that don't exist yet — it does NOT alter existing ones. All column
|
||||
# additions must go through Alembic migrations.
|
||||
# @REJECTED _ensure_*() inline additive migrations removed — they were duplicating
|
||||
# Alembic logic, were not versioned, and created hidden schema drift.
|
||||
# All schema changes must now go through Alembic.
|
||||
def init_db():
|
||||
with belief_scope("init_db"):
|
||||
Base.metadata.create_all(bind=engine)
|
||||
Base.metadata.create_all(bind=tasks_engine)
|
||||
Base.metadata.create_all(bind=auth_engine)
|
||||
_ensure_user_dashboard_preferences_columns(engine)
|
||||
_ensure_llm_validation_results_columns(engine)
|
||||
_ensure_user_dashboard_preferences_health_columns(engine)
|
||||
_ensure_git_server_configs_columns(engine)
|
||||
_ensure_auth_users_columns(auth_engine)
|
||||
_ensure_roles_is_admin_column(auth_engine)
|
||||
_ensure_filter_source_enum_values(engine)
|
||||
_ensure_dataset_review_session_columns(engine)
|
||||
_ensure_translation_jobs_columns(engine)
|
||||
_ensure_translation_schedules_columns(engine)
|
||||
_ensure_dictionary_entries_columns(engine)
|
||||
|
||||
|
||||
# #endregion init_db
|
||||
|
||||
Reference in New Issue
Block a user