bmc_hub/app/modules/shipmondo/backend/api_client.py

76 lines
3.3 KiB
Python
Raw Permalink Normal View History

2026-08-25 01:09:45 +02:00
import json
import logging
from typing import Any, Dict, List, Optional
import aiohttp
from app.core.config import settings
logger = logging.getLogger(__name__)
class ShipmondoApiClient:
def __init__(self) -> None:
self.base_url = (settings.SHIPMONDO_API_BASE_URL or "").rstrip("/")
self.timeout_seconds = max(5, int(settings.SHIPMONDO_TIMEOUT_SECONDS or 30))
@property
def configured(self) -> bool:
return bool(self.base_url and settings.SHIPMONDO_API_USER and settings.SHIPMONDO_API_KEY)
async def _request(
self,
method: str,
path: str,
*,
params: Optional[Dict[str, Any]] = None,
json_payload: Optional[Dict[str, Any]] = None,
) -> Any:
if not self.configured:
raise RuntimeError("Shipmondo API credentials are not configured")
timeout = aiohttp.ClientTimeout(total=self.timeout_seconds)
auth = aiohttp.BasicAuth(settings.SHIPMONDO_API_USER, settings.SHIPMONDO_API_KEY)
headers = {"Accept": "application/json", "Content-Type": "application/json"}
url = f"{self.base_url}/{path.lstrip('/')}"
async with aiohttp.ClientSession(timeout=timeout, auth=auth, headers=headers) as session:
async with session.request(method, url, params=params, json=json_payload) as response:
body = await response.text()
if response.status >= 400:
detail = ""
try:
parsed = json.loads(body)
detail = str(parsed.get("message") or parsed.get("error") or parsed.get("errors") or "")
except (ValueError, AttributeError):
detail = body.strip()
detail = detail[:300]
logger.warning("Shipmondo request failed (%s %s): HTTP %s", method, path, response.status)
suffix = f": {detail}" if detail else ""
raise RuntimeError(f"Shipmondo API returned HTTP {response.status}{suffix}")
if not body.strip():
return {}
return json.loads(body)
async def list_products(self, country_code: str) -> List[Dict[str, Any]]:
payload = await self._request("GET", "/products", params={"country_code": country_code.upper()})
if isinstance(payload, list):
return [item for item in payload if isinstance(item, dict)]
if isinstance(payload, dict):
raw_items = payload.get("items") or payload.get("products") or payload.get("data") or []
if isinstance(raw_items, list):
return [item for item in raw_items if isinstance(item, dict)]
return []
async def create_shipment(self, payload: Dict[str, Any]) -> Dict[str, Any]:
response = await self._request("POST", "/shipments", json_payload=payload)
if not isinstance(response, dict):
raise RuntimeError("Shipmondo returned an invalid shipment response")
return response
async def get_shipment(self, shipment_id: str) -> Dict[str, Any]:
response = await self._request("GET", f"/shipments/{shipment_id}")
if not isinstance(response, dict):
raise RuntimeError("Shipmondo returned an invalid shipment response")
return response