v1.0.89: detailed code comments — all Python files + index.html architecture overview

This commit is contained in:
2026-07-27 22:02:51 +04:00
parent 7180587538
commit 1cc3f6eb7e
9 changed files with 290 additions and 38 deletions
+52 -4
View File
@@ -1,5 +1,20 @@
"""
HTTP-клиент для Nubes API + автоопределение стенда.
Класс HttpClient — тонкая обёртка над requests.Session:
- Добавляет заголовки: Authorization Bearer, User-Agent (DDoS-Guard)
- GET: raise_for_status → .json()
- POST: проверка r.ok, извлечение Location-заголовка
Функции автостенда:
- detect_endpoint(token) — пробует dev→test стенды, возвращает URL
- stand_name(endpoint) — "dev" / "test" по URL
- create_client(token, fallback) — HttpClient + endpoint (одним вызовом)
"""
import requests
# Список стендов для автоопределения. Порядок важен: dev первый (быстрее).
STANDS = [
"https://lk-api-gateway-dev.ngcloud.ru/api/v1/svc",
"https://lk-api-gateway-test.ngcloud.ru/api/v1/svc",
@@ -7,7 +22,11 @@ STANDS = [
def detect_endpoint(token):
"""Пробуем токен против dev и test стендов, возвращаем рабочий URL."""
"""Пробуем токен против dev и test стендов, возвращаем рабочий URL.
Делает GET /instances?pageSize=1 на каждый стенд.
Если results != None — стенд рабочий, возвращаем его URL.
Если ни один не подошёл — возвращаем None."""
for ep in STANDS:
try:
c = HttpClient(ep, token)
@@ -20,7 +39,10 @@ def detect_endpoint(token):
def stand_name(endpoint):
"""dev/test по URL стенда."""
"""dev/test по URL стенда.
Ищет подстроку 'dev' или 'test' в URL.
Если не найдено — возвращает '?'."""
for name in ("dev", "test"):
if name in (endpoint or ""):
return name
@@ -28,7 +50,10 @@ def stand_name(endpoint):
def create_client(token, fallback_endpoint=None):
"""HttpClient с автоопределением стенда по токену."""
"""HttpClient с автоопределением стенда по токену.
Сначала detect_endpoint(token), если не сработало — fallback_endpoint.
Возвращает кортеж (HttpClient, endpoint_url) или None если стенд не определён."""
ep = detect_endpoint(token) or fallback_endpoint
if not ep:
return None
@@ -36,8 +61,17 @@ def create_client(token, fallback_endpoint=None):
class HttpClient:
"""HTTP-клиент для Nubes REST API.
Использует requests.Session для keep-alive соединений.
Добавляет обязательные заголовки:
- Authorization: Bearer <token> (аутентификация)
- User-Agent: Mozilla/5.0 (DDoS-Guard требует НЕ python-requests)"""
def __init__(self, endpoint, token):
# Убираем trailing slash чтобы потом добавлять "/path"
self._endpoint = endpoint.rstrip("/")
# Session — переиспользует TCP-соединения между запросами
self._session = requests.Session()
self._session.headers.update({
"Authorization": f"Bearer {token}",
@@ -45,14 +79,27 @@ class HttpClient:
})
def get(self, path, **kwargs):
"""GET-запрос. Таймаут по умолчанию 10 секунд.
Вызывает raise_for_status() — при 4xx/5xx выбрасывает HTTPError.
Возвращает распарсенный JSON (dict/list)."""
kwargs.setdefault("timeout", 10)
r = self._session.get(f"{self._endpoint}{path}", **kwargs)
r.raise_for_status()
return r.json()
def post(self, path, data=None, **kwargs):
"""POST-запрос. Таймаут по умолчанию 30 секунд.
Отправляет data как JSON (json=...).
При HTTP-ошибке выбрасывает Exception с кодом и телом ответа.
Возвращает dict:
- Распарсенный JSON (если ответ — валидный JSON-объект)
- + ключ "_location" со значением заголовка Location (если есть)
Location нужен для получения UID созданного ресурса (instanceUid, opUid)."""
kwargs.setdefault("timeout", 30)
url = f"{self._endpoint}{path}"
# json=... — requests сам сериализует и ставит Content-Type: application/json
r = self._session.post(url, json=(data if data is not None else {}), **kwargs)
if not r.ok:
raise Exception(f"POST {path}: {r.status_code} {r.reason}: {r.text[:200]}")
@@ -62,8 +109,9 @@ class HttpClient:
if isinstance(parsed, dict):
result = parsed
except Exception:
pass
pass # тело не JSON или не dict — ок, Location всё равно извлечём
loc = r.headers.get("Location", "")
if loc:
# Location бывает вида "./UUID" или "/api/v1/svc/instanceOperations/UUID"
result["_location"] = loc
return result