Created
September 29, 2017 12:33
-
-
Save workingenius/d144682a229d1dde110ddc8d1d38575d to your computer and use it in GitHub Desktop.
retry in python
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding:utf8 -*- | |
from functools import wraps | |
from time import sleep | |
from random import random | |
import logging | |
from curry import curry | |
logger = logging.getLogger('retry') | |
error_logger = logging.getLogger('retry.error') | |
error_logger.propagate = False | |
def _is_number(x): | |
return isinstance(x, (int, float)) | |
def _app_sign(func, args, kwargs): | |
if args and kwargs: | |
return '{0}(*{1}, **{2})'.format(func.__name__, args, kwargs) | |
elif args and not kwargs: | |
return '{0}(*{1})'.format(func.__name__, args) | |
elif not args and kwargs: | |
return '{0}(**{1})'.format(func.__name__, kwargs) | |
else: | |
return '{0}()'.format(func.__name__) | |
def catch(f, args, kwargs, times=0): | |
sign = _app_sign(f, args, kwargs) | |
logger.info(sign) | |
try: | |
retval = f(*args, **kwargs) | |
except Exception as e: | |
error_logger.exception('{0} for time {1}'.format(sign, times)) | |
return None, e | |
else: | |
return retval, None | |
def _retry(func, args, kwargs, should_retry, wait, max_times=0): | |
times = 0 | |
res, exc = catch(func, args, kwargs) | |
while should_retry(res, exc, times) and (max_times <= 0 or times < max_times): | |
wait(res, exc, times) | |
times += 1 | |
res, exc = catch(func, args, kwargs) | |
if exc: | |
raise exc | |
else: | |
return res | |
# several waiting patterns: | |
def wait_for(ms): | |
"""wait with logging""" | |
if ms > 0: | |
logger.info('wait for {} ms'.format(ms)) | |
sleep(float(ms) / 1000) | |
logger.info('finish waiting') | |
def f_no_waiting(*args, **kwargs): | |
pass | |
@curry | |
def f_constant_ms(ms, | |
retval, exc, times): | |
wait_for(ms) | |
@curry | |
def f_exp_ms(exp_init, exp_fac, | |
retval, exc, times): | |
ms = exp_init * (exp_fac ** times) | |
wait_for(ms) | |
@curry | |
def f_rand_ms(rand_min, rand_max, | |
retval, exc, times): | |
ms = rand_min + random() * (rand_max - rand_min) | |
wait_for(ms) | |
def mk_wait(no_waiting=True, # strategy 1: dont wait | |
constant_ms=0, # strategy 2: wait for constant time | |
exp_init=0, exp_fac=0, # strategy 3: wait time increase exponentially | |
rand_min=0, rand_max=None): # strategy 4: wait for random time | |
if isinstance(constant_ms, (int, float)) and constant_ms > 0: | |
return f_constant_ms(constant_ms) | |
elif exp_init > 0 and exp_fac > 0: | |
assert exp_fac > 1 | |
return f_exp_ms(exp_init, exp_fac) | |
elif _is_number(rand_min) and _is_number(rand_max): | |
assert 0 <= rand_min < rand_max | |
return f_rand_ms(rand_min, rand_max) | |
elif no_waiting: | |
return f_no_waiting | |
else: | |
# invalid waiting strategy | |
# you should provide a custom waiting function, if default strategies do not suffice | |
raise | |
# default should retry | |
@curry | |
def f_should_retry(retval, exc, times): | |
"""default should_retry predicate | |
should retry, when any exception occurs, | |
and has retried less than several times | |
as default, retry for unlimited times | |
""" | |
return exc is not None | |
@curry | |
def retry(func, | |
max_times=0, | |
should_retry=f_should_retry, | |
no_waiting=True, constant_ms=0, exp_init=0, exp_fac=0, rand_min=0, rand_max=None): # waiting options | |
@wraps(func) | |
def func_with_retry(*args, **kwargs): | |
return _retry(func, args, kwargs, should_retry, | |
mk_wait(no_waiting, constant_ms, exp_init, exp_fac, rand_min, rand_max), | |
max_times) | |
return func_with_retry |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment