CadHubManage/app/core/plugin_http_client.py

101 lines
3.8 KiB
Python

from __future__ import annotations
import asyncio
import json
from typing import Any, Dict, Optional
from urllib import error, parse, request
from app.config import software_config
class PluginSubmitError(RuntimeError):
pass
class PluginHttpClient:
"""HTTP client for submitting tasks to CAD plugins."""
def __init__(self, config_provider=software_config):
self._config_provider = config_provider
async def submit_task(self, software_id: str, task_type: str, payload: Dict[str, Any]) -> Dict[str, Any]:
plugin_config = self._config_provider.get_plugin_config(software_id)
if not plugin_config:
raise PluginSubmitError(f"Plugin config for software '{software_id}' not found")
base_url = plugin_config.get("base_url")
submit_path = plugin_config.get("submit_path", "/api/plugin/tasks")
timeout = int(plugin_config.get("request_timeout_sec", 10))
task_config = self._config_provider.get_plugin_task_config(software_id, task_type) or {}
submit_path = task_config.get("path", submit_path)
body_mode = task_config.get("body_mode", "passthrough")
if not base_url:
raise PluginSubmitError(f"Plugin base_url for software '{software_id}' is missing")
url = parse.urljoin(base_url.rstrip("/") + "/", submit_path.lstrip("/"))
request_payload = self._build_payload(payload=payload, body_mode=body_mode)
try:
return await asyncio.to_thread(self._post_json, url, request_payload, timeout)
except PluginSubmitError:
raise
except Exception as exc:
raise PluginSubmitError(str(exc)) from exc
@staticmethod
def _build_payload(payload: Dict[str, Any], body_mode: str) -> Dict[str, Any]:
task_params = payload.get("task_params", {}) if isinstance(payload.get("task_params"), dict) else {}
model_path = payload.get("model_path")
callback = {
"execution_id": payload.get("execution_id"),
"batch_id": payload.get("batch_id"),
"item_id": payload.get("item_id"),
"callback_url": payload.get("callback_url"),
"attempt": payload.get("attempt"),
}
if body_mode == "file_path":
return {"filePath": model_path, **task_params, **callback}
if body_mode == "task_params_only":
return {**task_params, **callback}
if body_mode == "project_open":
return {**task_params, **callback}
if body_mode == "creo_close_model":
return {
"software_type": "creo",
"force_close": task_params.get("force_close") is True,
}
if body_mode == "empty":
return {}
if body_mode == "passthrough":
return payload
# Unknown modes fallback to passthrough for compatibility.
return payload
@staticmethod
def _post_json(url: str, payload: Dict[str, Any], timeout: int) -> Dict[str, Any]:
raw = json.dumps(payload).encode("utf-8")
req = request.Request(
url=url,
data=raw,
headers={"Content-Type": "application/json"},
method="POST",
)
try:
with request.urlopen(req, timeout=timeout) as resp:
body = resp.read().decode("utf-8") if resp.length != 0 else ""
if not body:
return {}
try:
return json.loads(body)
except json.JSONDecodeError:
return {"raw": body}
except error.HTTPError as exc:
body = exc.read().decode("utf-8", errors="ignore") if exc.fp else ""
raise PluginSubmitError(f"Plugin HTTP {exc.code}: {body}") from exc
except error.URLError as exc:
raise PluginSubmitError(f"Plugin request failed: {exc.reason}") from exc