Files
polygon/site/routes/instances_routes.py
T

103 lines
4.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
routes/instances_routes.py — эндпоинты инстансов.
Blueprint "instances" с префиксом /api/v1/svc/instances.
GET /instances — список с пагинацией
GET /instances/<uid> — полные данные инстанса (state.params + state.out)
POST /instances — создать shell инстанса → 201 + Location: ./{uid}
КРИТИЧНО: POST /instances ОБЯЗАН возвращать заголовок Location.
HttpClient.post() в app-autotest достаёт instanceUid ИМЕННО из Location.
"""
from flask import Blueprint, g, jsonify, make_response, request
from werkzeug.local import LocalProxy
import mock_state
import config.loader as _cfg
state = LocalProxy(lambda: mock_state.get_state(g.stand_id))
SERVICES = LocalProxy(lambda: _cfg.get_services(g.stand_id))
bp = Blueprint("instances", __name__, url_prefix="/api/v1/svc/instances")
@bp.route("", methods=["GET"])
@bp.route("/", methods=["GET"])
def list_instances():
"""GET /api/v1/svc/instances — список инстансов с пагинацией.
Query-параметры:
pageSize — размер страницы (по умолчанию 200, максимум 200)
page — номер страницы (по умолчанию 1)
Возвращает {results, pageSize, page, total}.
Пагинация: стоп по len(batch) < pageSize (как в реальном API).
"""
page_size = request.args.get("pageSize", 200, type=int)
page = request.args.get("page", 1, type=int)
result = state.list_instances(page_size=page_size, page=page)
return jsonify(result)
@bp.route("/<uid>", methods=["GET"])
def get_instance(uid):
"""GET /api/v1/svc/instances/{uid} — полные данные инстанса.
Возвращает {instance: {instanceUid, serviceId, displayName, status,
explainedStatus, svc, dtCreate, state: {params: {...}, out: {...}}}}.
Если инстанс не найден — 404 с JSON-ошибкой.
"""
inst = state.get_instance(uid)
if not inst:
return jsonify({"error": "instance not found"}), 404
return jsonify({"instance": dict(inst)})
@bp.route("", methods=["POST"])
@bp.route("/", methods=["POST"])
def create_instance():
"""POST /api/v1/svc/instances — создать shell инстанса.
Ожидает JSON: {serviceId: int, displayName: str?}
⛔ КРИТИЧНО: возвращает 201 + заголовок Location: ./{instanceUid}.
HttpClient.post() в app-autotest парсит Location чтобы получить UUID:
- Берёт последний сегмент после /
- Проверяет len(сегмент) >= 32 (длина UUID без дефисов)
Также возвращает {"instanceUid": uid} в теле — двойная защита
на случай если заголовок не дойдёт.
"""
body = request.get_json(silent=True) or {}
# ⛔ body должен быть dict, не массив и не примитив
if not isinstance(body, dict):
return jsonify({"error": "request body must be a JSON object"}), 400
service_id = body.get("serviceId")
display_name = body.get("displayName", "unnamed")
# Валидация: serviceId обязателен
if service_id is None:
return jsonify({"error": "serviceId is required"}), 400
# Валидация: serviceId должен быть целым положительным числом
if not isinstance(service_id, int) or isinstance(service_id, bool) or service_id <= 0:
return jsonify({"error": "serviceId must be a positive integer"}), 400
# Проверка что сервис существует в конфиге
svc_def = SERVICES.get(service_id)
if not svc_def:
return jsonify({"error": f"service {service_id} not found"}), 404
# Создать shell инстанса (статус "creating")
uid = state.create_instance(service_id, display_name, svc_def)
# ⛔ Location ОБЯЗАТЕЛЕН — без него executor не получит instanceUid
resp = make_response(jsonify({"instanceUid": uid}), 201)
resp.headers["Location"] = f"./{uid}"
return resp