import aiohttp import asyncio from apollo_utils.service.exceptions import APIError from dataclasses import dataclass from datetime import date, datetime from typing import List, Union from server import config as base_config from server.constants.client.base.errors import HTTP_AUTH_ERROR from server.utils.client.base.request.make_request import make_request from server.utils.client.base.request.retry import retry from server.utils.delphi.exceptions import DelphiError @dataclass class DelphiConfig: client_id: str client_secret: str base_url: str class DelphiBaseClient: """Base Delphi API client.""" def __init__(self, session: aiohttp.ClientSession, config: DelphiConfig): self.session = session self.config = config self.token = None self.token_expires_at = datetime.min self.token_update_lock = asyncio.Lock() self.token_last_updated_at = datetime.min self.headers = {"Client-Service": base_config.SERVICE, "Client-Environment": base_config.ENVIRONMENT} async def _set_token(self, last_updated_at: datetime or None = None): """Make post request to delphi API to get a new auth token. Args: last_updated_at: On auth errors force update token if it is not updated yet. """ end_url = self.config.base_url + "oauth/token" body = { "client_id": self.config.client_id, "client_secret": self.config.client_secret, "grant_type": "client_credentials", } async with self.token_update_lock: if self.token_expires_at > datetime.utcnow() and ( not last_updated_at or last_updated_at < self.token_last_updated_at ): return resp_data = await make_request( self.session, end_url, method="POST", body=body, headers=self.headers, error_cls=DelphiError ) self.token_last_updated_at = datetime.utcnow() self.token_expires_at = datetime.utcfromtimestamp(resp_data["expires_at"]) self.token = "Bearer " + resp_data["access_token"] async def _handle_get_error(self, e: APIError): """Handle get auth error.""" if e.original_status_code == HTTP_AUTH_ERROR: await self._set_token(self.token_last_updated_at) else: raise e @staticmethod def prepare_params(params: Union[dict, List[tuple], None]) -> Union[dict, List[tuple], None]: """Prepare request params.""" if not params: return params if isinstance(params, dict): params = params.items() result = [] for key, value in params: if value is None: continue if isinstance(value, (list, tuple, set)): result.extend([(key, v) for v in value]) elif isinstance(value, bool): result.append((key, str(value).lower())) elif isinstance(value, (date, datetime)): result.append((key, value.isoformat())) else: result.append((key, value)) return result @retry(auth_handler=_handle_get_error) async def _get(self, url: str, params: Union[List[tuple], None] = None) -> dict or list: """Make http GET request to Delphi API.""" if self.token_expires_at <= datetime.utcnow(): await self._set_token() end_url = self.config.base_url + url headers = {"Authorization": self.token} headers.update(self.headers) return await make_request(self.session, end_url, params=params, headers=headers, error_cls=DelphiError) async def get(self, url: str, params: Union[dict, List[tuple], None] = None) -> dict or list: """Prepare params and make get request. Args: url: URL. params: Request params. Returns: Response data. """ return await self._get(url, self.prepare_params(params))