- 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
52 lines
1.6 KiB
Python
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())
|