""" Startup dependency checks for DB and Redis. Fail-fast checks used by long-running entrypoints (web/celery) to avoid silent hangs on connection issues. """ from __future__ import annotations import sys from urllib.parse import urlparse import psycopg2 import redis from django.conf import settings def _log(message: str) -> None: """Log to stderr to be visible early in startup.""" print(message, file=sys.stderr) def _check_db(timeout_seconds: int) -> tuple[bool, str]: db = settings.DATABASES["default"] params = { "dbname": db.get("NAME"), "user": db.get("USER"), "password": db.get("PASSWORD"), "host": db.get("HOST"), "port": db.get("PORT"), "connect_timeout": timeout_seconds, } options = db.get("OPTIONS", {}) if options.get("sslmode"): params["sslmode"] = options["sslmode"] conn = None try: conn = psycopg2.connect(**params) with conn.cursor() as cursor: cursor.execute("SELECT 1") cursor.fetchone() return True, "OK" except Exception as exc: # noqa: BLE001 target = f"{params['host']}:{params['port']}/{params['dbname']}" return False, f"{target} ({exc})" finally: if conn is not None: conn.close() def _check_redis(timeout_seconds: int) -> tuple[bool, str]: redis_url = settings.CACHES["default"]["LOCATION"] try: client = redis.Redis.from_url( redis_url, socket_connect_timeout=timeout_seconds, socket_timeout=timeout_seconds, ) client.ping() return True, "OK" except Exception as exc: # noqa: BLE001 parsed = urlparse(redis_url) target = f"{parsed.hostname}:{parsed.port}{parsed.path or ''}" return False, f"{target} ({exc})" def run_startup_checks(*, component: str = "app") -> None: """Run startup checks and exit process on failure.""" if not getattr(settings, "STARTUP_CHECKS_ENABLED", True): return db_timeout = int(getattr(settings, "STARTUP_DB_TIMEOUT_SECONDS", 3)) redis_timeout = int(getattr(settings, "STARTUP_REDIS_TIMEOUT_SECONDS", 3)) db_ok, db_message = _check_db(db_timeout) if not db_ok: _log( f"[startup:{component}] DB check failed " f"(timeout={db_timeout}s): {db_message}" ) raise SystemExit(1) redis_ok, redis_message = _check_redis(redis_timeout) if not redis_ok: _log( f"[startup:{component}] Redis check failed " f"(timeout={redis_timeout}s): {redis_message}" ) raise SystemExit(1)