From f67c4f9849bc2186f974f2c21cb9e58f7aafe51c Mon Sep 17 00:00:00 2001 From: Hiroshi Nishio <4620828+hiroshinishio@users.noreply.github.com> Date: Thu, 4 Jul 2024 13:42:39 +0900 Subject: [PATCH] Add a rate limit exception to handle_exceptions() --- cloudformation.yml | 2 +- utils/handle_exceptions.py | 46 ++++++++++++++++++++++++-------------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/cloudformation.yml b/cloudformation.yml index d23b254c..60dec727 100644 --- a/cloudformation.yml +++ b/cloudformation.yml @@ -20,7 +20,7 @@ Resources: Properties: Name: SchedulerEventRule Description: "Schedule Lambda function to run every weekday at 0 AM UTC" - ScheduleExpression: cron(30 13 ? * MON-FRI *) # min hour day month day-of-week year + ScheduleExpression: cron(50 4 ? * MON-FRI *) # min hour day month day-of-week year State: ENABLED Targets: - Arn: !Ref LambdaFunctionArn diff --git a/utils/handle_exceptions.py b/utils/handle_exceptions.py index 079344c6..6a965e05 100644 --- a/utils/handle_exceptions.py +++ b/utils/handle_exceptions.py @@ -1,32 +1,44 @@ +# pylint: disable=broad-exception-caught + +# Standard imports +import time from functools import wraps -import logging from typing import Any, Callable, Tuple, TypeVar -F = TypeVar('F', bound=Callable[..., Any]) +# Third party imports +import logging +import requests + +F = TypeVar("F", bound=Callable[..., Any]) -def handle_exceptions(default_return_value: Any = None, raise_on_error: bool = False) -> Callable[[F], F]: +def handle_exceptions( + default_return_value: Any = None, raise_on_error: bool = False +) -> Callable[[F], F]: def decorator(func: F) -> F: @wraps(wrapped=func) def wrapper(*args: Tuple[Any, ...], **kwargs: Any): try: return func(*args, **kwargs) - except AttributeError as err: - logging.error("%s encountered an AttributeError: %s", func.__name__, err) - if raise_on_error: - raise - except KeyError as err: - logging.error("%s encountered a KeyError: %s", func.__name__, err) - if raise_on_error: - raise - except TypeError as err: - logging.error("%s encountered a TypeError: %s", func.__name__, err) - if raise_on_error: - raise - except Exception as err: # pylint: disable=broad-except - logging.error("%s encountered an Exception: %s", func.__name__, err) + except requests.exceptions.HTTPError as err: + if ( + err.response.status_code == 403 + and "X-RateLimit-Reset" in err.response.headers + ): + reset_timestamp = int(err.response.headers["X-RateLimit-Reset"]) + current_timestamp = int(time.time()) + wait_time = reset_timestamp - current_timestamp + err_msg = f"{func.__name__} encountered a GitHubRateLimitError: {err}. Retrying after {wait_time} seconds." + logging.error(msg=err_msg) + time.sleep(wait_time + 5) # 5 seconds is a buffer + return wrapper(*args, **kwargs) + except (AttributeError, KeyError, TypeError, Exception) as err: + error_msg = f"{func.__name__} encountered an {type(err).__name__}: {err}\nArgs: {args}\nKwargs: {kwargs}" + logging.error(msg=error_msg) if raise_on_error: raise return default_return_value + return wrapper # type: ignore + return decorator