"""API Client - wrapper for HTTP requests to FastAPI server.""" from typing import Any import requests from flask import session from config import Config class APIClient: """HTTP client for TieMeasureFlow API server.""" def __init__(self): self.base_url = Config.API_SERVER_URL.rstrip("/") self.timeout = 30 @property def _headers(self) -> dict[str, str]: """Build request headers with API key from session.""" headers = {"Content-Type": "application/json"} api_key = session.get("api_key") if api_key: headers["X-API-Key"] = api_key return headers def _handle_response(self, response: requests.Response) -> dict[str, Any]: """Parse response and return normalized dict. Returns: - If 2xx and not 204: response JSON - If 204 No Content: empty dict - If 4xx/5xx: {"error": True, "status_code": ..., "detail": "..."} """ if response.ok: if response.status_code == 204: return {} return response.json() # Non-OK response (4xx/5xx) try: error_body = response.json() detail = error_body.get("detail", str(error_body)) # FastAPI 422 returns detail as a list of validation errors if isinstance(detail, list): detail = "; ".join( e.get("msg", str(e)) for e in detail if isinstance(e, dict) ) or str(detail) except Exception: detail = response.text or f"HTTP {response.status_code}" return { "error": True, "status_code": response.status_code, "detail": detail } def get(self, endpoint: str, params: dict | None = None) -> dict[str, Any]: """GET request to API server.""" try: response = requests.get( f"{self.base_url}{endpoint}", headers=self._headers, params=params, timeout=self.timeout, ) return self._handle_response(response) except (requests.ConnectionError, requests.Timeout) as e: return { "error": True, "status_code": 0, "detail": f"Errore di connessione al server: {str(e)}" } def post(self, endpoint: str, data: dict | None = None, files: dict | None = None) -> dict[str, Any]: """POST request to API server.""" try: if files: headers = {"X-API-Key": session.get("api_key", "")} response = requests.post( f"{self.base_url}{endpoint}", headers=headers, data=data, files=files, timeout=self.timeout, ) else: response = requests.post( f"{self.base_url}{endpoint}", headers=self._headers, json=data, timeout=self.timeout, ) return self._handle_response(response) except (requests.ConnectionError, requests.Timeout) as e: return { "error": True, "status_code": 0, "detail": f"Errore di connessione al server: {str(e)}" } def put(self, endpoint: str, data: dict | None = None) -> dict[str, Any]: """PUT request to API server.""" try: response = requests.put( f"{self.base_url}{endpoint}", headers=self._headers, json=data, timeout=self.timeout, ) return self._handle_response(response) except (requests.ConnectionError, requests.Timeout) as e: return { "error": True, "status_code": 0, "detail": f"Errore di connessione al server: {str(e)}" } def delete(self, endpoint: str) -> dict[str, Any]: """DELETE request to API server.""" try: response = requests.delete( f"{self.base_url}{endpoint}", headers=self._headers, timeout=self.timeout, ) return self._handle_response(response) except (requests.ConnectionError, requests.Timeout) as e: return { "error": True, "status_code": 0, "detail": f"Errore di connessione al server: {str(e)}" } # --- Domain helpers --- def get_station_recipes(self, station_code: str) -> dict[str, Any]: """Return the list of active recipes assigned to the given station.""" return self.get(f"/api/stations/by-code/{station_code}/recipes") api_client = APIClient()