Рефакторинг инфраструктуры и конфигурации проекта
- Перенесена структура Django-конфига в src/core и src/settings - Унифицирована Docker-сборка и docker-compose для dev/prod - Добавлены startup-checks (DB/Redis) и обновлены env-шаблоны - Расширена OpenAPI-документация и ответы API - Удалены устаревшие deploy/requirements/служебные скрипты - Обновлены CI/CD, README и тесты
This commit is contained in:
91
src/apps/core/startup_checks.py
Normal file
91
src/apps/core/startup_checks.py
Normal file
@@ -0,0 +1,91 @@
|
||||
"""
|
||||
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)
|
||||
Reference in New Issue
Block a user