#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:
@@ -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, без проверки подписи).
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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")
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user