from __future__ import annotations from typing import Any, Dict, Optional import threading from .HTTP import HTTPClient class ApiError(Exception): """Base exception for API errors.""" pass class API: """Base class for API clients using the internal HTTPClient.""" def __init__(self, base_url: str, timeout: float = 10.0) -> None: self.base_url = str(base_url or "").rstrip("/") self.timeout = float(timeout) self._http_client: Optional[HTTPClient] = None self._http_client_lock = threading.Lock() def _get_http_client(self) -> HTTPClient: """Return a reusable opened HTTP client for this API instance.""" client = self._http_client if client is not None and getattr(client, "_client", None) is not None: return client with self._http_client_lock: client = self._http_client if client is None: client = HTTPClient(timeout=self.timeout) self._http_client = client if getattr(client, "_client", None) is None: client.__enter__() return client def close(self) -> None: client = self._http_client if client is None: return with self._http_client_lock: current = self._http_client if current is None: return try: current.__exit__(None, None, None) except Exception: pass self._http_client = None def __del__(self) -> None: try: self.close() except Exception: pass def _get_json( self, path: str, params: Optional[Dict[str, Any]] = None, headers: Optional[Dict[str, str]] = None, ) -> Dict[str, Any]: url = f"{self.base_url}/{str(path or '').lstrip('/')}" try: client = self._get_http_client() response = client.get(url, params=params, headers=headers, allow_redirects=True) response.raise_for_status() return response.json() except Exception as exc: raise ApiError(f"API request failed for {url}: {exc}") from exc def _post_json( self, path: str, json_data: Optional[Dict[str, Any]] = None, params: Optional[Dict[str, Any]] = None, headers: Optional[Dict[str, str]] = None, ) -> Dict[str, Any]: url = f"{self.base_url}/{str(path or '').lstrip('/')}" try: client = self._get_http_client() response = client.request( "POST", url, json=json_data, params=params, headers=headers, follow_redirects=True, ) response.raise_for_status() return response.json() except Exception as exc: raise ApiError(f"API request failed for {url}: {exc}") from exc