from abc import ABC, abstractmethod from datetime import datetime, timedelta from typing import Any, Dict, List, Union, Optional, Type import aiohttp import asyncio import config as base_config from server.core.constants import HTTP_AUTH_ERROR from server.core.exceptions import APIError from server.core.utils import make_request, retry class BaseClient(ABC): """Base API app_client.""" error_cls: Type[APIError] = APIError def __init__(self, session: aiohttp.ClientSession, config): self.session = session self.config = config self.token = None self.token_expires_in = 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: Optional[datetime] = 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", "audience": self.config.audience, } async with self.token_update_lock: if self.token_expires_in > 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=self.error_cls ) current_time = datetime.utcnow() self.token_last_updated_at = current_time self.token_expires_in = current_time + timedelta(seconds=resp_data["expires_in"]) self.token = "Bearer " + resp_data["access_token"] @abstractmethod async def _make_end_url(self, url: str): pass 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 @retry(auth_handler=_handle_get_error) async def _make_request( self, end_url: str, method: str, params: Union[dict, List[tuple], None] = None, body: Dict[str, Any] = None, ) -> Union[dict, list]: """Make http GET request to Atlas API.""" if self.token_expires_in <= datetime.utcnow(): await self._set_token() headers: dict = {"Authorization": self.token} headers.update(self.headers) return await make_request( self.session, end_url, method, params=params, body=body, headers=headers, error_cls=self.error_cls )