from __future__ import annotations import secrets from dataclasses import asdict, dataclass from django.conf import settings from django.core.cache import cache from django.db import connections from django.db.utils import DatabaseError @dataclass(frozen=True) class HealthResult: ok: bool checks: dict[str, str] def to_dict(self) -> dict[str, object]: return asdict(self) def readiness() -> HealthResult: checks: dict[str, str] = {} ok = True try: with connections["default"].cursor() as cursor: cursor.execute("SELECT 1") cursor.fetchone() checks["database"] = "ok" except DatabaseError as exc: ok = False checks["database"] = f"error:{exc.__class__.__name__}" if getattr(settings, "HEALTHCHECK_REQUIRE_CACHE", False): key = f"health:{secrets.token_hex(8)}" try: cache.set(key, "ok", timeout=10) if cache.get(key) != "ok": raise RuntimeError("cache roundtrip failed") cache.delete(key) checks["cache"] = "ok" except Exception as exc: # Health boundary: report category, never payload/details. ok = False checks["cache"] = f"error:{exc.__class__.__name__}" return HealthResult(ok=ok, checks=checks)