From 980ef71074e0d04c92a29f1ea7d3aea5440075de Mon Sep 17 00:00:00 2001 From: AG3NTZ3R0 Date: Fri, 23 Feb 2024 19:59:57 -0500 Subject: [PATCH] Implictly creates a translation table to reduce CNBC API requests --- README.md | 12 +++++--- src/cnbc/api_wrapper.py | 52 +++++++++++++++++++++++++++++++++- tests/cnbc/test_api_wrapper.py | 44 ++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 68d837d..9ab05ed 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ pip install cnbc ## Utilization ### APIWrapper -The APIWrapper class is used to make requests to the CNBC API.
+The `APIWrapper` class is used to make requests to the CNBC API.
Note: A majority of the CNBC API endpoints require parameters. These must be set by an additional instruction. ```python @@ -36,12 +36,16 @@ api_wrapper = APIWrapper( # The APIWrapper class will supply the required parameters for the configured CNBC API endpoint. api_wrapper_params = api_wrapper.params api_wrapper_params['symbol'] = 'AAPL' -api_wrapper.params = api_wrapper_params # The APIWrapper class will make a request to the CNBC API and return the response in JSON. json_response = api_wrapper.request() # The APIWrapper class can be repurposed to make multiple requests to the CNBC API. api_wrapper.endpoint = Endpoints.GET_SUMMARY -api_wrapper.params = {'issueIds': json_response['issueId']} +api_wrapper_params = api_wrapper.params +api_wrapper_params['issueIds'] = json_response['issueId'] json_response = api_wrapper.request() -``` \ No newline at end of file +``` + +#### Translate Endpoint +A majority of the CNBC API endpoints require an `issueId` or `issueIds` parameter. The translate endpoint is used to convert a symbol to an issueId.
+Note: The `APIWrapper` class contains a translation table which can be loaded and saved to a file to reduce the number of requests to the CNBC API. \ No newline at end of file diff --git a/src/cnbc/api_wrapper.py b/src/cnbc/api_wrapper.py index 338a473..444ec27 100644 --- a/src/cnbc/api_wrapper.py +++ b/src/cnbc/api_wrapper.py @@ -1,6 +1,7 @@ """ A module to make API requests. """ +import json import requests from .endpoints import Endpoints @@ -25,10 +26,14 @@ def __init__(self, api_key: str, endpoint: Endpoints, timeout: int = 10): self._headers: dict[str, str] self._timeout: int + self._translate_table: dict[str, str] + self._endpoint, self._params = endpoint.value self._headers = {'x-rapidapi-host': Endpoints.HOST.value, 'x-rapidapi-key': api_key} self._timeout = timeout + self._translation_table = {} + def _safe_delete(self): """ Safely delete the attributes. @@ -63,6 +68,7 @@ def params(self): @params.setter def params(self, params: dict[str, str]): try: + # Update the parameters if the input parameters match the endpoint parameters. if set(self._params.keys()) == set(params.keys()): self._params.update(params) else: @@ -106,16 +112,60 @@ def timeout(self, timeout: int): def timeout(self): del self._timeout + @property + def translation_table(self): + """ + Get the translation table of the API request. + :return: The translation table of the API request. + """ + return self._translation_table + + @translation_table.setter + def translation_table(self, translation_table: dict): + self._translation_table = translation_table + + @translation_table.deleter + def translation_table(self): + del self._translation_table + + def translation_table_load(self, file_path: str): + """ + Load the translation table from a file. + :param file_path: The file path of the translation table. + :return: None + """ + with open(file_path, "r") as file: + self._translation_table = json.load(file) + + def translation_table_save(self, file_path: str): + """ + Save the translation table to a file. + :param file_path: The file path of the translation table. + :return: None + """ + with open(file_path, "w") as file: + json.dump(self._translation_table, file) + def request(self) -> dict: """ Make an API request. :return: API response in JSON. """ + # If the endpoint is the translate endpoint, then check if the symbol is in the translation table. + if self._endpoint == Endpoints.TRANSLATE.get_endpoint(): + # If the symbol is in the translation table, then return a faux JSON response. + if issueId := self._translation_table.get(self._params['symbol']): + return {'issueId': issueId, 'errorMessage': '', 'errorCode': ''} + with requests.request("GET", self._endpoint, headers=self._headers, params=self._params, timeout=self._timeout) as response: try: response.raise_for_status() - return response.json() + response_json = response.json() + # If the endpoint is the translate endpoint, then update the translation table. + if self._endpoint == Endpoints.TRANSLATE.get_endpoint(): + self._translation_table[self._params['symbol']] = response_json['issueId'] + return response_json except requests.exceptions.HTTPError as e: raise APIRequestException(response.status_code, response.text) from e except requests.exceptions.RequestException as e: diff --git a/tests/cnbc/test_api_wrapper.py b/tests/cnbc/test_api_wrapper.py index 8a058fd..54c198c 100644 --- a/tests/cnbc/test_api_wrapper.py +++ b/tests/cnbc/test_api_wrapper.py @@ -54,3 +54,47 @@ def test_request(self, mock_request: MagicMock): headers=endpoint.headers, params=endpoint.params, timeout=endpoint.timeout ) self.assertEqual({"key": "value"}, response) + + def test_request_translate_translation_table(self): + """ + Test the request method with the translate endpoint with a translation table. + :return: None + """ + translation_table = {'AAPL': '123'} + json_response_expected = {'issueId': '123', 'errorMessage': '', 'errorCode': ''} + + api_wrapper = APIWrapper('API_KEY', Endpoints.TRANSLATE) + api_wrapper._translation_table = translation_table + api_wrapper_params = api_wrapper.params + api_wrapper_params['symbol'] = 'AAPL' + json_response = api_wrapper.request() + + self.assertEqual(translation_table, api_wrapper.translation_table) + self.assertEqual(json_response_expected, json_response) + + @patch('src.cnbc.api_wrapper.requests.request') + def test_request_translate_translation_table_empty(self, mock_request: MagicMock): + """ + Test the request method with the translate endpoint without a translation table. + :param mock_request: The mock request. + :return: None + """ + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = {'issueId': '123', 'errorMessage': '', 'errorCode': ''} + + mock_request.return_value.__enter__.return_value = mock_response + + translation_table = {'AAPL': '123'} + + api_wrapper = APIWrapper('API_KEY', Endpoints.TRANSLATE) + api_wrapper_params = api_wrapper.params + api_wrapper_params['symbol'] = 'AAPL' + json_response = api_wrapper.request() + + mock_request.assert_called_once_with( + "GET", api_wrapper.endpoint, + headers=api_wrapper.headers, params=api_wrapper.params, timeout=api_wrapper.timeout + ) + self.assertEqual(translation_table, api_wrapper.translation_table) + self.assertEqual(mock_response.json.return_value, json_response)