from abc import ABC, abstractmethod from datetime import datetime, timedelta from typing import Any, Dict, List, Optional, Type, Union import requests # type: ignore import config as base_config from utils.external_clients.constants import HTTP_AUTH_ERROR from utils.external_clients.exceptions import APIError from utils.external_clients.utils import make_request, retry class BaseClient(ABC): """Base API app_client.""" error_cls: Type[APIError] = APIError def __init__(self, session: requests.Session, config): self.session = session self.config = config self.token = None self.token_expires_in = datetime.min self.token_last_updated_at = datetime.min self.headers = {"Client-Service": base_config.SERVICE, "Client-Environment": base_config.ENVIRONMENT} 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, } if self.token_expires_in > datetime.utcnow() and ( not last_updated_at or last_updated_at < self.token_last_updated_at ): return resp_data = 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 def _make_end_url(self, url: str): pass def _handle_get_error(self, e: APIError): """Handle get auth error.""" if e.original_status_code == HTTP_AUTH_ERROR: self._set_token(self.token_last_updated_at) else: raise e @retry(auth_handler=_handle_get_error) def _make_request( self, end_url: str, method: str, params: Union[dict, List[tuple], None] = None, body: Optional[Dict[str, Any]] = None, ) -> Union[dict, list]: """Make http request to external API via M2M token.""" if self.token_expires_in <= datetime.utcnow(): self._set_token() headers: dict = {"Authorization": self.token} headers.update(self.headers) return make_request( self.session, end_url, method, params=params, body=body, headers=headers, error_cls=self.error_cls )