-
-
Save FBosler/be10229aba491a8c912e3a1543bbc74e to your computer and use it in GitHub Desktop.
#Copyright 2021 Fabian Bosler | |
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation | |
# files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, | |
# modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom | |
# the Software is furnished to do so, subject to the following conditions: | |
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the | |
# Software. | |
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO | |
# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS | |
# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR | |
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
from functools import wraps | |
import time | |
import logging | |
import random | |
logger = logging.getLogger(__name__) | |
def retry(exceptions, total_tries=4, initial_wait=0.5, backoff_factor=2, logger=None): | |
""" | |
calling the decorated function applying an exponential backoff. | |
Args: | |
exceptions: Exception(s) that trigger a retry, can be a tuple | |
total_tries: Total tries | |
initial_wait: Time to first retry | |
backoff_factor: Backoff multiplier (e.g. value of 2 will double the delay each retry). | |
logger: logger to be used, if none specified print | |
""" | |
def retry_decorator(f): | |
@wraps(f) | |
def func_with_retries(*args, **kwargs): | |
_tries, _delay = total_tries + 1, initial_wait | |
while _tries > 1: | |
try: | |
log(f'{total_tries + 2 - _tries}. try:', logger) | |
return f(*args, **kwargs) | |
except exceptions as e: | |
_tries -= 1 | |
print_args = args if args else 'no args' | |
if _tries == 1: | |
msg = str(f'Function: {f.__name__}\n' | |
f'Failed despite best efforts after {total_tries} tries.\n' | |
f'args: {print_args}, kwargs: {kwargs}') | |
log(msg, logger) | |
raise | |
msg = str(f'Function: {f.__name__}\n' | |
f'Exception: {e}\n' | |
f'Retrying in {_delay} seconds!, args: {print_args}, kwargs: {kwargs}\n') | |
log(msg, logger) | |
time.sleep(_delay) | |
_delay *= backoff_factor | |
return func_with_retries | |
return retry_decorator | |
def log(msg, logger=None): | |
if logger: | |
logger.warning(msg) | |
else: | |
print(msg) | |
def test_func(*args, **kwargs): | |
rnd = random.random() | |
if rnd < .2: | |
raise ConnectionAbortedError('Connection was aborted :(') | |
elif rnd < .4: | |
raise ConnectionRefusedError('Connection was refused :/') | |
elif rnd < .8: | |
raise ConnectionResetError('Guess the connection was reset') | |
else: | |
return 'Yay!!' | |
if __name__ == '__main__': | |
# wrapper = retry((ConnectionAbortedError), tries=3, delay=.2, backoff=1, logger=logger) | |
# wrapped_test_func = wrapper(test_func) | |
# print(wrapped_test_func('hi', 'bye', hi='ciao')) | |
wrapper_all_exceptions = retry(Exception, total_tries=2, logger=logger) | |
wrapped_test_func = wrapper_all_exceptions(test_func) | |
print(wrapped_test_func('hi', 'bye', hi='ciao')) |
Thanks for sharing this. It was very useful
Elegant solution, thanks you!
First - AWESOME! I sure gonna use this one.
In the docstring: "exceptions: Exeption(s) that trigger a retry, can be a tuble"
I guess you meant "tuple", right?
First - AWESOME! I sure gonna use this one.
In the docstring: "exceptions: Exeption(s) that trigger a retry, can be a tuble"
I guess you meant "tuple", right?
@liorbunzl Thanks so much! Appreciate it :)
Your comment made me laugh :) Had to think about bubbles. But jupp, ofc you are right. Thx for pointing it out.
Thanks for this code. Really helpful :)
One doubt, in line no 22, Is there any reason why you've set _tries = total_tries + 1
. Can't it just be _tries= total_tries
? And check while _tries > 0
Hi @FBosler,
I would like to reuse your code, do you have a open source license attached to it?
Thank you!
nice when you randomly stumble onto your ex-managers code
@NicholasBallard thank you! Your kind words are much appreciated!