Kairo-Jinkendo/backend/db.py
Lars f8b3ae3011
All checks were successful
Deploy Development / deploy (push) Successful in 34s
Test Suite / pytest-backend (push) Successful in 10s
Test Suite / lint-backend (push) Successful in 2s
Test Suite / compose-smoke (push) Has been skipped
Test Suite / k6 /api/health Baseline (push) Successful in 18s
Test Suite / playwright-smoke (push) Successful in 12s
DB-Verbindung: bei Auth-Fehlern sofort abbrechen, Doku zum Volume-Wechsel.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-04 22:46:12 +02:00

76 lines
2.1 KiB
Python

"""PostgreSQL connection helpers."""
from __future__ import annotations
import os
import time
import psycopg2
from psycopg2.extensions import connection
def db_params() -> dict[str, str]:
return {
"host": os.getenv("DB_HOST", "localhost"),
"port": os.getenv("DB_PORT", "5432"),
"dbname": os.getenv("DB_NAME", "kairo_dev"),
"user": os.getenv("DB_USER", "kairo_dev"),
"password": os.getenv("DB_PASSWORD", "dev_password"),
}
def get_connection() -> connection:
p = db_params()
return psycopg2.connect(
host=p["host"],
port=p["port"],
database=p["dbname"],
user=p["user"],
password=p["password"],
)
def is_retryable_db_error(exc: Exception) -> bool:
"""Nur temporäre Verbindungsprobleme erneut versuchen (nicht Auth/Config)."""
msg = str(exc).lower()
if "password authentication failed" in msg:
return False
if "does not exist" in msg and any(token in msg for token in ("role", "database")):
return False
if "no pg_hba.conf entry" in msg:
return False
return True
def connect_with_retry(max_retries: int = 30, sleep_seconds: float = 2.0) -> connection:
p = db_params()
last_exc: Exception | None = None
for attempt in range(max_retries):
try:
conn = get_connection()
conn.autocommit = False
print(f"[OK] Connected to database: {p['dbname']}")
return conn
except psycopg2.OperationalError as exc:
last_exc = exc
if not is_retryable_db_error(exc):
raise
if attempt >= max_retries - 1:
raise
print(f"Waiting for database... ({attempt + 1}/{max_retries})")
time.sleep(sleep_seconds)
if last_exc:
raise last_exc
raise RuntimeError("database connection failed")
def check_db() -> bool:
conn = get_connection()
try:
with conn.cursor() as cur:
cur.execute("SELECT 1")
cur.fetchone()
return True
finally:
conn.close()