Skip to content

Instantly share code, notes, and snippets.

@workingenius
Created September 29, 2017 12:33
Show Gist options
  • Save workingenius/d144682a229d1dde110ddc8d1d38575d to your computer and use it in GitHub Desktop.
Save workingenius/d144682a229d1dde110ddc8d1d38575d to your computer and use it in GitHub Desktop.
retry in python
# -*- 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