from __future__ import annotations from typing import Any, Optional import httpx class CrudApiClient: """Thin async client around the ABT CRUD APIs. Keep business/transformation logic out of this class. The client only knows how to call the source endpoints and unwrap the collection names from the CRUD contracts. """ def __init__( self, notifications_base_url: str, customers_base_url: str, timeout_seconds: float = 10.0, ): self.notifications_base_url = notifications_base_url.rstrip("/") self.customers_base_url = customers_base_url.rstrip("/") self.timeout_seconds = timeout_seconds async def _get( self, base_url: str, path: str, params: Optional[dict[str, Any]] = None, ) -> dict[str, Any]: async with httpx.AsyncClient(base_url=base_url, timeout=self.timeout_seconds) as client: response = await client.get(path, params=params) response.raise_for_status() return response.json() # notifications-crud.yaml async def get_notification_subscriptions(self, customer_profile_id: int) -> list[dict[str, Any]]: payload = await self._get( self.notifications_base_url, "/notificationsubscriptions", params={"customerProfileId": customer_profile_id, "expand": "eventTypeChannel"}, ) return payload.get("notificationSubscriptions", []) async def get_notification_category_defaults(self, notification_category_id: int) -> Optional[dict[str, Any]]: payload = await self._get( self.notifications_base_url, "/notificationcategories", params={"notificationCategoryId": notification_category_id, "expand": "eventTypeChannel"}, ) categories = payload.get("notificationCategories", []) return categories[0] if categories else None # customers-crud-v2.yaml async def get_ovpay_token(self, ovpay_token_id: Optional[int]) -> Optional[dict[str, Any]]: if ovpay_token_id is None: return None payload = await self._get( self.customers_base_url, "/ovpaytokens", params={"ovPayTokenId": ovpay_token_id}, ) tokens = payload.get("ovPayTokens", []) return tokens[0] if tokens else None async def get_devices(self, customer_profile_id: int) -> list[dict[str, Any]]: payload = await self._get( self.customers_base_url, "/devices", params={"customerProfileId": customer_profile_id}, ) return payload.get("devices", []) async def get_persons(self, customer_profile_id: int) -> list[dict[str, Any]]: """Return customer/person resources used for email-like channels. customers-crud-v2.yaml exposes person details through GET /customers, filtered by customerProfileId. For the transformation service we normalize the matching customer profile into one resource with the customer's email. """ payload = await self._get( self.customers_base_url, "/customers", params={"customerProfileId": customer_profile_id}, ) return payload.get("customers", []) # Backwards-compatible alias used by the service type hints. NotificationsCrudClient = CrudApiClient