from __future__ import annotations import json from urllib.error import HTTPError, URLError from urllib.parse import urlencode from urllib.request import Request, urlopen from src.core.config import load_settings from src.integrations.exchange.exceptions import ( ExchangeConnectionError, ExchangeResponseError, ) class ExchangeRestClient: def __init__(self) -> None: self.settings = load_settings() if not self.settings.exchange_base_url: raise ExchangeConnectionError("EXCHANGE_BASE_URL is empty.") self.base_url = self.settings.exchange_base_url.rstrip("/") self.timeout = self.settings.exchange_timeout_sec def get_json(self, path: str, params: dict[str, str] | None = None) -> dict: query = f"?{urlencode(params)}" if params else "" url = f"{self.base_url}{path}{query}" request = Request( url=url, method="GET", headers={ "Accept": "application/json", "User-Agent": "dzentra-bot/2.0.0", }, ) try: with urlopen(request, timeout=self.timeout) as response: status = getattr(response, "status", 200) body = response.read().decode("utf-8") except HTTPError as exc: raise ExchangeResponseError( f"HTTP {exc.code} from exchange: {exc.reason}" ) from exc except URLError as exc: raise ExchangeConnectionError( f"Network error while calling exchange: {exc.reason}" ) from exc except TimeoutError as exc: raise ExchangeConnectionError("Timeout while calling exchange.") from exc if status != 200: raise ExchangeResponseError(f"Unexpected HTTP status: {status}") try: payload = json.loads(body) except json.JSONDecodeError as exc: raise ExchangeResponseError("Exchange returned non-JSON response.") from exc if not isinstance(payload, dict): raise ExchangeResponseError("Exchange response is not a JSON object.") return payload