"""Parent class for ows services.""" from typing import Any from typing import TypeVar from httpx import Response from lambdacommon.common_config import logger from owsclient import M2MTokenManager from owsclient import OwsClient from secrets_manager.python_ext import PythonSecretsManager from config import APPLICATION_NAME from config import ENVIRONMENT from config import OWS_CLIENT_TOKEN from src.utils.constants import DEFAULT_HEADERS from src.utils.constants import RequestMethod from src.utils.error_handling import OwsServiceException class OwsService: """Ows parent class on which clients will inherit.""" _service: str _secrets_manager = PythonSecretsManager(region_name='us-east-1') _m2m_token_manager = M2MTokenManager( secrets_manager=_secrets_manager, environment=ENVIRONMENT, service_name=APPLICATION_NAME, ) _ows_client = OwsClient( environment=ENVIRONMENT, service_name=APPLICATION_NAME, m2m_token_manager=_m2m_token_manager ) @classmethod def get(cls, path: str, params: dict | None = None, **options: Any) -> dict | list[dict] | None: """Make a GET request and return the response. Args: path (str): the url of the request. params (dict): query parameters options (dict): keyword arguments sent to the client request parameters. Returns: dict, none, list[dict]: an optional json response in the form of a dict or list[dict]. """ response = cls._ows_client.get( cls._service, path=path, headers=cls._get_request_headers(), params=params, **options ) return cls._process_request(path, RequestMethod.GET, response, params=params) @classmethod def put(cls, path: str, body: dict) -> dict | None: """Make a PUT request and return the response. Args: path (str): the url of the request. body (dict): keyword arguments for the body of the request. Returns: dict: a json response in the form of a dict. """ response = cls._ows_client.put( cls._service, path=path, headers=cls._get_request_headers(), json=body ) return cls._process_request(path, RequestMethod.PUT, response, body=body) @classmethod def _get_request_headers(cls) -> dict: """Get the headers for each request. Returns: dict: Request headers. """ headers = DEFAULT_HEADERS if OWS_CLIENT_TOKEN: headers['authorization'] = f'Bearer {OWS_CLIENT_TOKEN}' return headers @classmethod def _process_request( cls, path: str, method: RequestMethod, response: Response, params: dict | None = None, body: dict | None = None, ) -> dict | None: """Process a request by logging it, checking the response status and returning it. Args: path (str): the path of the request. method (RequestMethod): the request method. response (Response): the response returned from the endpoint. params (dict | None): the query parameters of the request. body (dict | None): the body of the request being sent. Returns: dict: response data converted to json. """ headers = ' '.join([f'{h[0]}:{h[1]}' for h in DEFAULT_HEADERS.items()]) status = response.status_code body_str = str(body) if body else '' text = response.text params_str = '?' + '&'.join([f'{k}={v}' for k, v in params.items()]) if params else '' logger.info(f'{method} {cls._service} {path}{params_str} ({headers}) {body_str}') if response.status_code > 299: message = ( f'{cls._service} error: {status} response from' f' {method.value} {path}{body_str}: {text}' ) raise OwsServiceException(message, status) data = response.json() logger.info(f'{response.status_code} ' + str(data)) return data T = TypeVar('T') @classmethod def validate_response_type(cls, response: dict | list | None, expected_type: type[T]) -> T: """Validate that the response is in the expected type. Args: response (dict | list): response to validate expected_type (Type[T]): the type we expect the response to be Returns: T: the response as expected type Raises: OwsServiceException: If the response is not the expected type. """ if not isinstance(response, expected_type): raise OwsServiceException( f'{cls._service} error: Unexpected response type {type(response)}', 500 ) return response