92 lines
3.0 KiB
Python
92 lines
3.0 KiB
Python
"""
|
|
Datenbank-Anbindung für die WDM-Performance-Anwendung.
|
|
|
|
Verwendet einen Thread-sicheren Connection-Pool. Beim Start wird mehrfach
|
|
versucht die Verbindung aufzubauen, da Postgres im Docker-Setup u.U. noch
|
|
nicht bereit ist, wenn der App-Container startet.
|
|
"""
|
|
import os
|
|
import time
|
|
import logging
|
|
from contextlib import contextmanager
|
|
|
|
import psycopg2
|
|
import psycopg2.extras
|
|
from psycopg2.pool import ThreadedConnectionPool
|
|
|
|
log = logging.getLogger("wdm-performance")
|
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
|
|
|
DB_HOST = os.environ.get("DB_HOST", "db")
|
|
DB_PORT = os.environ.get("DB_PORT", "5432")
|
|
DB_NAME = os.environ.get("DB_NAME", "wdm")
|
|
DB_USER = os.environ.get("DB_USER", "wdm")
|
|
DB_PASSWORD = os.environ.get("DB_PASSWORD", "wdm")
|
|
|
|
_pool = None
|
|
|
|
|
|
def init_pool(max_versuche=30, wartezeit_sekunden=2):
|
|
"""Baut den Connection-Pool auf. Versucht es mehrfach, falls die DB
|
|
beim Containerstart noch nicht erreichbar ist (kein harter Absturz)."""
|
|
global _pool
|
|
for versuch in range(1, max_versuche + 1):
|
|
try:
|
|
_pool = ThreadedConnectionPool(
|
|
minconn=1,
|
|
maxconn=10,
|
|
host=DB_HOST,
|
|
port=DB_PORT,
|
|
dbname=DB_NAME,
|
|
user=DB_USER,
|
|
password=DB_PASSWORD,
|
|
connect_timeout=5,
|
|
)
|
|
log.info("Datenbankverbindung zu %s:%s/%s hergestellt.", DB_HOST, DB_PORT, DB_NAME)
|
|
return
|
|
except psycopg2.OperationalError as exc:
|
|
if versuch == max_versuche:
|
|
log.error("Konnte nach %d Versuchen keine DB-Verbindung herstellen: %s", max_versuche, exc)
|
|
raise
|
|
log.warning("DB noch nicht erreichbar (Versuch %d/%d), warte %ds ...", versuch, max_versuche, wartezeit_sekunden)
|
|
time.sleep(wartezeit_sekunden)
|
|
|
|
|
|
@contextmanager
|
|
def get_conn():
|
|
"""Liefert eine Connection aus dem Pool. Bei Fehlern wird die
|
|
Connection verworfen statt sie kaputt zurückzugeben (Stabilität bei
|
|
Langzeitbetrieb)."""
|
|
if _pool is None:
|
|
init_pool()
|
|
conn = _pool.getconn()
|
|
try:
|
|
yield conn
|
|
conn.commit()
|
|
except Exception:
|
|
conn.rollback()
|
|
raise
|
|
finally:
|
|
_pool.putconn(conn)
|
|
|
|
|
|
@contextmanager
|
|
def get_cursor(dict_cursor=True):
|
|
with get_conn() as conn:
|
|
cursor_factory = psycopg2.extras.RealDictCursor if dict_cursor else None
|
|
cur = conn.cursor(cursor_factory=cursor_factory)
|
|
try:
|
|
yield cur
|
|
finally:
|
|
cur.close()
|
|
|
|
|
|
def migrate():
|
|
"""Leichte, idempotente Schema-Migrationen für bereits laufende
|
|
Installationen. `init.sql` wird nur beim allerersten Start (leeres
|
|
Datenvolume) ausgeführt - bestehende Installationen bekommen neue Spalten
|
|
stattdessen hier beim App-Start nachgerüstet."""
|
|
with get_cursor() as cur:
|
|
cur.execute('ALTER TABLE "Auftraege" ADD COLUMN IF NOT EXISTS "artikel" text')
|
|
log.info("Schema-Migration geprüft/angewendet.")
|