martian7777
feat: implement backend core with ORM models, authentication, and AI-driven telemetry diagnostics
e5be436 | """Thin synchronous HTTP client wrapping the FastAPI backend for the Gradio UI. | |
| Holds the bearer token in-instance so each Gradio session carries its own | |
| authenticated client (stored in gr.State). | |
| """ | |
| from __future__ import annotations | |
| import os | |
| from typing import Any | |
| import httpx | |
| API_BASE_URL = os.getenv("API_BASE_URL", "http://localhost:8000") | |
| API_V1 = os.getenv("API_V1_PREFIX", "/api/v1") | |
| class APIError(Exception): | |
| def __init__(self, status_code: int, detail: str) -> None: | |
| self.status_code = status_code | |
| self.detail = detail | |
| super().__init__(f"[{status_code}] {detail}") | |
| class APIClient: | |
| def __init__(self, base_url: str = API_BASE_URL, timeout: float = 120.0) -> None: | |
| self.base_url = base_url.rstrip("/") | |
| self.prefix = API_V1 | |
| self.token: str | None = None | |
| self._client = httpx.Client(base_url=self.base_url, timeout=timeout) | |
| # ------------------------------------------------------------------ helpers | |
| def _headers(self) -> dict[str, str]: | |
| return {"Authorization": f"Bearer {self.token}"} if self.token else {} | |
| def _url(self, path: str) -> str: | |
| return f"{self.prefix}{path}" | |
| def _handle(self, resp: httpx.Response) -> Any: | |
| if resp.status_code >= 400: | |
| try: | |
| detail = resp.json().get("detail", resp.text) | |
| except Exception: # noqa: BLE001 | |
| detail = resp.text | |
| raise APIError(resp.status_code, str(detail)) | |
| if resp.status_code == 204 or not resp.content: | |
| return None | |
| return resp.json() | |
| def is_authenticated(self) -> bool: | |
| return self.token is not None | |
| # ------------------------------------------------------------------ auth | |
| def register(self, email: str, password: str, full_name: str | None = None) -> dict: | |
| resp = self._client.post( | |
| self._url("/auth/register"), | |
| json={"email": email, "password": password, "full_name": full_name}, | |
| ) | |
| return self._handle(resp) | |
| def login(self, email: str, password: str) -> dict: | |
| resp = self._client.post( | |
| self._url("/auth/login"), | |
| data={"username": email, "password": password}, | |
| headers={"Content-Type": "application/x-www-form-urlencoded"}, | |
| ) | |
| data = self._handle(resp) | |
| self.token = data["access_token"] | |
| return data | |
| def me(self) -> dict: | |
| return self._handle(self._client.get(self._url("/auth/me"), headers=self._headers())) | |
| def logout(self) -> None: | |
| self.token = None | |
| # ------------------------------------------------------------------ machines | |
| def list_machines(self) -> list[dict]: | |
| return self._handle(self._client.get(self._url("/machines"), headers=self._headers())) | |
| def create_machine(self, name: str, type_: str, location: str | None = None) -> dict: | |
| return self._handle( | |
| self._client.post( | |
| self._url("/machines"), | |
| json={"name": name, "type": type_, "location": location}, | |
| headers=self._headers(), | |
| ) | |
| ) | |
| def machine_summary(self, machine_id: str) -> dict: | |
| return self._handle( | |
| self._client.get(self._url(f"/machines/{machine_id}/summary"), headers=self._headers()) | |
| ) | |
| # ------------------------------------------------------------------ telemetry | |
| def upload_csv(self, machine_id: str, file_path: str) -> dict: | |
| with open(file_path, "rb") as fh: | |
| files = {"file": (os.path.basename(file_path), fh, "text/csv")} | |
| resp = self._client.post( | |
| self._url(f"/telemetry/upload/{machine_id}"), | |
| files=files, | |
| headers=self._headers(), | |
| ) | |
| return self._handle(resp) | |
| def get_task(self, task_id: str) -> dict: | |
| return self._handle( | |
| self._client.get(self._url(f"/telemetry/tasks/{task_id}"), headers=self._headers()) | |
| ) | |
| def get_series(self, machine_id: str, limit: int = 5000) -> dict: | |
| return self._handle( | |
| self._client.get( | |
| self._url(f"/telemetry/machines/{machine_id}/series"), | |
| params={"limit": limit}, | |
| headers=self._headers(), | |
| ) | |
| ) | |
| # ------------------------------------------------------------------ ai | |
| def explain(self, machine_id: str, window: int = 500) -> dict: | |
| return self._handle( | |
| self._client.get( | |
| self._url(f"/ai/explain/{machine_id}"), | |
| params={"window": window}, | |
| headers=self._headers(), | |
| ) | |
| ) | |