- Перенесена структура Django-конфига в src/core и src/settings - Унифицирована Docker-сборка и docker-compose для dev/prod - Добавлены startup-checks (DB/Redis) и обновлены env-шаблоны - Расширена OpenAPI-документация и ответы API - Удалены устаревшие deploy/requirements/служебные скрипты - Обновлены CI/CD, README и тесты
92 lines
2.6 KiB
Python
92 lines
2.6 KiB
Python
"""
|
|
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)
|