feat(infra): auto-commit con 88 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,212 @@
|
||||
"""Ejecuta una peticion HTTP y la registra en el History de Hoppscotch self-host.
|
||||
|
||||
Doble proposito: (1) lanza la request real con `requests` resolviendo placeholders
|
||||
de variables y (2) opcionalmente la persiste en el UserHistory del backend
|
||||
Hoppscotch self-hosted via la mutation GraphQL createUserHistory, de modo que el
|
||||
humano la vea aparecer en vivo en la pestana History de su GUI (la GUI escucha la
|
||||
subscription `userHistoryCreated`).
|
||||
|
||||
La request se ejecuta con las variables resueltas, pero lo que se guarda en el
|
||||
History es la request SIN resolver (con `<<var>>`/`{{var}}` literales), igual que
|
||||
en la GUI: asi el humano ve la plantilla con sus variables, no los valores
|
||||
expandidos. La mutation esta protegida por GqlAuthGuard: el JWT de sesion viaja en
|
||||
la cookie `access_token`.
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
|
||||
import requests
|
||||
|
||||
from infra.build_hoppscotch_collection import build_hoppscotch_collection
|
||||
|
||||
# Hoppscotch usa la sintaxis <<var>>; muchas plantillas tambien traen {{var}}.
|
||||
# Aceptamos ambas: grupo 1 = delimitador de apertura, grupo 2 = nombre de la
|
||||
# variable, grupo 3 = delimitador de cierre.
|
||||
_VAR_RE = re.compile(r"(<<|\{\{)\s*([A-Za-z0-9_]+)\s*(>>|\}\})")
|
||||
|
||||
# Limite del cuerpo de respuesta en el output, para no devolver payloads enormes.
|
||||
_BODY_TRUNCATE = 5000
|
||||
|
||||
_HISTORY_MUTATION = (
|
||||
"mutation($d:String!,$m:String!,$t:ReqType!){"
|
||||
" createUserHistory(reqData:$d, resMetadata:$m, reqType:$t){ id } }"
|
||||
)
|
||||
|
||||
|
||||
def _resolve_placeholders(text: str, variables: dict) -> str:
|
||||
"""Sustituye <<var>>/{{var}} por su valor en `variables`.
|
||||
|
||||
Si la variable no esta en `variables`, se conserva el literal tal cual
|
||||
(incluidos los delimitadores). Determinista y sin I/O.
|
||||
|
||||
Args:
|
||||
text: cadena con (opcionales) placeholders.
|
||||
variables: dict name->value con los valores de sustitucion.
|
||||
|
||||
Returns:
|
||||
la cadena con los placeholders conocidos resueltos.
|
||||
"""
|
||||
|
||||
def repl(match: re.Match) -> str:
|
||||
name = match.group(2)
|
||||
if name in variables:
|
||||
return str(variables[name])
|
||||
return match.group(0)
|
||||
|
||||
return _VAR_RE.sub(repl, text)
|
||||
|
||||
|
||||
def hoppscotch_run_request(
|
||||
method: str,
|
||||
url: str,
|
||||
*,
|
||||
title: str | None = None,
|
||||
headers: dict | None = None,
|
||||
body: str | None = None,
|
||||
body_type: str | None = None,
|
||||
variables: dict | None = None,
|
||||
access_token: str,
|
||||
backend_url: str = "http://localhost:3170",
|
||||
record_history: bool = True,
|
||||
timeout_s: float = 30.0,
|
||||
verify_tls: bool = True,
|
||||
) -> dict:
|
||||
"""Ejecuta una request HTTP y la registra en el History de Hoppscotch.
|
||||
|
||||
Resuelve los placeholders `<<var>>`/`{{var}}` de la url, los headers y el
|
||||
body usando `variables`, lanza la peticion real con `requests`, y (si
|
||||
`record_history`) guarda en el UserHistory del backend self-host la request
|
||||
SIN resolver (para que en la GUI History se vea con las variables, igual que
|
||||
en el editor).
|
||||
|
||||
Args:
|
||||
method: metodo HTTP (GET, POST, ...).
|
||||
url: endpoint, puede contener placeholders `<<var>>`/`{{var}}`.
|
||||
title: nombre visible de la request en el History. None = derivar de
|
||||
method + path via build_hoppscotch_collection.
|
||||
headers: dict name->value de cabeceras. Sus values admiten placeholders.
|
||||
body: cuerpo de la request como texto ya serializado. Admite placeholders.
|
||||
body_type: tipo de cuerpo ("json"|"form"|"raw"|None).
|
||||
variables: dict name->value para resolver los placeholders al EJECUTAR.
|
||||
None = no se resuelve nada (los literales viajan tal cual).
|
||||
access_token: JWT de sesion (de hoppscotch_login). Viaja en la cookie
|
||||
`access_token`, NO en el header Authorization. Necesario para grabar
|
||||
en el History.
|
||||
backend_url: base del backend Hoppscotch self-host (sin barra final).
|
||||
record_history: si True y hay access_token, registra la request en el
|
||||
UserHistory via createUserHistory.
|
||||
timeout_s: timeout de la peticion HTTP en segundos.
|
||||
verify_tls: verificacion del certificado TLS de la request ejecutada.
|
||||
|
||||
Returns:
|
||||
Dict. En exito de la ejecucion HTTP:
|
||||
``{"status": "ok", "status_code": int, "duration_ms": int,
|
||||
"response_body": str (truncado a 5000 chars), "response_headers": dict,
|
||||
"recorded": bool, "history_id": str|None}``. Si la ejecucion fue ok pero
|
||||
el registro de History fallo, `status` sigue "ok", `recorded` False y se
|
||||
anade `history_error`. Si la ejecucion HTTP falla (RequestException):
|
||||
``{"status": "error", "error": str, "recorded": False}``.
|
||||
"""
|
||||
variables = variables or {}
|
||||
headers = headers or {}
|
||||
|
||||
# 1) Resolver placeholders para EJECUTAR (copia; los originales se conservan
|
||||
# para registrarlos sin resolver en el History).
|
||||
resolved_url = _resolve_placeholders(url, variables)
|
||||
resolved_headers = {
|
||||
key: _resolve_placeholders(str(value), variables)
|
||||
for key, value in headers.items()
|
||||
}
|
||||
resolved_body = (
|
||||
_resolve_placeholders(body, variables) if body is not None else None
|
||||
)
|
||||
|
||||
# 2) Ejecutar la peticion real.
|
||||
try:
|
||||
resp = requests.request(
|
||||
method,
|
||||
resolved_url,
|
||||
headers=resolved_headers,
|
||||
data=resolved_body if resolved_body is not None else None,
|
||||
timeout=timeout_s,
|
||||
verify=verify_tls,
|
||||
)
|
||||
except requests.RequestException as exc:
|
||||
return {
|
||||
"status": "error",
|
||||
"error": f"transport error: {exc}",
|
||||
"recorded": False,
|
||||
}
|
||||
|
||||
duration_ms = int(resp.elapsed.total_seconds() * 1000)
|
||||
status_code = resp.status_code
|
||||
response_body = resp.text[:_BODY_TRUNCATE]
|
||||
response_headers = dict(resp.headers)
|
||||
|
||||
result = {
|
||||
"status": "ok",
|
||||
"status_code": status_code,
|
||||
"duration_ms": duration_ms,
|
||||
"response_body": response_body,
|
||||
"response_headers": response_headers,
|
||||
"recorded": False,
|
||||
"history_id": None,
|
||||
}
|
||||
|
||||
# 3) Registrar en el UserHistory (request SIN resolver, como en la GUI).
|
||||
if not record_history or not access_token:
|
||||
return result
|
||||
|
||||
spec = {
|
||||
"method": method,
|
||||
"url": url,
|
||||
"headers": headers,
|
||||
"body": body,
|
||||
"body_type": body_type,
|
||||
}
|
||||
req_names = [title] if title else None
|
||||
req_item = build_hoppscotch_collection([spec], request_names=req_names)[
|
||||
"requests"
|
||||
][0]
|
||||
req_data = json.dumps(req_item)
|
||||
res_metadata = json.dumps(
|
||||
{"statusCode": status_code, "duration": duration_ms}
|
||||
)
|
||||
|
||||
payload = {
|
||||
"query": _HISTORY_MUTATION,
|
||||
"variables": {"d": req_data, "m": res_metadata, "t": "REST"},
|
||||
}
|
||||
|
||||
try:
|
||||
hist_resp = requests.post(
|
||||
f"{backend_url}/graphql",
|
||||
json=payload,
|
||||
cookies={"access_token": access_token},
|
||||
timeout=timeout_s,
|
||||
)
|
||||
except requests.RequestException as exc:
|
||||
result["history_error"] = f"transport error: {exc}"
|
||||
return result
|
||||
|
||||
try:
|
||||
hist_data = hist_resp.json()
|
||||
except ValueError:
|
||||
result["history_error"] = (
|
||||
f"non-JSON history response (HTTP {hist_resp.status_code})"
|
||||
)
|
||||
return result
|
||||
|
||||
if hist_data.get("errors"):
|
||||
result["history_error"] = f"graphql errors: {hist_data['errors']}"
|
||||
return result
|
||||
|
||||
created = (hist_data.get("data") or {}).get("createUserHistory")
|
||||
if not created or not created.get("id"):
|
||||
result["history_error"] = "createUserHistory returned no id"
|
||||
return result
|
||||
|
||||
result["recorded"] = True
|
||||
result["history_id"] = created["id"]
|
||||
return result
|
||||
Reference in New Issue
Block a user