fix: код-ревью #5 #6 #8 #9 #10 — 5 исправлений (v1.2.43)

#5 api_history: try/finally для conn/cur (как везде)
#6 stand_name: точное сравнение URL вместо substring
#8 get_real_client: удалён мёртвый алиас
#9 UNIQUE индекс idx_runs_op_uid для предотвращения гонок
#10 flock flush: f.flush() перед LOCK_UN в ротации лога
#7 (отступ scenario.py) пропущен — только косметика, риск сломать else:
This commit is contained in:
2026-08-04 08:02:08 +04:00
parent 126500a30b
commit 764592ba60
6 changed files with 21 additions and 28 deletions
-9
View File
@@ -102,15 +102,6 @@ def get_client():
return HttpClient(_cached_detect(token), token) return HttpClient(_cached_detect(token), token)
def get_real_client():
"""Алиас get_client() — историческая совместимость.
Раньше get_real_client() всегда ходил в реальный API (для сервисов).
Теперь в эмуляции сервисы тоже из полигона → обе функции идентичны.
Оставлено чтобы не ломать все места вызова."""
return get_client()
def get_client_id(): def get_client_id():
"""Извлечь ClientID из payload JWT-токена (base64url, без проверки подписи). """Извлечь ClientID из payload JWT-токена (base64url, без проверки подписи).
+4 -5
View File
@@ -52,12 +52,11 @@ def detect_endpoint(token):
def stand_name(endpoint): def stand_name(endpoint):
"""Определить имя стенда по URL. """Определить имя стенда по URL — точное совпадение endpoint.
Ищет подстроку 'dev' или 'test' в URL. Возвращает 'dev'/'test' или '?' если не совпало."""
Если не найдено — '?' (неизвестный стенд).""" for name, url in (("dev", STANDS[0]), ("test", STANDS[1])):
for name in ("dev", "test"): if (endpoint or "").rstrip("/") == url.rstrip("/"):
if name in (endpoint or ""):
return name return name
return "?" return "?"
+1 -1
View File
@@ -32,7 +32,7 @@ from routes.api_scenario_defs import bp_defs as api_scenario_defs_bp
# Версия — показывается в топбаре UI. Меняется при КАЖДОМ изменении кода. # Версия — показывается в топбаре UI. Меняется при КАЖДОМ изменении кода.
# Нужна для фильтрации истории (пользователь видит только записи своей версии). # Нужна для фильтрации истории (пользователь видит только записи своей версии).
VERSION = "1.2.42" VERSION = "1.2.43"
# Flask-приложение с Jinja2-шаблонами из папки templates/ # Flask-приложение с Jinja2-шаблонами из папки templates/
app = Flask(__name__, template_folder="templates", static_folder="static") app = Flask(__name__, template_folder="templates", static_folder="static")
+4
View File
@@ -84,6 +84,10 @@ ALTER TABLE runs ADD COLUMN IF NOT EXISTS app_version VARCHAR(16);
ALTER TABLE runs ADD COLUMN IF NOT EXISTS scenario_run_id INTEGER; ALTER TABLE runs ADD COLUMN IF NOT EXISTS scenario_run_id INTEGER;
ALTER TABLE runs ADD COLUMN IF NOT EXISTS step_number INTEGER; ALTER TABLE runs ADD COLUMN IF NOT EXISTS step_number INTEGER;
ALTER TABLE runs ADD COLUMN IF NOT EXISTS instance_meta JSONB; ALTER TABLE runs ADD COLUMN IF NOT EXISTS instance_meta JSONB;
-- Уникальный индекс на op_uid для предотвращения гонок в save_run
CREATE UNIQUE INDEX IF NOT EXISTS idx_runs_op_uid
ON runs (op_uid) WHERE op_uid IS NOT NULL;
""" """
# ── DDL для scenario_runs (запуски сценариев) ── # ── DDL для scenario_runs (запуски сценариев) ──
+11 -12
View File
@@ -31,7 +31,7 @@ import json
import fcntl import fcntl
from api.http_client import HttpClient, detect_endpoint, stand_name from api.http_client import HttpClient, detect_endpoint, stand_name
from api.auth import get_token, get_client, get_real_client, get_client_id, get_stand, get_token_info from api.auth import get_token, get_client, get_client_id, get_stand, get_token_info
from api.utils import find_uid, uid_from_location from api.utils import find_uid, uid_from_location
from operations.get_services import get_services, get_service_detail from operations.get_services import get_services, get_service_detail
from operations.get_instances import get_instances from operations.get_instances import get_instances
@@ -70,6 +70,7 @@ def _log(msg):
f.seek(0) f.seek(0)
f.truncate() f.truncate()
f.write(rest) f.write(rest)
f.flush() # сбросить буфер перед снятием лока
fcntl.flock(f, fcntl.LOCK_UN) fcntl.flock(f, fcntl.LOCK_UN)
except Exception: except Exception:
pass # молча — не ронять запрос из-за лога pass # молча — не ронять запрос из-за лога
@@ -395,6 +396,8 @@ def api_log():
@bp.route("/api/history") @bp.route("/api/history")
def api_history(): def api_history():
"""Последние 50 записей истории тестов (фильтр по client_id + stand).""" """Последние 50 записей истории тестов (фильтр по client_id + stand)."""
conn = None
cur = None
try: try:
from db.pool import get_conn, put_conn from db.pool import get_conn, put_conn
conn = get_conn() conn = get_conn()
@@ -414,20 +417,16 @@ def api_history():
rows = cur.fetchall() rows = cur.fetchall()
cols = [d[0] for d in cur.description] cols = [d[0] for d in cur.description]
result = [dict(zip(cols, r)) for r in rows] result = [dict(zip(cols, r)) for r in rows]
# Конвертировать datetime в строку
for r in result: for r in result:
if r["created_at"]: if r["created_at"]:
r["created_at"] = r["created_at"].isoformat() r["created_at"] = r["created_at"].isoformat()
cur.close()
put_conn(conn)
return jsonify(result) return jsonify(result)
except Exception as e: except Exception as e:
try:
cur.close()
except Exception:
pass
try:
put_conn(conn)
except Exception:
pass
return jsonify({"error": str(e)}), 500 return jsonify({"error": str(e)}), 500
finally:
if cur:
try: cur.close()
except Exception: pass
if conn:
try: put_conn(conn)
except Exception: pass
+1 -1
View File
@@ -11,7 +11,7 @@ GET /api/operations/<svc_id> — операции и autotest-инста
from flask import Blueprint, current_app, render_template, request, make_response, jsonify, redirect from flask import Blueprint, current_app, render_template, request, make_response, jsonify, redirect
from api.http_client import HttpClient, detect_endpoint, create_client, stand_name from api.http_client import HttpClient, detect_endpoint, create_client, stand_name
from api.auth import get_token, get_client_id, get_token_info, get_token_masked, get_client, get_real_client, get_stand, get_mode, get_polygon_stand from api.auth import get_token, get_client_id, get_token_info, get_token_masked, get_client, get_stand, get_mode, get_polygon_stand
from operations.get_instances import get_organization, get_instances from operations.get_instances import get_organization, get_instances
from operations.get_services import get_services, get_service_detail from operations.get_services import get_services, get_service_detail
from operations.service_list import load_service_ids from operations.service_list import load_service_ids