From 72ec5853ccca5618167ca2d90dd51d42deb71824 Mon Sep 17 00:00:00 2001 From: gavin-aguiar <80794152+gavin-aguiar@users.noreply.github.com> Date: Tue, 10 Oct 2023 10:21:29 -0500 Subject: [PATCH] Attribute error fix for retry policy (#1322) * ModuleNotFound error fix for retry policy * Flake8 fixes * Changing modulenotfound to attribute error --------- Co-authored-by: Gavin Aguiar --- azure_functions_worker/loader.py | 67 +++++++++++++++++++++----------- tests/unittests/test_loader.py | 25 +++++++++--- 2 files changed, 64 insertions(+), 28 deletions(-) diff --git a/azure_functions_worker/loader.py b/azure_functions_worker/loader.py index 618a8c64..7277eb3e 100644 --- a/azure_functions_worker/loader.py +++ b/azure_functions_worker/loader.py @@ -19,6 +19,7 @@ from .bindings.retrycontext import RetryPolicy from .constants import MODULE_NOT_FOUND_TS_URL, SCRIPT_FILE_NAME, \ PYTHON_LANGUAGE_RUNTIME, RETRY_POLICY, CUSTOMER_PACKAGES_PATH +from .logging import logger from .utils.wrappers import attach_message_to_exception _AZURE_NAMESPACE = '__app__' @@ -66,36 +67,56 @@ def build_binding_protos(indexed_function) -> Dict: def build_retry_protos(indexed_function) -> Dict: - retry = indexed_function.get_settings_dict(RETRY_POLICY) + retry = get_retry_settings(indexed_function) + if not retry: return None strategy = retry.get(RetryPolicy.STRATEGY.value) + max_retry_count = int(retry.get(RetryPolicy.MAX_RETRY_COUNT.value)) + retry_strategy = retry.get(RetryPolicy.STRATEGY.value) + if strategy == "fixed_delay": - delay_interval = Duration( - seconds=convert_to_seconds( - retry.get(RetryPolicy.DELAY_INTERVAL.value))) - retry_protos = protos.RpcRetryOptions( - max_retry_count=int(retry.get(RetryPolicy.MAX_RETRY_COUNT.value)), - retry_strategy=retry.get(RetryPolicy.STRATEGY.value), - delay_interval=delay_interval, - ) + return build_fixed_delay_retry(retry, max_retry_count, retry_strategy) else: - minimum_interval = Duration( - seconds=convert_to_seconds( - retry.get(RetryPolicy.MINIMUM_INTERVAL.value))) - maximum_interval = Duration( - seconds=convert_to_seconds( - retry.get(RetryPolicy.MAXIMUM_INTERVAL.value))) - - retry_protos = protos.RpcRetryOptions( - max_retry_count=int(retry.get(RetryPolicy.MAX_RETRY_COUNT.value)), - retry_strategy=retry.get(RetryPolicy.STRATEGY.value), - minimum_interval=minimum_interval, - maximum_interval=maximum_interval - ) + return build_variable_interval_retry(retry, max_retry_count, + retry_strategy) + + +def get_retry_settings(indexed_function): + try: + return indexed_function.get_settings_dict(RETRY_POLICY) + except AttributeError as e: + logger.warning("AttributeError while loading retry policy. %s", e) + return None + - return retry_protos +def build_fixed_delay_retry(retry, max_retry_count, retry_strategy): + delay_interval = Duration( + seconds=convert_to_seconds(retry.get(RetryPolicy.DELAY_INTERVAL.value)) + ) + return protos.RpcRetryOptions( + max_retry_count=max_retry_count, + retry_strategy=retry_strategy, + delay_interval=delay_interval, + ) + + +def build_variable_interval_retry(retry, max_retry_count, retry_strategy): + minimum_interval = Duration( + seconds=convert_to_seconds( + retry.get(RetryPolicy.MINIMUM_INTERVAL.value)) + ) + maximum_interval = Duration( + seconds=convert_to_seconds( + retry.get(RetryPolicy.MAXIMUM_INTERVAL.value)) + ) + return protos.RpcRetryOptions( + max_retry_count=max_retry_count, + retry_strategy=retry_strategy, + minimum_interval=minimum_interval, + maximum_interval=maximum_interval + ) def process_indexed_function(functions_registry: functions.Registry, diff --git a/tests/unittests/test_loader.py b/tests/unittests/test_loader.py index 96ed7e6e..85ff1472 100644 --- a/tests/unittests/test_loader.py +++ b/tests/unittests/test_loader.py @@ -5,6 +5,7 @@ import subprocess import sys import textwrap +from unittest.mock import Mock, patch from azure.functions import Function from azure.functions.decorators.retry_policy import RetryPolicy @@ -25,7 +26,11 @@ def test_function(): self.func = Function(self.test_function, script_file="test.py") self.function_registry = functions.Registry() - def test_building_fixed_retry_protos(self): + @classmethod + def get_script_dir(cls): + return testutils.UNIT_TESTS_FOLDER / 'load_functions' + + def test_loader_building_fixed_retry_protos(self): trigger = TimerTrigger(schedule="*/1 * * * * *", arg_name="mytimer", name="mytimer") self.func.add_trigger(trigger=trigger) @@ -38,7 +43,7 @@ def test_building_fixed_retry_protos(self): self.assertEqual(protos.retry_strategy, 1) # 1 enum for fixed delay self.assertEqual(protos.delay_interval.seconds, 120) - def test_building_exponential_retry_protos(self): + def test_loader_building_exponential_retry_protos(self): trigger = TimerTrigger(schedule="*/1 * * * * *", arg_name="mytimer", name="mytimer") self.func.add_trigger(trigger=trigger) @@ -55,9 +60,19 @@ def test_building_exponential_retry_protos(self): self.assertEqual(protos.minimum_interval.seconds, 60) self.assertEqual(protos.maximum_interval.seconds, 120) - @classmethod - def get_script_dir(cls): - return testutils.UNIT_TESTS_FOLDER / 'load_functions' + @patch('azure_functions_worker.logging.logger.warning') + def test_loader_retry_policy_attribute_error(self, mock_logger): + self.func = Mock() + self.func.get_settings_dict.side_effect = AttributeError('DummyError') + + result = build_retry_protos(self.func) + self.assertIsNone(result) + + # Check if the logged message starts with the expected string + logged_message = mock_logger.call_args[0][ + 0] # Get the first argument of the logger.warning call + self.assertTrue(logged_message.startswith( + 'AttributeError while loading retry policy.')) def test_loader_simple(self): r = self.webhost.request('GET', 'simple')