fix: code review #2 — _cfg.DELAY + VERSION в config/loader + мёртвые импорты

This commit is contained in:
2026-07-31 22:32:00 +04:00
parent 9647afcfa2
commit 94d35f3914
8 changed files with 48 additions and 27 deletions
+19 -4
View File
@@ -1,4 +1,4 @@
# polygon v0.2.0 # polygon v0.2.2
Эмулятор REST API облачной платформы Nubes для интеграционных тестов app-autotest. Эмулятор REST API облачной платформы Nubes для интеграционных тестов app-autotest.
@@ -14,12 +14,27 @@ polygon/
│ ├── test_converter.py # 10 юнит-тестов from_stands.py │ ├── test_converter.py # 10 юнит-тестов from_stands.py
│ └── test_state_machine.py # 9 юнит-тестов state_machine.py │ └── test_state_machine.py # 9 юнит-тестов state_machine.py
└── site/ └── site/
├── app.py # Flask (17 эндпоинтов) ├── app.py # Flask: только blueprint'ы + app.run() (57 строк)
├── routes/ # 6 blueprint-файлов по доменам
│ ├── root.py # /health, /
│ ├── services_routes.py# /api/v1/svc/services
│ ├── instances_routes.py# /api/v1/svc/instances
│ ├── operations_routes.py# /api/v1/svc/instanceOperations/*
│ ├── run.py # /api/v1/svc/instanceOperations/<uid>/run
│ └── mock_routes.py # /api/v1/svc/_mock/*
├── mock_state.py # MockState: instances, operations, op_params ├── mock_state.py # MockState: instances, operations, op_params
├── state_machine.py # apply_effect() — мутация состояния ├── state_machine.py # apply_effect() — мутация состояния
├── config_loader.py # загрузка services/*.yaml ├── config/
│ └── loader.py # загрузка YAML + SERVICES/OPS_INDEX/DELAY/VERSION
├── utils/
│ ├── now.py # now() — UTC ISO с 'Z'
│ └── pluralize.py # pluralize() — плюрализация
├── from_stands.py # конвертер STANDS YAML → polygon config ├── from_stands.py # конвертер STANDS YAML → polygon config
── services/ # 37 YAML-конфигов (сгенерированы) ── static/
│ └── style.css # тёмная тема
├── templates/
│ └── index.html # Jinja2-шаблон
└── services/ # YAML-конфиги (сгенерированы from_stands.py)
``` ```
**Ровно 1 gunicorn-воркер.** Состояние в памяти, не shared. **Ровно 1 gunicorn-воркер.** Состояние в памяти, не shared.
+3 -2
View File
@@ -1,5 +1,5 @@
""" """
polygon v0.2.1 — Mock Nubes API. polygon v0.2.2 — Mock Nubes API.
Эмулятор REST API облачной платформы Nubes для интеграционных тестов. Эмулятор REST API облачной платформы Nubes для интеграционных тестов.
Blueprint'ы (каждый — отдельный файл в routes/): Blueprint'ы (каждый — отдельный файл в routes/):
@@ -35,7 +35,8 @@ from routes.operations_routes import bp as operations_bp
from routes.run import bp as run_bp from routes.run import bp as run_bp
from routes.mock_routes import bp as mock_bp from routes.mock_routes import bp as mock_bp
VERSION = "0.2.2" # Версия — единый источник правды в config/loader.py
from config.loader import VERSION
# Flask-приложение с Jinja2-шаблонами и статикой # Flask-приложение с Jinja2-шаблонами и статикой
app = Flask(__name__, template_folder="templates", static_folder="static") app = Flask(__name__, template_folder="templates", static_folder="static")
+9 -2
View File
@@ -2,10 +2,11 @@
config/loader.py — загрузка сервисов и глобальные конфигурационные переменные. config/loader.py — загрузка сервисов и глобальные конфигурационные переменные.
Этот модуль загружает все YAML-конфиги сервисов при первом импорте и хранит их Этот модуль загружает все YAML-конфиги сервисов при первом импорте и хранит их
как модульные переменные. Все routes/*.py импортируют SERVICES, OPS_INDEX, DELAY как модульные переменные. Все routes/*.py импортируют SERVICES, OPS_INDEX, DELAY,
отсюда — без дублирования загрузки и без циклических импортов. VERSION отсюда — без дублирования загрузки и без циклических импортов.
Переменные модуля (вычисляются ОДИН раз при старте): Переменные модуля (вычисляются ОДИН раз при старте):
VERSION — str, версия полигона (единая для app.py и root.py)
SERVICES — {service_id: service_def} все сервисы SERVICES — {service_id: service_def} все сервисы
OPS_INDEX — {svcOperationId: service_def} поиск сервиса по ID операции OPS_INDEX — {svcOperationId: service_def} поиск сервиса по ID операции
DELAY — float, задержка операции из MOCK_OP_DELAY (по умолчанию 0.1) DELAY — float, задержка операции из MOCK_OP_DELAY (по умолчанию 0.1)
@@ -64,4 +65,10 @@ SERVICES, OPS_INDEX = _load_all()
# Задержка операции в секундах (для синхронного sleep в /run). # Задержка операции в секундах (для синхронного sleep в /run).
# Меняется на лету через POST /_mock/delay/<s> — см. mock_routes.py. # Меняется на лету через POST /_mock/delay/<s> — см. mock_routes.py.
# ⛔ Импортировать ТОЛЬКО через `import config.loader as _cfg` и читать `_cfg.DELAY` —
# иначе float копируется по значению и не реагирует на изменение.
DELAY = float(os.getenv("MOCK_OP_DELAY", "0.1")) DELAY = float(os.getenv("MOCK_OP_DELAY", "0.1"))
# Версия полигона — единый источник правды для app.py и routes/root.py.
# Меняется при КАЖДОМ изменении кода.
VERSION = "0.2.2"
+2 -3
View File
@@ -11,8 +11,7 @@ Blueprint "mock" с префиксом /api/v1/svc/_mock.
from flask import Blueprint, jsonify from flask import Blueprint, jsonify
import mock_state import mock_state
from config.loader import SERVICES, DELAY as _DELAY import config.loader as _cfg # для чтения/записи модульных переменных DELAY, SERVICES
import config.loader as _cfg # для модификации модульной переменной DELAY
bp = Blueprint("mock", __name__, url_prefix="/api/v1/svc/_mock") bp = Blueprint("mock", __name__, url_prefix="/api/v1/svc/_mock")
@@ -73,7 +72,7 @@ def mock_services():
не дёргая реальный /api/v1/svc/services. не дёргая реальный /api/v1/svc/services.
""" """
result = {} result = {}
for svc_id, svc_def in SERVICES.items(): for svc_id, svc_def in _cfg.SERVICES.items():
result[str(svc_id)] = { result[str(svc_id)] = {
"name": svc_def.get("name", ""), "name": svc_def.get("name", ""),
"displayName": svc_def.get("service_display_name", ""), "displayName": svc_def.get("service_display_name", ""),
+3 -4
View File
@@ -8,10 +8,9 @@ Blueprint "operations" с префиксом /api/v1/svc.
POST /instanceOperationCfsParams — установить параметр POST /instanceOperationCfsParams — установить параметр
GET /instanceOperations/<uid>/validate-cfs — пустое тело, 200 GET /instanceOperations/<uid>/validate-cfs — пустое тело, 200
ВАЖНО: порядок маршрутов в蓝图 критичен. Порядок маршрутов: default/<int:op_id> (целое число) и <uid> (строка UUID)
/instanceOperations/default/<int:op_id> ДОЛЖЕН быть выше различаются Flask/Werkzeug автоматически — конфликта нет, но default размещён
/instanceOperations/<uid> выше для ясности.
Иначе Flask распарсит "default" как uid и уйдёт не в тот обработчик.
""" """
from flask import Blueprint, jsonify, make_response, request from flask import Blueprint, jsonify, make_response, request
+6 -7
View File
@@ -8,11 +8,10 @@ Blueprint "root" регистрируется в app.py БЕЗ url_prefix.
from flask import Blueprint, render_template from flask import Blueprint, render_template
import mock_state import mock_state
from config.loader import SERVICES, DELAY import config.loader as _cfg
from utils.now import now as _now
# Версия — показывается на HTML-странице и в healthcheck (опционально) # Версия — из config/loader.py (единый источник правды)
VERSION = "0.2.2" VERSION = _cfg.VERSION
# Blueprint без префикса — роуты /health и / будут на корне домена # Blueprint без префикса — роуты /health и / будут на корне домена
bp = Blueprint("root", __name__) bp = Blueprint("root", __name__)
@@ -36,12 +35,12 @@ def index():
текущую задержку операций. Использует Jinja2-шаблон templates/index.html текущую задержку операций. Использует Jinja2-шаблон templates/index.html
и внешний CSS из static/style.css. и внешний CSS из static/style.css.
""" """
svc_count = len(SERVICES) svc_count = len(_cfg.SERVICES)
inst_count = len(mock_state.state.instances) inst_count = len(mock_state.state.instances)
return render_template( return render_template(
"index.html", "index.html",
version=VERSION, version=_cfg.VERSION,
svc_count=svc_count, svc_count=svc_count,
inst_count=inst_count, inst_count=inst_count,
delay=DELAY, delay=_cfg.DELAY,
) )
+5 -4
View File
@@ -14,7 +14,7 @@ from flask import Blueprint, jsonify
import mock_state import mock_state
import state_machine import state_machine
from config.loader import SERVICES, DELAY import config.loader as _cfg
from utils.now import now as _now from utils.now import now as _now
bp = Blueprint("run", __name__, url_prefix="/api/v1/svc") bp = Blueprint("run", __name__, url_prefix="/api/v1/svc")
@@ -48,12 +48,13 @@ def run_operation(uid):
op["dtStart"] = _now() op["dtStart"] = _now()
# Эмулируем задержку выполнения (по умолчанию 0.1с) # Эмулируем задержку выполнения (по умолчанию 0.1с)
if DELAY > 0: # ⛔ _cfg.DELAY — модульная переменная, может меняться через /_mock/delay
time.sleep(DELAY) if _cfg.DELAY > 0:
time.sleep(_cfg.DELAY)
# Применить эффект операции к состоянию инстанса # Применить эффект операции к состоянию инстанса
# (create → running+params, delete → удалить, modify → мерж params, ...) # (create → running+params, delete → удалить, modify → мерж params, ...)
state_machine.apply_effect(uid, mock_state.state, SERVICES) state_machine.apply_effect(uid, mock_state.state, _cfg.SERVICES)
# Фиксируем время завершения — операция выполнена успешно # Фиксируем время завершения — операция выполнена успешно
op["dtFinish"] = _now() op["dtFinish"] = _now()
+1 -1
View File
@@ -29,7 +29,7 @@ def apply_effect(op_uid, mock_state, services):
Args: Args:
op_uid — UUID операции op_uid — UUID операции
mock_state — экземпляр MockState mock_state — экземпляр MockState
services — {service_id: service_def} из config_loader services — {service_id: service_def} из config/loader.py
""" """
op = mock_state.operations.get(op_uid) op = mock_state.operations.get(op_uid)
if not op: if not op: