пул соединений psycopg2 (min=1 max=3), db.put_conn вместо close, v1.20
This commit is contained in:
+4
-4
@@ -118,7 +118,7 @@ class ContractsApp:
|
|||||||
contract_id = cur.fetchone()[0]
|
contract_id = cur.fetchone()[0]
|
||||||
conn.commit()
|
conn.commit()
|
||||||
cur.close()
|
cur.close()
|
||||||
conn.close()
|
db.put_conn(conn)
|
||||||
|
|
||||||
for f in files:
|
for f in files:
|
||||||
if not f.filename:
|
if not f.filename:
|
||||||
@@ -209,11 +209,11 @@ class ContractsApp:
|
|||||||
if not doc_id:
|
if not doc_id:
|
||||||
conn.rollback()
|
conn.rollback()
|
||||||
cur.close()
|
cur.close()
|
||||||
conn.close()
|
db.put_conn(conn)
|
||||||
return jsonify({"error": "Не удалось сохранить файл в БД"}), 500
|
return jsonify({"error": "Не удалось сохранить файл в БД"}), 500
|
||||||
|
|
||||||
cur.close()
|
cur.close()
|
||||||
conn.close()
|
db.put_conn(conn)
|
||||||
_log("assembly_done", f"cid={contract_id}")
|
_log("assembly_done", f"cid={contract_id}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
_log("assembly_error", str(e))
|
_log("assembly_error", str(e))
|
||||||
@@ -221,7 +221,7 @@ class ContractsApp:
|
|||||||
except: pass
|
except: pass
|
||||||
try: cur.close()
|
try: cur.close()
|
||||||
except: pass
|
except: pass
|
||||||
try: conn.close()
|
try: db.put_conn(conn)
|
||||||
except: pass
|
except: pass
|
||||||
return jsonify({"error": f"Сборка: {e}"}), 500
|
return jsonify({"error": f"Сборка: {e}"}), 500
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
+46
-30
@@ -1,27 +1,38 @@
|
|||||||
"""
|
"""
|
||||||
db.py — Транспортный слой к базе данных.
|
db.py — Транспортный слой к базе данных (с пулом соединений).
|
||||||
|
|
||||||
Только connect() и query(). Никакой бизнес-логики, никакого DDL.
|
Пул psycopg2.pool.ThreadedConnectionPool (min=1, max=3).
|
||||||
DDL → schema.py. Бизнес-запросы → upload.py, extractor.py, api.py.
|
Все query/execute забирают соединение из пула и возвращают обратно.
|
||||||
|
|
||||||
Функции:
|
|
||||||
_pg_connect(dbname) — сырое подключение к ЛЮБОЙ базе (используется /test createdb)
|
|
||||||
connect() — подключение к ЦЕЛЕВОЙ базе (из DB_NAME)
|
|
||||||
query(sql, params) — выполнить SELECT → (result, error)
|
|
||||||
execute(sql, params) — выполнить INSERT/UPDATE/DELETE → (rowcount, error)
|
|
||||||
query_one(sql, params) — как query, но возвращает одну строку или None
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
from psycopg2 import pool as _pgpool
|
||||||
import psycopg2
|
import psycopg2
|
||||||
|
|
||||||
|
_pool = None
|
||||||
|
|
||||||
|
|
||||||
|
def _get_pool():
|
||||||
|
"""Ленивая инициализация пула."""
|
||||||
|
global _pool
|
||||||
|
if _pool is None:
|
||||||
|
_pool = _pgpool.ThreadedConnectionPool(
|
||||||
|
minconn=1,
|
||||||
|
maxconn=3,
|
||||||
|
host=os.getenv("DB_HOST"),
|
||||||
|
port=os.getenv("DB_PORT", 5432),
|
||||||
|
dbname=os.getenv("DB_NAME"),
|
||||||
|
user=os.getenv("DB_USER"),
|
||||||
|
password=os.getenv("DB_PASS"),
|
||||||
|
sslmode=os.getenv("DB_SSLMODE", "disable"),
|
||||||
|
)
|
||||||
|
return _pool
|
||||||
|
|
||||||
|
|
||||||
def _pg_connect(dbname):
|
def _pg_connect(dbname):
|
||||||
"""
|
"""
|
||||||
Сырое подключение к указанной базе данных.
|
Сырое подключение к ЛЮБОЙ базе (для /test createdb).
|
||||||
Используется:
|
НЕ из пула — для создания БД нужна отдельная сессия.
|
||||||
- connect() для целевой БД
|
|
||||||
- test_routes.py для /test createdb (подключение к 'postgres')
|
|
||||||
"""
|
"""
|
||||||
return psycopg2.connect(
|
return psycopg2.connect(
|
||||||
host=os.getenv("DB_HOST"),
|
host=os.getenv("DB_HOST"),
|
||||||
@@ -35,20 +46,26 @@ def _pg_connect(dbname):
|
|||||||
|
|
||||||
def connect():
|
def connect():
|
||||||
"""
|
"""
|
||||||
Подключение к ЦЕЛЕВОЙ базе данных (DB_NAME из переменных окружения).
|
Подключение к ЦЕЛЕВОЙ базе из пула.
|
||||||
Возвращает (connection, None) при успехе или (None, error) при ошибке.
|
Возвращает (connection, None) или (None, error).
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return _pg_connect(os.getenv("DB_NAME")), None
|
return _get_pool().getconn(), None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return None, str(e)
|
return None, str(e)
|
||||||
|
|
||||||
|
|
||||||
|
def put_conn(conn):
|
||||||
|
"""Вернуть соединение в пул."""
|
||||||
|
try:
|
||||||
|
_get_pool().putconn(conn)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def query(sql_text, params=None):
|
def query(sql_text, params=None):
|
||||||
"""
|
"""
|
||||||
Выполнить SQL-запрос к целевой БД.
|
SELECT → (result, None) или (None, error).
|
||||||
Возвращает (result, None) или (None, error).
|
|
||||||
|
|
||||||
result = {"columns": [...], "rows": [[...], ...]}
|
result = {"columns": [...], "rows": [[...], ...]}
|
||||||
"""
|
"""
|
||||||
conn, err = connect()
|
conn, err = connect()
|
||||||
@@ -60,17 +77,16 @@ def query(sql_text, params=None):
|
|||||||
rows = cur.fetchall()
|
rows = cur.fetchall()
|
||||||
cols = [desc[0] for desc in cur.description] if cur.description else []
|
cols = [desc[0] for desc in cur.description] if cur.description else []
|
||||||
cur.close()
|
cur.close()
|
||||||
conn.close()
|
|
||||||
return {"columns": cols, "rows": [list(r) for r in rows]}, None
|
return {"columns": cols, "rows": [list(r) for r in rows]}, None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
conn.close()
|
|
||||||
return None, str(e)
|
return None, str(e)
|
||||||
|
finally:
|
||||||
|
put_conn(conn)
|
||||||
|
|
||||||
|
|
||||||
def execute(sql_text, params=None):
|
def execute(sql_text, params=None):
|
||||||
"""
|
"""
|
||||||
Выполнить INSERT/UPDATE/DELETE к целевой БД.
|
INSERT/UPDATE/DELETE → (rowcount, None) или (None, error).
|
||||||
Возвращает (rowcount, None) или (None, error).
|
|
||||||
"""
|
"""
|
||||||
conn, err = connect()
|
conn, err = connect()
|
||||||
if err:
|
if err:
|
||||||
@@ -81,23 +97,23 @@ def execute(sql_text, params=None):
|
|||||||
conn.commit()
|
conn.commit()
|
||||||
rowcount = cur.rowcount
|
rowcount = cur.rowcount
|
||||||
cur.close()
|
cur.close()
|
||||||
conn.close()
|
|
||||||
return rowcount, None
|
return rowcount, None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
conn.rollback()
|
try: conn.rollback()
|
||||||
conn.close()
|
except: pass
|
||||||
return None, str(e)
|
return None, str(e)
|
||||||
|
finally:
|
||||||
|
put_conn(conn)
|
||||||
|
|
||||||
|
|
||||||
def query_one(sql_text, params=None):
|
def query_one(sql_text, params=None):
|
||||||
"""
|
"""
|
||||||
Выполнить SELECT и вернуть ПЕРВУЮ строку или None.
|
SELECT одной строки → (row_dict, None) или (None, error).
|
||||||
Возвращает (row_dict, None) или (None, error).
|
|
||||||
"""
|
"""
|
||||||
result, err = query(sql_text, params)
|
result, err = query(sql_text, params)
|
||||||
if err:
|
if err:
|
||||||
return None, err
|
return None, err
|
||||||
rows = result["rows"]
|
rows = result["rows"]
|
||||||
if not rows:
|
if not rows:
|
||||||
return None, None # нет строк — не ошибка
|
return None, None
|
||||||
return dict(zip(result["columns"], rows[0])), None
|
return dict(zip(result["columns"], rows[0])), None
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<div class="topbar">
|
<div class="topbar">
|
||||||
<img src="{{ url_for('static', filename='nubes-logo.svg') }}" alt="Nubes">
|
<img src="{{ url_for('static', filename='nubes-logo.svg') }}" alt="Nubes">
|
||||||
<span class="title">Сверка договоров <span style="font-weight:400;color:var(--muted);font-size:12px;">v1.19</span></span>
|
<span class="title">Сверка договоров <span style="font-weight:400;color:var(--muted);font-size:12px;">v1.20</span></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
|||||||
Reference in New Issue
Block a user