1
- from typing import List , Dict
2
-
3
- import requests
4
-
5
1
from functools import wraps
6
- def retry ( ExceptionToCheck , tries = 5 , delay = 3 , backoff = 2 , logger = None ):
7
- """Retry calling the decorated function using an exponential backoff.
8
-
9
- http://www.saltycrane.com/blog/2009/11/trying-out-retry-decorator-python/
10
- original from: http://wiki.python.org/moin/PythonDecoratorLibrary#Retry
2
+ import logging
3
+ from typing import Callable , Type , Union , Tuple , Optional , List , Dict
4
+ import random
5
+ import time
6
+ import requests
11
7
12
- :param ExceptionToCheck: the exception to check. may be a tuple of
13
- exceptions to check
14
- :type ExceptionToCheck: Exception or tuple
15
- :param tries: number of times to try (not retry) before giving up
16
- :type tries: int
17
- :param delay: initial delay between retries in seconds
18
- :type delay: int
19
- :param backoff: backoff multiplier e.g. value of 2 will double the delay
20
- each retry
21
- :type backoff: int
22
- :param logger: logger to use. If None, print
23
- :type logger: logging.Logger instance
8
+ def retry (
9
+ exception_to_check : Union [Type [BaseException ], Tuple [Type [BaseException ], ...]],
10
+ tries : int = 5 ,
11
+ delay : int = 3 ,
12
+ backoff : int = 2 ,
13
+ logger : Optional [logging .Logger ] = None ,
14
+ log_level : int = logging .WARNING ,
15
+ re_raise : bool = True ,
16
+ jitter : float = 0.1
17
+ ) -> Callable :
24
18
"""
25
- def deco_retry ( f ):
19
+ Retry calling the decorated function using an exponential backoff.
26
20
27
- @wraps (f )
21
+ :param exception_to_check: Exception or a tuple of exceptions to check.
22
+ :param tries: Number of times to try (not retry) before giving up.
23
+ :param delay: Initial delay between retries in seconds.
24
+ :param backoff: Backoff multiplier e.g., a value of 2 will double the delay each retry.
25
+ :param logger: Logger to use. If None, print.
26
+ :param log_level: Logging level.
27
+ :param re_raise: Whether to re-raise the exception after the last retry.
28
+ :param jitter: The maximum jitter to apply to the delay as a fraction of the delay.
29
+ """
30
+ def deco_retry (func : Callable ) -> Callable :
31
+ @wraps (func )
28
32
def f_retry (* args , ** kwargs ):
29
- mtries , mdelay = tries , delay
30
- while mtries > 1 :
33
+ remaining_tries , current_delay = tries , delay
34
+ while remaining_tries > 1 :
31
35
try :
32
- return f (* args , ** kwargs )
33
- except ExceptionToCheck as e :
34
- msg = "%s , Retrying in %d seconds..." % ( str ( e ), mdelay )
36
+ return func (* args , ** kwargs )
37
+ except exception_to_check as e :
38
+ msg = f" { e } , Retrying in { current_delay } seconds..."
35
39
if logger :
36
- #logger.exception(msg) # would print stack trace
37
- logger .warning (msg )
40
+ logger .log (log_level , msg )
38
41
else :
39
42
print (msg )
40
- time .sleep (mdelay )
41
- mtries -= 1
42
- mdelay *= backoff
43
- return f (* args , ** kwargs )
43
+ time .sleep (current_delay * (1 + jitter * (2 * random .random () - 1 )))
44
+ remaining_tries -= 1
45
+ current_delay *= backoff
44
46
45
- return f_retry # true decorator
47
+ try :
48
+ return func (* args , ** kwargs )
49
+ except exception_to_check as e :
50
+ msg = f"Failed after { tries } tries. { e } "
51
+ if logger :
52
+ logger .log (log_level , msg )
53
+ else :
54
+ print (msg )
55
+ if re_raise :
56
+ raise
46
57
58
+ return f_retry
47
59
return deco_retry
48
60
49
- import time
50
- import requests
51
-
52
61
class RetryableError (Exception ):
53
62
pass
54
63
64
+ class InvalidAPIKeyError (Exception ):
65
+ pass
55
66
56
67
class SimpleAimonRelyClient (object ):
57
68
"""
@@ -63,6 +74,8 @@ def __init__(self, api_key: str):
63
74
"""
64
75
:param api_key: the Aimon Rely API key. If you don't have one, request one by sending an email to info@aimon.ai
65
76
"""
77
+ if len (api_key ) == 0 or "YOUR API KEY" in api_key :
78
+ raise InvalidAPIKeyError ("Enter a valid Aimon API key. Request it at info@aimon.ai or on Discord." )
66
79
self .api_key = api_key
67
80
68
81
@retry (RetryableError )
@@ -83,6 +96,8 @@ def detect(self, data_to_send: List[Dict[str, str]]):
83
96
response = requests .post (self .URL , json = data_to_send , headers = headers , timeout = 30 )
84
97
if response .status_code in [503 , 504 ]:
85
98
raise RetryableError ("Status code: {} received" .format (response .status_code ))
99
+ if response .status_code == 401 :
100
+ raise InvalidAPIKeyError ("Use a valid Aimon API key. Request it at info@aimon.ai or on Discord." )
86
101
if response .status_code != 200 :
87
102
raise Exception (f"Error, bad response: { response } " )
88
103
if len (response .json ()) == 0 or 'error' in response .json () or 'error' in response .json ()[0 ]:
0 commit comments