diff --git a/src/handler/decorator.py b/src/handler/decorator.py index 092941c..92c6d8d 100644 --- a/src/handler/decorator.py +++ b/src/handler/decorator.py @@ -49,7 +49,7 @@ def wrapper(*args, **kwargs): ) # send full log to admin channel # notify user that something is wrong. - msg = ":blob-fearful: 요청이 정상적으로 처리되지 않았어. 운영진에게 알려줘!" + msg = ":blob-fearful: 요청이 정상적으로 처리되지 않았어. 한 번 더 시도해보고, 그래도 안돼면 운영진에게 알려줘!" say( text=msg, channel=search_value(event, "channel") diff --git a/src/implementation/google_spreadsheet_client.py b/src/implementation/google_spreadsheet_client.py index e86febf..a983cf9 100644 --- a/src/implementation/google_spreadsheet_client.py +++ b/src/implementation/google_spreadsheet_client.py @@ -7,6 +7,7 @@ from gspread_formatting import set_column_width from config.env_config import envs +from util.utils import with_retry class GoogleSpreadsheetClient: @@ -57,6 +58,7 @@ def _create_worksheet( return worksheet.id + @with_retry def append_row( self, worksheet_id: int, @@ -94,6 +96,7 @@ def _get_worksheet(self, worksheet_id: int) -> Worksheet: worksheet = spreadsheet.get_worksheet_by_id(worksheet_id) return worksheet + @with_retry def create_bigchat_sheet(self, title=None) -> Optional[int]: worksheet_id: Optional[int] = self._create_worksheet( title=title, diff --git a/src/util/__init__.py b/src/util/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/util/utils.py b/src/util/utils.py index 6e22731..c697091 100644 --- a/src/util/utils.py +++ b/src/util/utils.py @@ -1,3 +1,7 @@ +import functools +import time + + def search_value(d, key): """ 주어진 key 에 해당하는 value 를 찾아주는 메서드. @@ -48,3 +52,26 @@ def strip_multiline(text, *args, ignore_first_line=True): result = result.replace("{}", arg, 1) return result + + +def with_retry(max_try_cnt=10, fixed_wait_time_in_sec=3): + """ + usage: @retry(3, 1) + """ + + def decorator(func): + + @functools.wraps(func) + def wrapper(*args, **kwargs): + last_exception = None + for attempt in range(max_try_cnt): + try: + return func(*args, **kwargs) + except Exception as ex: + last_exception = ex + time.sleep(fixed_wait_time_in_sec) + raise last_exception + + return wrapper + + return decorator diff --git a/test/util/test_utils.py b/test/util/test_utils.py index a508725..b8135bd 100644 --- a/test/util/test_utils.py +++ b/test/util/test_utils.py @@ -1,6 +1,6 @@ import unittest -from util.utils import search_value, strip_multiline +from util.utils import search_value, strip_multiline, with_retry class TestUtils(unittest.TestCase): @@ -111,3 +111,43 @@ def test_multiline_with_arguments(self): A B CCC""" + +class TestWithRetry(unittest.TestCase): + def test_with_retry_success(self): + attempts=0 + + @with_retry(fixed_wait_time_in_sec=0.01) + def always_succeed(): + nonlocal attempts + attempts+=1 + return "success" + + self.assertEqual(always_succeed(), "success") + self.assertEqual(attempts, 1) + + def test_with_retry_failure(self): + attempts = 0 + + @with_retry(fixed_wait_time_in_sec=0.01) + def always_fail(): + nonlocal attempts + attempts+=1 + raise ValueError("failure") + + with self.assertRaises(ValueError): + always_fail() + self.assertEqual(attempts, 10) + + def test_with_retry_partial_success(self): + attempts = 0 + + @with_retry(fixed_wait_time_in_sec=0.01) + def succeed_after_two_attempts(): + nonlocal attempts + attempts += 1 + if attempts < 3: + raise ValueError("failure") + return "success" + + self.assertEqual(succeed_after_two_attempts(), "success") + self.assertEqual(attempts, 3)