""" db.py — Транспортный слой к базе данных. Только connect() и query(). Никакой бизнес-логики, никакого DDL. DDL → schema.py. Бизнес-запросы → upload.py, extractor.py, api.py. Функции: _pg_connect(dbname) — сырое подключение к ЛЮБОЙ базе (используется /test createdb) connect() — подключение к ЦЕЛЕВОЙ базе (из DB_NAME) query(sql, params) — выполнить запрос → (result, error) """ import os import psycopg2 def _pg_connect(dbname): """ Сырое подключение к указанной базе данных. Используется: - connect() для целевой БД - test_routes.py для /test createdb (подключение к 'postgres') """ return psycopg2.connect( host=os.getenv("DB_HOST"), port=os.getenv("DB_PORT", 5432), dbname=dbname, user=os.getenv("DB_USER"), password=os.getenv("DB_PASS"), sslmode=os.getenv("DB_SSLMODE", "disable"), ) def connect(): """ Подключение к ЦЕЛЕВОЙ базе данных (DB_NAME из переменных окружения). Возвращает (connection, None) при успехе или (None, error) при ошибке. """ try: return _pg_connect(os.getenv("DB_NAME")), None except Exception as e: return None, str(e) def query(sql_text, params=None): """ Выполнить SQL-запрос к целевой БД. Возвращает (result, None) или (None, error). result = {"columns": [...], "rows": [[...], ...]} """ conn, err = connect() if err: return None, f"connect: {err}" try: cur = conn.cursor() cur.execute(sql_text, params) rows = cur.fetchall() cols = [desc[0] for desc in cur.description] if cur.description else [] cur.close() conn.close() return {"columns": cols, "rows": [list(r) for r in rows]}, None except Exception as e: conn.close() return None, str(e)