From f6a767fcbf16ef2d2d1fcae0c2fcbf0e9abaa6a7 Mon Sep 17 00:00:00 2001 From: Matjaz Pirnovar Date: Mon, 8 Aug 2022 14:32:56 -0700 Subject: [PATCH 1/9] feat: add odp rest api manager --- optimizely/helpers/enums.py | 2 +- optimizely/odp/odp_event.py | 27 ++++++ optimizely/odp/zaius_rest_api_manager.py | 86 +++++++++++++++++ tests/helpers_for_tests.py | 25 +++++ tests/test_odp_zaius_graphql_api_manager.py | 46 ++++----- tests/test_odp_zaius_rest_api_manager.py | 100 ++++++++++++++++++++ 6 files changed, 258 insertions(+), 28 deletions(-) create mode 100644 optimizely/odp/odp_event.py create mode 100644 optimizely/odp/zaius_rest_api_manager.py create mode 100644 tests/helpers_for_tests.py create mode 100644 tests/test_odp_zaius_rest_api_manager.py diff --git a/optimizely/helpers/enums.py b/optimizely/helpers/enums.py index a82d6a98..ab63d1e3 100644 --- a/optimizely/helpers/enums.py +++ b/optimizely/helpers/enums.py @@ -122,7 +122,7 @@ class Errors: 'This version of the Python SDK does not support the given datafile version: "{}".') INVALID_SEGMENT_IDENTIFIER = 'Audience segments fetch failed (invalid identifier).' FETCH_SEGMENTS_FAILED = 'Audience segments fetch failed ({}).' - ODP_EVENT_FAILED = 'ODP event send failed (invalid url).' + ODP_EVENT_FAILED = 'ODP event send failed ({}).' ODP_NOT_ENABLED = 'ODP is not enabled. ' diff --git a/optimizely/odp/odp_event.py b/optimizely/odp/odp_event.py new file mode 100644 index 00000000..23015db5 --- /dev/null +++ b/optimizely/odp/odp_event.py @@ -0,0 +1,27 @@ +# Copyright 2022, Optimizely +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from typing import Any, Dict + + +class OdpEvent: + """ Representation of an odp event which can be sent to the Optimizely odp platform. """ + + def __init__(self, type: str, action: str, + identifiers: Dict[str, str], data: Dict[str, Any]) -> None: + self.type = type, + self.action = action, + self.identifiers = identifiers, + self.data = data diff --git a/optimizely/odp/zaius_rest_api_manager.py b/optimizely/odp/zaius_rest_api_manager.py new file mode 100644 index 00000000..77a4f50d --- /dev/null +++ b/optimizely/odp/zaius_rest_api_manager.py @@ -0,0 +1,86 @@ +# Copyright 2022, Optimizely +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +import json +from typing import Optional, List + +import requests +from requests.exceptions import RequestException, ConnectionError, Timeout, JSONDecodeError, InvalidURL + +from optimizely import logger as optimizely_logger +from optimizely.helpers.enums import Errors, OdpRestApiConfig +from optimizely.odp.odp_event import OdpEvent + +""" + ODP REST Events API + - https://api.zaius.com/v3/events + - test ODP public API key = "W4WzcEs-ABgXorzY7h1LCQ" + + [Event Request] + curl -i -H 'Content-Type: application/json' -H 'x-api-key: W4WzcEs-ABgXorzY7h1LCQ' -X POST -d '{"type":"fullstack","action":"identified","identifiers":{"vuid": "123","fs_user_id": "abc"},"data":{"idempotence_id":"xyz","source":"swift-sdk"}}' https://api.zaius.com/v3/events + [Event Response] + {"title":"Accepted","status":202,"timestamp":"2022-06-30T20:59:52.046Z"} +""" + + +class ZaiusRestApiManager: + """Provides an internal service for ODP event REST api access.""" + + def __init__(self, logger: Optional[optimizely_logger.Logger] = None): + self.logger = logger or optimizely_logger.NoOpLogger() + + def sendOdpEvents(self, api_key: str, api_host: str, events: List[OdpEvent]) -> Optional[bool]: + """ + Dispatch the event being represented by the OdpEvent object. + + Args: + api_key: public api key + api_host: domain url of the host + events: list of odp events to be sent to optimizely's odp platform. + + Returns: + retry is True - if network or server error (5xx), otherwise False + """ + can_retry: bool = True + url = f'{api_host}/v3/events' + request_headers = {'content-type': 'application/json', 'x-api-key': api_key} + + try: + response = requests.post(url=url, + headers=request_headers, + data=json.dumps(events), + timeout=OdpRestApiConfig.REQUEST_TIMEOUT) + + response.raise_for_status() + can_retry = False + + except (ConnectionError, Timeout): + self.logger.error(Errors.ODP_EVENT_FAILED.format('network error')) + # we do retry, can_retry = True + except JSONDecodeError: + self.logger.error(Errors.ODP_EVENT_FAILED.format('JSON decode error')) + can_retry = False + except InvalidURL: + self.logger.error(Errors.ODP_EVENT_FAILED.format('invalid URL')) + can_retry = False + except RequestException as err: + if 400 <= err.response.status_code < 500: + self.logger.error(Errors.ODP_EVENT_FAILED.format(err)) + can_retry = False + else: + self.logger.error(Errors.ODP_EVENT_FAILED.format(err)) + # we do retry, can_retry = True + finally: + return can_retry diff --git a/tests/helpers_for_tests.py b/tests/helpers_for_tests.py new file mode 100644 index 00000000..a2e9641c --- /dev/null +++ b/tests/helpers_for_tests.py @@ -0,0 +1,25 @@ +# Copyright 2022, Optimizely +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http:#www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from requests import Response +from typing import Optional + + +def fake_server_response(status_code: int = None, content: bytes = None, url: str = None) -> Optional[Response]: + """Mock the server response.""" + response = Response() + response.status_code = status_code + if content: + response._content = content.encode('utf-8') + response.url = url + return response diff --git a/tests/test_odp_zaius_graphql_api_manager.py b/tests/test_odp_zaius_graphql_api_manager.py index cb728962..fcfe95cd 100644 --- a/tests/test_odp_zaius_graphql_api_manager.py +++ b/tests/test_odp_zaius_graphql_api_manager.py @@ -14,10 +14,10 @@ import json from unittest import mock -from requests import Response from requests import exceptions as request_exception -from optimizely.helpers.enums import OdpGraphQLApiConfig +from tests.helpers_for_tests import fake_server_response +from optimizely.helpers.enums import OdpGraphQLApiConfig from optimizely.odp.zaius_graphql_api_manager import ZaiusGraphQLApiManager from . import base @@ -50,7 +50,7 @@ def test_fetch_qualified_segments__valid_request(self): def test_fetch_qualified_segments__success(self): with mock.patch('requests.post') as mock_request_post: mock_request_post.return_value = \ - self.fake_server_response(status_code=200, content=self.good_response_data) + fake_server_response(status_code=200, content=self.good_response_data) api = ZaiusGraphQLApiManager() response = api.fetch_segments(api_key=self.api_key, @@ -65,7 +65,7 @@ def test_fetch_qualified_segments__node_missing(self): with mock.patch('requests.post') as mock_request_post, \ mock.patch('optimizely.logger') as mock_logger: mock_request_post.return_value = \ - self.fake_server_response(status_code=200, content=self.node_missing_response_data) + fake_server_response(status_code=200, content=self.node_missing_response_data) api = ZaiusGraphQLApiManager(logger=mock_logger) api.fetch_segments(api_key=self.api_key, @@ -81,8 +81,8 @@ def test_fetch_qualified_segments__mixed_missing_keys(self): with mock.patch('requests.post') as mock_request_post, \ mock.patch('optimizely.logger') as mock_logger: mock_request_post.return_value = \ - self.fake_server_response(status_code=200, - content=self.mixed_missing_keys_response_data) + fake_server_response(status_code=200, + content=self.mixed_missing_keys_response_data) api = ZaiusGraphQLApiManager(logger=mock_logger) api.fetch_segments(api_key=self.api_key, @@ -97,7 +97,7 @@ def test_fetch_qualified_segments__mixed_missing_keys(self): def test_fetch_qualified_segments__success_with_empty_segments(self): with mock.patch('requests.post') as mock_request_post: mock_request_post.return_value = \ - self.fake_server_response(status_code=200, content=self.good_empty_response_data) + fake_server_response(status_code=200, content=self.good_empty_response_data) api = ZaiusGraphQLApiManager() response = api.fetch_segments(api_key=self.api_key, @@ -112,8 +112,8 @@ def test_fetch_qualified_segments__invalid_identifier(self): with mock.patch('requests.post') as mock_request_post, \ mock.patch('optimizely.logger') as mock_logger: mock_request_post.return_value = \ - self.fake_server_response(status_code=200, - content=self.invalid_identifier_response_data) + fake_server_response(status_code=200, + content=self.invalid_identifier_response_data) api = ZaiusGraphQLApiManager(logger=mock_logger) api.fetch_segments(api_key=self.api_key, @@ -129,7 +129,7 @@ def test_fetch_qualified_segments__other_exception(self): with mock.patch('requests.post') as mock_request_post, \ mock.patch('optimizely.logger') as mock_logger: mock_request_post.return_value = \ - self.fake_server_response(status_code=200, content=self.other_exception_response_data) + fake_server_response(status_code=200, content=self.other_exception_response_data) api = ZaiusGraphQLApiManager(logger=mock_logger) api.fetch_segments(api_key=self.api_key, @@ -145,7 +145,7 @@ def test_fetch_qualified_segments__bad_response(self): with mock.patch('requests.post') as mock_request_post, \ mock.patch('optimizely.logger') as mock_logger: mock_request_post.return_value = \ - self.fake_server_response(status_code=200, content=self.bad_response_data) + fake_server_response(status_code=200, content=self.bad_response_data) api = ZaiusGraphQLApiManager(logger=mock_logger) api.fetch_segments(api_key=self.api_key, @@ -161,7 +161,7 @@ def test_fetch_qualified_segments__name_invalid(self): with mock.patch('requests.post') as mock_request_post, \ mock.patch('optimizely.logger') as mock_logger: mock_request_post.return_value = \ - self.fake_server_response(status_code=200, content=self.name_invalid_response_data) + fake_server_response(status_code=200, content=self.name_invalid_response_data) api = ZaiusGraphQLApiManager(logger=mock_logger) api.fetch_segments(api_key=self.api_key, @@ -176,7 +176,8 @@ def test_fetch_qualified_segments__name_invalid(self): def test_fetch_qualified_segments__invalid_key(self): with mock.patch('requests.post') as mock_request_post, \ mock.patch('optimizely.logger') as mock_logger: - mock_request_post.return_value.json.return_value = json.loads(self.invalid_edges_key_response_data) + mock_request_post.return_value = fake_server_response(status_code=200, + content=self.invalid_edges_key_response_data) api = ZaiusGraphQLApiManager(logger=mock_logger) api.fetch_segments(api_key=self.api_key, @@ -191,7 +192,8 @@ def test_fetch_qualified_segments__invalid_key(self): def test_fetch_qualified_segments__invalid_key_in_error_body(self): with mock.patch('requests.post') as mock_request_post, \ mock.patch('optimizely.logger') as mock_logger: - mock_request_post.return_value.json.return_value = json.loads(self.invalid_key_for_error_response_data) + mock_request_post.return_value = fake_server_response(status_code=200, + content=self.invalid_key_for_error_response_data) api = ZaiusGraphQLApiManager(logger=mock_logger) api.fetch_segments(api_key=self.api_key, @@ -221,7 +223,7 @@ def test_fetch_qualified_segments__network_error(self): def test_fetch_qualified_segments__400(self): with mock.patch('requests.post') as mock_request_post, \ mock.patch('optimizely.logger') as mock_logger: - mock_request_post.return_value = self.fake_server_response(status_code=403, url=self.api_host) + mock_request_post.return_value = fake_server_response(status_code=403, url=self.api_host) api = ZaiusGraphQLApiManager(logger=mock_logger) api.fetch_segments(api_key=self.api_key, @@ -241,7 +243,7 @@ def test_fetch_qualified_segments__400(self): def test_fetch_qualified_segments__500(self): with mock.patch('requests.post') as mock_request_post, \ mock.patch('optimizely.logger') as mock_logger: - mock_request_post.return_value = self.fake_server_response(status_code=500, url=self.api_host) + mock_request_post.return_value = fake_server_response(status_code=500, url=self.api_host) api = ZaiusGraphQLApiManager(logger=mock_logger) api.fetch_segments(api_key=self.api_key, @@ -265,17 +267,7 @@ def test_make_subset_filter(self): self.assertEqual("(subset:[\"a\", \"b\", \"c\"])", api.make_subset_filter(["a", "b", "c"])) self.assertEqual("(subset:[\"a\", \"b\", \"don't\"])", api.make_subset_filter(["a", "b", "don't"])) - # fake server response function and test json responses - - @staticmethod - def fake_server_response(status_code=None, content=None, url=None): - """Mock the server response.""" - response = Response() - response.status_code = status_code - if content: - response._content = content.encode('utf-8') - response.url = url - return response + # test json responses good_response_data = """ { diff --git a/tests/test_odp_zaius_rest_api_manager.py b/tests/test_odp_zaius_rest_api_manager.py new file mode 100644 index 00000000..a26c4cdc --- /dev/null +++ b/tests/test_odp_zaius_rest_api_manager.py @@ -0,0 +1,100 @@ +# Copyright 2022, Optimizely +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http:#www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +from unittest import mock + +from requests import exceptions as request_exception + +from tests.helpers_for_tests import fake_server_response +from optimizely.helpers.enums import OdpRestApiConfig +from optimizely.odp.zaius_rest_api_manager import ZaiusRestApiManager +from . import base + + +class ZaiusRestApiManagerTest(base.BaseTest): + user_key = "vuid" + user_value = "test-user-value" + api_key = "test-api-key" + api_host = "test-host" + + events = [ + {"type": "t1", "action": "a1", "identifiers": {"id-key-1": "id-value-1"}, "data": {"key-1": "value1"}}, + {"type": "t2", "action": "a2", "identifiers": {"id-key-2": "id-value-2"}, "data": {"key-2": "value2"}}, + ] + + def test_send_odp_events__valid_request(self): + with mock.patch('requests.post') as mock_request_post: + api = ZaiusRestApiManager() + api.sendOdpEvents(api_key=self.api_key, + api_host=self.api_host, + events=self.events) + + request_headers = {'content-type': 'application/json', 'x-api-key': self.api_key} + mock_request_post.assert_called_once_with(url=self.api_host + "/v3/events", + headers=request_headers, + data=json.dumps(self.events), + timeout=OdpRestApiConfig.REQUEST_TIMEOUT) + + def testSendOdpEvents_success(self): + with mock.patch('requests.post') as mock_request_post: + mock_request_post.return_value = \ + fake_server_response(status_code=200) + + api = ZaiusRestApiManager() + response = api.sendOdpEvents(api_key=self.api_key, + api_host=self.api_host, + events=self.events) # content of events doesn't matter for the test + + self.assertFalse(response) + + def testSendOdpEvents_network_error_retry(self): + with mock.patch('requests.post', + side_effect=request_exception.ConnectionError('Connection error')) as mock_request_post, \ + mock.patch('optimizely.logger') as mock_logger: + api = ZaiusRestApiManager(logger=mock_logger) + response = api.sendOdpEvents(api_key=self.api_key, + api_host=self.api_host, + events=self.events) + + self.assertTrue(response) + mock_request_post.assert_called_once() + mock_logger.error.assert_called_once_with('ODP event send failed (network error).') + + def testSendOdpEvents_400_no_retry(self): + with mock.patch('requests.post') as mock_request_post, \ + mock.patch('optimizely.logger') as mock_logger: + mock_request_post.return_value = fake_server_response(status_code=403, url=self.api_host) + + api = ZaiusRestApiManager(logger=mock_logger) + response = api.sendOdpEvents(api_key=self.api_key, + api_host=self.api_host, + events=self.events) + + self.assertFalse(response) + mock_request_post.assert_called_once() + mock_logger.error.assert_called_once_with('ODP event send failed (403 Client Error: None for url: test-host).') + + def testSendOdpEvents_500_retry(self): + with mock.patch('requests.post') as mock_request_post, \ + mock.patch('optimizely.logger') as mock_logger: + mock_request_post.return_value = fake_server_response(status_code=500, url=self.api_host) + + api = ZaiusRestApiManager(logger=mock_logger) + response = api.sendOdpEvents(api_key=self.api_key, + api_host=self.api_host, + events=self.events) + + self.assertTrue(response) + mock_request_post.assert_called_once() + mock_logger.error.assert_called_once_with('ODP event send failed (500 Server Error: None for url: test-host).') From 0d254cb8db5ea23a65e49ddceedd6493cd6d1edd Mon Sep 17 00:00:00 2001 From: Matjaz Pirnovar Date: Mon, 8 Aug 2022 15:09:46 -0700 Subject: [PATCH 2/9] fix: fix linting, str type --- optimizely/odp/zaius_rest_api_manager.py | 4 +++- tests/helpers_for_tests.py | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/optimizely/odp/zaius_rest_api_manager.py b/optimizely/odp/zaius_rest_api_manager.py index 77a4f50d..4deb48dd 100644 --- a/optimizely/odp/zaius_rest_api_manager.py +++ b/optimizely/odp/zaius_rest_api_manager.py @@ -29,7 +29,9 @@ - test ODP public API key = "W4WzcEs-ABgXorzY7h1LCQ" [Event Request] - curl -i -H 'Content-Type: application/json' -H 'x-api-key: W4WzcEs-ABgXorzY7h1LCQ' -X POST -d '{"type":"fullstack","action":"identified","identifiers":{"vuid": "123","fs_user_id": "abc"},"data":{"idempotence_id":"xyz","source":"swift-sdk"}}' https://api.zaius.com/v3/events + curl -i -H 'Content-Type: application/json' -H 'x-api-key: W4WzcEs-ABgXorzY7h1LCQ' -X POST -d + '{"type":"fullstack","action":"identified","identifiers":{"vuid": "123","fs_user_id": "abc"}, + "data":{"idempotence_id":"xyz","source":"swift-sdk"}}' https://api.zaius.com/v3/events [Event Response] {"title":"Accepted","status":202,"timestamp":"2022-06-30T20:59:52.046Z"} """ diff --git a/tests/helpers_for_tests.py b/tests/helpers_for_tests.py index a2e9641c..faa1fea8 100644 --- a/tests/helpers_for_tests.py +++ b/tests/helpers_for_tests.py @@ -15,7 +15,8 @@ from typing import Optional -def fake_server_response(status_code: int = None, content: bytes = None, url: str = None) -> Optional[Response]: +def fake_server_response(status_code: Optional[int] = None, content: Optional[str] = None, + url: Optional[str] = None) -> Optional[Response]: """Mock the server response.""" response = Response() response.status_code = status_code From 92099d489c05af35066169b1c66163d5d6df68b1 Mon Sep 17 00:00:00 2001 From: Matjaz Pirnovar Date: Mon, 8 Aug 2022 15:11:42 -0700 Subject: [PATCH 3/9] fix white space --- optimizely/odp/zaius_rest_api_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/optimizely/odp/zaius_rest_api_manager.py b/optimizely/odp/zaius_rest_api_manager.py index 4deb48dd..aa872be8 100644 --- a/optimizely/odp/zaius_rest_api_manager.py +++ b/optimizely/odp/zaius_rest_api_manager.py @@ -29,7 +29,7 @@ - test ODP public API key = "W4WzcEs-ABgXorzY7h1LCQ" [Event Request] - curl -i -H 'Content-Type: application/json' -H 'x-api-key: W4WzcEs-ABgXorzY7h1LCQ' -X POST -d + curl -i -H 'Content-Type: application/json' -H 'x-api-key: W4WzcEs-ABgXorzY7h1LCQ' -X POST -d '{"type":"fullstack","action":"identified","identifiers":{"vuid": "123","fs_user_id": "abc"}, "data":{"idempotence_id":"xyz","source":"swift-sdk"}}' https://api.zaius.com/v3/events [Event Response] From c9db45a528896be9991f3f8e65fcaab2ff07bc0b Mon Sep 17 00:00:00 2001 From: Matjaz Pirnovar Date: Tue, 9 Aug 2022 23:45:17 -0700 Subject: [PATCH 4/9] addressed PR comments --- optimizely/odp/zaius_rest_api_manager.py | 43 +++++++---- tests/helpers_for_tests.py | 12 ++- tests/test_odp_zaius_rest_api_manager.py | 94 +++++++++++++++++------- 3 files changed, 102 insertions(+), 47 deletions(-) diff --git a/optimizely/odp/zaius_rest_api_manager.py b/optimizely/odp/zaius_rest_api_manager.py index aa872be8..6f0919e6 100644 --- a/optimizely/odp/zaius_rest_api_manager.py +++ b/optimizely/odp/zaius_rest_api_manager.py @@ -17,7 +17,7 @@ from typing import Optional, List import requests -from requests.exceptions import RequestException, ConnectionError, Timeout, JSONDecodeError, InvalidURL +from requests.exceptions import RequestException, ConnectionError, Timeout, InvalidURL, InvalidSchema from optimizely import logger as optimizely_logger from optimizely.helpers.enums import Errors, OdpRestApiConfig @@ -43,7 +43,7 @@ class ZaiusRestApiManager: def __init__(self, logger: Optional[optimizely_logger.Logger] = None): self.logger = logger or optimizely_logger.NoOpLogger() - def sendOdpEvents(self, api_key: str, api_host: str, events: List[OdpEvent]) -> Optional[bool]: + def send_odp_events(self, api_key: str, api_host: str, events: List[OdpEvent]) -> bool: """ Dispatch the event being represented by the OdpEvent object. @@ -55,34 +55,45 @@ def sendOdpEvents(self, api_key: str, api_host: str, events: List[OdpEvent]) -> Returns: retry is True - if network or server error (5xx), otherwise False """ - can_retry: bool = True + should_retry: bool = False url = f'{api_host}/v3/events' request_headers = {'content-type': 'application/json', 'x-api-key': api_key} + try: + payload_dict = json.dumps(events) + except TypeError as err: + self.logger.error(Errors.ODP_EVENT_FAILED.format(err)) + return should_retry + try: response = requests.post(url=url, headers=request_headers, - data=json.dumps(events), + data=payload_dict, timeout=OdpRestApiConfig.REQUEST_TIMEOUT) response.raise_for_status() - can_retry = False except (ConnectionError, Timeout): self.logger.error(Errors.ODP_EVENT_FAILED.format('network error')) - # we do retry, can_retry = True - except JSONDecodeError: - self.logger.error(Errors.ODP_EVENT_FAILED.format('JSON decode error')) - can_retry = False - except InvalidURL: + # we do retry on network errors + should_retry = True + except (InvalidURL, InvalidSchema, UnicodeError): + # The three exceptions combined catch different cases of invalid URL format. + # For example: + # UnicodeError catches double dot in URL: https://api.zaius..com + # InvalidURL catches extra characters such as forward slash: https:///api.zaius.com + # InvalidSchema catches extra prepended characters: XXhttps://api.zaius.com self.logger.error(Errors.ODP_EVENT_FAILED.format('invalid URL')) - can_retry = False except RequestException as err: if 400 <= err.response.status_code < 500: - self.logger.error(Errors.ODP_EVENT_FAILED.format(err)) - can_retry = False + if err.response.text: + # log response text if it exists + self.logger.error(Errors.ODP_EVENT_FAILED.format(err.response.text)) + else: + # otherwise log error message + self.logger.error(Errors.ODP_EVENT_FAILED.format(err)) else: self.logger.error(Errors.ODP_EVENT_FAILED.format(err)) - # we do retry, can_retry = True - finally: - return can_retry + # retry on 500 errors + should_retry = True + return should_retry diff --git a/tests/helpers_for_tests.py b/tests/helpers_for_tests.py index faa1fea8..313ed8c7 100644 --- a/tests/helpers_for_tests.py +++ b/tests/helpers_for_tests.py @@ -15,12 +15,16 @@ from typing import Optional -def fake_server_response(status_code: Optional[int] = None, content: Optional[str] = None, - url: Optional[str] = None) -> Optional[Response]: +def fake_server_response(status_code: int = None, content: Optional[str] = None, + url: str = None) -> Optional[Response]: """Mock the server response.""" response = Response() - response.status_code = status_code + + if status_code: + response.status_code = status_code if content: response._content = content.encode('utf-8') - response.url = url + if url: + response.url = url + return response diff --git a/tests/test_odp_zaius_rest_api_manager.py b/tests/test_odp_zaius_rest_api_manager.py index a26c4cdc..b69e3363 100644 --- a/tests/test_odp_zaius_rest_api_manager.py +++ b/tests/test_odp_zaius_rest_api_manager.py @@ -36,9 +36,9 @@ class ZaiusRestApiManagerTest(base.BaseTest): def test_send_odp_events__valid_request(self): with mock.patch('requests.post') as mock_request_post: api = ZaiusRestApiManager() - api.sendOdpEvents(api_key=self.api_key, - api_host=self.api_host, - events=self.events) + api.send_odp_events(api_key=self.api_key, + api_host=self.api_host, + events=self.events) request_headers = {'content-type': 'application/json', 'x-api-key': self.api_key} mock_request_post.assert_called_once_with(url=self.api_host + "/v3/events", @@ -46,55 +46,95 @@ def test_send_odp_events__valid_request(self): data=json.dumps(self.events), timeout=OdpRestApiConfig.REQUEST_TIMEOUT) - def testSendOdpEvents_success(self): + def test_send_odp_ovents_success(self): with mock.patch('requests.post') as mock_request_post: - mock_request_post.return_value = \ - fake_server_response(status_code=200) + # no need to moch urla nd content because we're not returning the response + mock_request_post.return_value = fake_server_response(status_code=200) api = ZaiusRestApiManager() - response = api.sendOdpEvents(api_key=self.api_key, - api_host=self.api_host, - events=self.events) # content of events doesn't matter for the test + should_retry = api.send_odp_events(api_key=self.api_key, + api_host=self.api_host, + events=self.events) # content of events doesn't matter for the test - self.assertFalse(response) + self.assertFalse(should_retry) + + def test_send_odp_events_invalid_json_no_retry(self): + events = {1, 2, 3} # using a set to trigger JSON-not-serializable error + + with mock.patch('requests.post') as mock_request_post, \ + mock.patch('optimizely.logger') as mock_logger: + api = ZaiusRestApiManager(logger=mock_logger) + should_retry = api.send_odp_events(api_key=self.api_key, + api_host=self.api_host, + events=events) + + self.assertFalse(should_retry) + mock_request_post.assert_not_called() + mock_logger.error.assert_called_once_with( + 'ODP event send failed (Object of type set is not JSON serializable).') + + def test_send_odp_events_invalid_url_no_retry(self): + invalid_url = 'https://*api.zaius.com' - def testSendOdpEvents_network_error_retry(self): + with mock.patch('requests.post', + side_effect=request_exception.InvalidURL('Invalid URL error')) as mock_request_post, \ + mock.patch('optimizely.logger') as mock_logger: + api = ZaiusRestApiManager(logger=mock_logger) + should_retry = api.send_odp_events(api_key=self.api_key, + api_host=invalid_url, + events=self.events) + + self.assertFalse(should_retry) + mock_request_post.assert_called_once() + mock_logger.error.assert_called_once_with('ODP event send failed (invalid URL).') + + def test_send_odp_events_network_error_retry(self): with mock.patch('requests.post', side_effect=request_exception.ConnectionError('Connection error')) as mock_request_post, \ mock.patch('optimizely.logger') as mock_logger: api = ZaiusRestApiManager(logger=mock_logger) - response = api.sendOdpEvents(api_key=self.api_key, - api_host=self.api_host, - events=self.events) + should_retry = api.send_odp_events(api_key=self.api_key, + api_host=self.api_host, + events=self.events) - self.assertTrue(response) + self.assertTrue(should_retry) mock_request_post.assert_called_once() mock_logger.error.assert_called_once_with('ODP event send failed (network error).') - def testSendOdpEvents_400_no_retry(self): + def test_send_odp_events_400_no_retry(self): with mock.patch('requests.post') as mock_request_post, \ mock.patch('optimizely.logger') as mock_logger: - mock_request_post.return_value = fake_server_response(status_code=403, url=self.api_host) + mock_request_post.return_value = fake_server_response(status_code=400, + url=self.api_host, + content=self.failure_response_data) api = ZaiusRestApiManager(logger=mock_logger) - response = api.sendOdpEvents(api_key=self.api_key, - api_host=self.api_host, - events=self.events) + should_retry = api.send_odp_events(api_key=self.api_key, + api_host=self.api_host, + events=self.events) - self.assertFalse(response) + self.assertFalse(should_retry) mock_request_post.assert_called_once() - mock_logger.error.assert_called_once_with('ODP event send failed (403 Client Error: None for url: test-host).') + mock_logger.error.assert_called_once_with('ODP event send failed ({"title":"Bad Request","status":400,' + '"timestamp":"2022-07-01T20:44:00.945Z","detail":{"invalids":' + '[{"event":0,"message":"missing \'type\' field"}]}}).') - def testSendOdpEvents_500_retry(self): + def test_send_odp_events_500_retry(self): with mock.patch('requests.post') as mock_request_post, \ mock.patch('optimizely.logger') as mock_logger: mock_request_post.return_value = fake_server_response(status_code=500, url=self.api_host) api = ZaiusRestApiManager(logger=mock_logger) - response = api.sendOdpEvents(api_key=self.api_key, - api_host=self.api_host, - events=self.events) + should_retry = api.send_odp_events(api_key=self.api_key, + api_host=self.api_host, + events=self.events) - self.assertTrue(response) + self.assertTrue(should_retry) mock_request_post.assert_called_once() mock_logger.error.assert_called_once_with('ODP event send failed (500 Server Error: None for url: test-host).') + + # test json responses + success_response_data = '{"title":"Accepted","status":202,"timestamp":"2022-07-01T16:04:06.786Z"}' + + failure_response_data = '{"title":"Bad Request","status":400,"timestamp":"2022-07-01T20:44:00.945Z",' \ + '"detail":{"invalids":[{"event":0,"message":"missing \'type\' field"}]}}' From 94e546b551745c46a49f17807da77160d1acae41 Mon Sep 17 00:00:00 2001 From: Matjaz Pirnovar Date: Wed, 10 Aug 2022 00:05:30 -0700 Subject: [PATCH 5/9] moved helper test funciton to base.py --- tests/base.py | 17 ++++++++++++++ tests/helpers_for_tests.py | 30 ------------------------ tests/test_odp_zaius_rest_api_manager.py | 13 +++++----- 3 files changed, 23 insertions(+), 37 deletions(-) delete mode 100644 tests/helpers_for_tests.py diff --git a/tests/base.py b/tests/base.py index e793d1c3..ede8acc9 100644 --- a/tests/base.py +++ b/tests/base.py @@ -13,6 +13,9 @@ import json import unittest +from typing import Optional + +from requests import Response from optimizely import optimizely @@ -28,6 +31,20 @@ def assertStrictTrue(self, to_assert): def assertStrictFalse(self, to_assert): self.assertIs(to_assert, False) + def fake_server_response(self, status_code: int = None, content: Optional[str] = None, + url: str = None) -> Optional[Response]: + """Mock the server response.""" + response = Response() + + if status_code: + response.status_code = status_code + if content: + response._content = content.encode('utf-8') + if url: + response.url = url + + return response + def setUp(self, config_dict='config_dict'): self.config_dict = { 'revision': '42', diff --git a/tests/helpers_for_tests.py b/tests/helpers_for_tests.py deleted file mode 100644 index 313ed8c7..00000000 --- a/tests/helpers_for_tests.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright 2022, Optimizely -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http:#www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from requests import Response -from typing import Optional - - -def fake_server_response(status_code: int = None, content: Optional[str] = None, - url: str = None) -> Optional[Response]: - """Mock the server response.""" - response = Response() - - if status_code: - response.status_code = status_code - if content: - response._content = content.encode('utf-8') - if url: - response.url = url - - return response diff --git a/tests/test_odp_zaius_rest_api_manager.py b/tests/test_odp_zaius_rest_api_manager.py index b69e3363..d82026cd 100644 --- a/tests/test_odp_zaius_rest_api_manager.py +++ b/tests/test_odp_zaius_rest_api_manager.py @@ -16,7 +16,6 @@ from requests import exceptions as request_exception -from tests.helpers_for_tests import fake_server_response from optimizely.helpers.enums import OdpRestApiConfig from optimizely.odp.zaius_rest_api_manager import ZaiusRestApiManager from . import base @@ -48,8 +47,8 @@ def test_send_odp_events__valid_request(self): def test_send_odp_ovents_success(self): with mock.patch('requests.post') as mock_request_post: - # no need to moch urla nd content because we're not returning the response - mock_request_post.return_value = fake_server_response(status_code=200) + # no need to mock url and content because we're not returning the response + mock_request_post.return_value = self.fake_server_response(status_code=200) api = ZaiusRestApiManager() should_retry = api.send_odp_events(api_key=self.api_key, @@ -104,9 +103,9 @@ def test_send_odp_events_network_error_retry(self): def test_send_odp_events_400_no_retry(self): with mock.patch('requests.post') as mock_request_post, \ mock.patch('optimizely.logger') as mock_logger: - mock_request_post.return_value = fake_server_response(status_code=400, - url=self.api_host, - content=self.failure_response_data) + mock_request_post.return_value = self.fake_server_response(status_code=400, + url=self.api_host, + content=self.failure_response_data) api = ZaiusRestApiManager(logger=mock_logger) should_retry = api.send_odp_events(api_key=self.api_key, @@ -122,7 +121,7 @@ def test_send_odp_events_400_no_retry(self): def test_send_odp_events_500_retry(self): with mock.patch('requests.post') as mock_request_post, \ mock.patch('optimizely.logger') as mock_logger: - mock_request_post.return_value = fake_server_response(status_code=500, url=self.api_host) + mock_request_post.return_value = self.fake_server_response(status_code=500, url=self.api_host) api = ZaiusRestApiManager(logger=mock_logger) should_retry = api.send_odp_events(api_key=self.api_key, From 14a1f82e188d539e6cb704447f134319e7af755a Mon Sep 17 00:00:00 2001 From: Matjaz Pirnovar Date: Wed, 10 Aug 2022 00:12:22 -0700 Subject: [PATCH 6/9] fix graphql tests becasue helper method moved to base.py --- tests/test_odp_zaius_graphql_api_manager.py | 33 ++++++++++----------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/tests/test_odp_zaius_graphql_api_manager.py b/tests/test_odp_zaius_graphql_api_manager.py index fcfe95cd..5ac85b2a 100644 --- a/tests/test_odp_zaius_graphql_api_manager.py +++ b/tests/test_odp_zaius_graphql_api_manager.py @@ -16,7 +16,6 @@ from requests import exceptions as request_exception -from tests.helpers_for_tests import fake_server_response from optimizely.helpers.enums import OdpGraphQLApiConfig from optimizely.odp.zaius_graphql_api_manager import ZaiusGraphQLApiManager from . import base @@ -50,7 +49,7 @@ def test_fetch_qualified_segments__valid_request(self): def test_fetch_qualified_segments__success(self): with mock.patch('requests.post') as mock_request_post: mock_request_post.return_value = \ - fake_server_response(status_code=200, content=self.good_response_data) + self.fake_server_response(status_code=200, content=self.good_response_data) api = ZaiusGraphQLApiManager() response = api.fetch_segments(api_key=self.api_key, @@ -65,7 +64,7 @@ def test_fetch_qualified_segments__node_missing(self): with mock.patch('requests.post') as mock_request_post, \ mock.patch('optimizely.logger') as mock_logger: mock_request_post.return_value = \ - fake_server_response(status_code=200, content=self.node_missing_response_data) + self.fake_server_response(status_code=200, content=self.node_missing_response_data) api = ZaiusGraphQLApiManager(logger=mock_logger) api.fetch_segments(api_key=self.api_key, @@ -81,8 +80,8 @@ def test_fetch_qualified_segments__mixed_missing_keys(self): with mock.patch('requests.post') as mock_request_post, \ mock.patch('optimizely.logger') as mock_logger: mock_request_post.return_value = \ - fake_server_response(status_code=200, - content=self.mixed_missing_keys_response_data) + self.fake_server_response(status_code=200, + content=self.mixed_missing_keys_response_data) api = ZaiusGraphQLApiManager(logger=mock_logger) api.fetch_segments(api_key=self.api_key, @@ -97,7 +96,7 @@ def test_fetch_qualified_segments__mixed_missing_keys(self): def test_fetch_qualified_segments__success_with_empty_segments(self): with mock.patch('requests.post') as mock_request_post: mock_request_post.return_value = \ - fake_server_response(status_code=200, content=self.good_empty_response_data) + self.fake_server_response(status_code=200, content=self.good_empty_response_data) api = ZaiusGraphQLApiManager() response = api.fetch_segments(api_key=self.api_key, @@ -112,8 +111,8 @@ def test_fetch_qualified_segments__invalid_identifier(self): with mock.patch('requests.post') as mock_request_post, \ mock.patch('optimizely.logger') as mock_logger: mock_request_post.return_value = \ - fake_server_response(status_code=200, - content=self.invalid_identifier_response_data) + self.fake_server_response(status_code=200, + content=self.invalid_identifier_response_data) api = ZaiusGraphQLApiManager(logger=mock_logger) api.fetch_segments(api_key=self.api_key, @@ -129,7 +128,7 @@ def test_fetch_qualified_segments__other_exception(self): with mock.patch('requests.post') as mock_request_post, \ mock.patch('optimizely.logger') as mock_logger: mock_request_post.return_value = \ - fake_server_response(status_code=200, content=self.other_exception_response_data) + self.fake_server_response(status_code=200, content=self.other_exception_response_data) api = ZaiusGraphQLApiManager(logger=mock_logger) api.fetch_segments(api_key=self.api_key, @@ -145,7 +144,7 @@ def test_fetch_qualified_segments__bad_response(self): with mock.patch('requests.post') as mock_request_post, \ mock.patch('optimizely.logger') as mock_logger: mock_request_post.return_value = \ - fake_server_response(status_code=200, content=self.bad_response_data) + self.fake_server_response(status_code=200, content=self.bad_response_data) api = ZaiusGraphQLApiManager(logger=mock_logger) api.fetch_segments(api_key=self.api_key, @@ -161,7 +160,7 @@ def test_fetch_qualified_segments__name_invalid(self): with mock.patch('requests.post') as mock_request_post, \ mock.patch('optimizely.logger') as mock_logger: mock_request_post.return_value = \ - fake_server_response(status_code=200, content=self.name_invalid_response_data) + self.fake_server_response(status_code=200, content=self.name_invalid_response_data) api = ZaiusGraphQLApiManager(logger=mock_logger) api.fetch_segments(api_key=self.api_key, @@ -176,8 +175,8 @@ def test_fetch_qualified_segments__name_invalid(self): def test_fetch_qualified_segments__invalid_key(self): with mock.patch('requests.post') as mock_request_post, \ mock.patch('optimizely.logger') as mock_logger: - mock_request_post.return_value = fake_server_response(status_code=200, - content=self.invalid_edges_key_response_data) + mock_request_post.return_value = self.fake_server_response(status_code=200, + content=self.invalid_edges_key_response_data) api = ZaiusGraphQLApiManager(logger=mock_logger) api.fetch_segments(api_key=self.api_key, @@ -192,8 +191,8 @@ def test_fetch_qualified_segments__invalid_key(self): def test_fetch_qualified_segments__invalid_key_in_error_body(self): with mock.patch('requests.post') as mock_request_post, \ mock.patch('optimizely.logger') as mock_logger: - mock_request_post.return_value = fake_server_response(status_code=200, - content=self.invalid_key_for_error_response_data) + mock_request_post.return_value = self.fake_server_response(status_code=200, + content=self.invalid_key_for_error_response_data) api = ZaiusGraphQLApiManager(logger=mock_logger) api.fetch_segments(api_key=self.api_key, @@ -223,7 +222,7 @@ def test_fetch_qualified_segments__network_error(self): def test_fetch_qualified_segments__400(self): with mock.patch('requests.post') as mock_request_post, \ mock.patch('optimizely.logger') as mock_logger: - mock_request_post.return_value = fake_server_response(status_code=403, url=self.api_host) + mock_request_post.return_value = self.fake_server_response(status_code=403, url=self.api_host) api = ZaiusGraphQLApiManager(logger=mock_logger) api.fetch_segments(api_key=self.api_key, @@ -243,7 +242,7 @@ def test_fetch_qualified_segments__400(self): def test_fetch_qualified_segments__500(self): with mock.patch('requests.post') as mock_request_post, \ mock.patch('optimizely.logger') as mock_logger: - mock_request_post.return_value = fake_server_response(status_code=500, url=self.api_host) + mock_request_post.return_value = self.fake_server_response(status_code=500, url=self.api_host) api = ZaiusGraphQLApiManager(logger=mock_logger) api.fetch_segments(api_key=self.api_key, From 74b2ff8abfe95d508ab074b7de33a6eb95abd5ef Mon Sep 17 00:00:00 2001 From: Matjaz Pirnovar Date: Wed, 10 Aug 2022 18:03:04 -0700 Subject: [PATCH 7/9] remove unnecessary url parsing exceptions --- optimizely/odp/zaius_rest_api_manager.py | 26 ++++++++++-------------- tests/test_odp_zaius_rest_api_manager.py | 4 ++-- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/optimizely/odp/zaius_rest_api_manager.py b/optimizely/odp/zaius_rest_api_manager.py index 6f0919e6..b2615ad2 100644 --- a/optimizely/odp/zaius_rest_api_manager.py +++ b/optimizely/odp/zaius_rest_api_manager.py @@ -17,7 +17,7 @@ from typing import Optional, List import requests -from requests.exceptions import RequestException, ConnectionError, Timeout, InvalidURL, InvalidSchema +from requests.exceptions import RequestException, ConnectionError, Timeout from optimizely import logger as optimizely_logger from optimizely.helpers.enums import Errors, OdpRestApiConfig @@ -63,6 +63,7 @@ def send_odp_events(self, api_key: str, api_host: str, events: List[OdpEvent]) - payload_dict = json.dumps(events) except TypeError as err: self.logger.error(Errors.ODP_EVENT_FAILED.format(err)) + print(' - in JSON Encoding err - ') return should_retry try: @@ -75,25 +76,20 @@ def send_odp_events(self, api_key: str, api_host: str, events: List[OdpEvent]) - except (ConnectionError, Timeout): self.logger.error(Errors.ODP_EVENT_FAILED.format('network error')) - # we do retry on network errors + # retry on network errors should_retry = True - except (InvalidURL, InvalidSchema, UnicodeError): - # The three exceptions combined catch different cases of invalid URL format. - # For example: - # UnicodeError catches double dot in URL: https://api.zaius..com - # InvalidURL catches extra characters such as forward slash: https:///api.zaius.com - # InvalidSchema catches extra prepended characters: XXhttps://api.zaius.com - self.logger.error(Errors.ODP_EVENT_FAILED.format('invalid URL')) except RequestException as err: - if 400 <= err.response.status_code < 500: - if err.response.text: - # log response text if it exists + if err.response is not None: + if 400 <= err.response.status_code < 500: + # log 4xx self.logger.error(Errors.ODP_EVENT_FAILED.format(err.response.text)) else: - # otherwise log error message + # log 5xx self.logger.error(Errors.ODP_EVENT_FAILED.format(err)) + # retry on 500 exceptions + should_retry = True else: + # log exceptions without response body (i.e. invalid url) self.logger.error(Errors.ODP_EVENT_FAILED.format(err)) - # retry on 500 errors - should_retry = True + return should_retry diff --git a/tests/test_odp_zaius_rest_api_manager.py b/tests/test_odp_zaius_rest_api_manager.py index d82026cd..e7327d6f 100644 --- a/tests/test_odp_zaius_rest_api_manager.py +++ b/tests/test_odp_zaius_rest_api_manager.py @@ -76,7 +76,7 @@ def test_send_odp_events_invalid_url_no_retry(self): invalid_url = 'https://*api.zaius.com' with mock.patch('requests.post', - side_effect=request_exception.InvalidURL('Invalid URL error')) as mock_request_post, \ + side_effect=request_exception.InvalidURL('Invalid URL')) as mock_request_post, \ mock.patch('optimizely.logger') as mock_logger: api = ZaiusRestApiManager(logger=mock_logger) should_retry = api.send_odp_events(api_key=self.api_key, @@ -85,7 +85,7 @@ def test_send_odp_events_invalid_url_no_retry(self): self.assertFalse(should_retry) mock_request_post.assert_called_once() - mock_logger.error.assert_called_once_with('ODP event send failed (invalid URL).') + mock_logger.error.assert_called_once_with('ODP event send failed (Invalid URL).') def test_send_odp_events_network_error_retry(self): with mock.patch('requests.post', From 0d541b50ac78a39249c77c8736e185fe248b5c5b Mon Sep 17 00:00:00 2001 From: Matjaz Pirnovar Date: Wed, 10 Aug 2022 23:10:05 -0700 Subject: [PATCH 8/9] remove print statement --- optimizely/odp/zaius_rest_api_manager.py | 1 - 1 file changed, 1 deletion(-) diff --git a/optimizely/odp/zaius_rest_api_manager.py b/optimizely/odp/zaius_rest_api_manager.py index b2615ad2..834957a3 100644 --- a/optimizely/odp/zaius_rest_api_manager.py +++ b/optimizely/odp/zaius_rest_api_manager.py @@ -63,7 +63,6 @@ def send_odp_events(self, api_key: str, api_host: str, events: List[OdpEvent]) - payload_dict = json.dumps(events) except TypeError as err: self.logger.error(Errors.ODP_EVENT_FAILED.format(err)) - print(' - in JSON Encoding err - ') return should_retry try: From 0c669af8fec9d6d76176530e2172ee4203525837 Mon Sep 17 00:00:00 2001 From: Matjaz Pirnovar Date: Thu, 11 Aug 2022 13:54:01 -0700 Subject: [PATCH 9/9] fixed type hints --- optimizely/odp/zaius_rest_api_manager.py | 6 +++--- tests/base.py | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/optimizely/odp/zaius_rest_api_manager.py b/optimizely/odp/zaius_rest_api_manager.py index 834957a3..9cbe2638 100644 --- a/optimizely/odp/zaius_rest_api_manager.py +++ b/optimizely/odp/zaius_rest_api_manager.py @@ -14,7 +14,7 @@ from __future__ import annotations import json -from typing import Optional, List +from typing import Optional import requests from requests.exceptions import RequestException, ConnectionError, Timeout @@ -43,7 +43,7 @@ class ZaiusRestApiManager: def __init__(self, logger: Optional[optimizely_logger.Logger] = None): self.logger = logger or optimizely_logger.NoOpLogger() - def send_odp_events(self, api_key: str, api_host: str, events: List[OdpEvent]) -> bool: + def send_odp_events(self, api_key: str, api_host: str, events: list[OdpEvent]) -> bool: """ Dispatch the event being represented by the OdpEvent object. @@ -55,7 +55,7 @@ def send_odp_events(self, api_key: str, api_host: str, events: List[OdpEvent]) - Returns: retry is True - if network or server error (5xx), otherwise False """ - should_retry: bool = False + should_retry = False url = f'{api_host}/v3/events' request_headers = {'content-type': 'application/json', 'x-api-key': api_key} diff --git a/tests/base.py b/tests/base.py index ede8acc9..65ae1fe1 100644 --- a/tests/base.py +++ b/tests/base.py @@ -31,8 +31,9 @@ def assertStrictTrue(self, to_assert): def assertStrictFalse(self, to_assert): self.assertIs(to_assert, False) - def fake_server_response(self, status_code: int = None, content: Optional[str] = None, - url: str = None) -> Optional[Response]: + def fake_server_response(self, status_code: Optional[int] = None, + content: Optional[str] = None, + url: Optional[str] = None) -> Response: """Mock the server response.""" response = Response()