hm_ai_qc_report_tool/wait_for_db.py
nickviljoen 2258fa532b Phase 0: bootstrap Alembic, add /health, prep for Dev/Prod cutover
- core/health blueprint exposes GET /health for deploy smoke tests
- Replace db.create_all() + ensure_schema() ALTER patch with Alembic
- Initial migration captures current schema (5 tables, all indexes)
- docker-entrypoint runs wait_for_db.py + flask db upgrade before gunicorn
2026-05-09 13:47:54 +02:00

52 lines
1.6 KiB
Python

"""Wait for the database to accept connections before booting the app.
For SQLite (current setup) this is effectively a no-op — the file is
created on first connection. The script exists as scaffolding for a
future move to Postgres, where the entrypoint must wait for the DB
container to come up before `flask db upgrade` will succeed.
Exit codes:
0 — DB is reachable
1 — gave up after MAX_WAIT_SECONDS
"""
import os
import sys
import time
from sqlalchemy import create_engine, text
from sqlalchemy.exc import OperationalError
import config as app_config
MAX_WAIT_SECONDS = int(os.environ.get('DB_WAIT_TIMEOUT', '30'))
POLL_INTERVAL = 1.0
def main() -> int:
uri = app_config.Config.SQLALCHEMY_DATABASE_URI
# SQLite needs no wait — sqlite:/// URIs always succeed once the dir exists.
if uri.startswith('sqlite:'):
print(f'[wait_for_db] SQLite ({uri}) — no wait needed')
return 0
engine = create_engine(uri, pool_pre_ping=True)
deadline = time.monotonic() + MAX_WAIT_SECONDS
attempt = 0
while True:
attempt += 1
try:
with engine.connect() as conn:
conn.execute(text('SELECT 1'))
print(f'[wait_for_db] connected after {attempt} attempt(s)')
return 0
except OperationalError as e:
if time.monotonic() >= deadline:
print(f'[wait_for_db] gave up after {MAX_WAIT_SECONDS}s: {e}',
file=sys.stderr)
return 1
time.sleep(POLL_INTERVAL)
if __name__ == '__main__':
sys.exit(main())