I would like to write a python decorator so that a function raising an exception will be run again until either it succeeds, or it reaches the maximum number of attempts before giving up.
Like so :
def tryagain(func):
def retrier(*args,**kwargs,attempts=MAXIMUM):
try:
return func(*args,**kwargs)
except Exception as e:
if numberofattempts > 0:
logging.error("Failed. Trying again")
return retrier(*args,**kwargs,attempts=attempts-1)
else:
logging.error("Tried %d times and failed, giving up" % MAXIMUM)
raise e
return retrier
My problem is that I want a guarantee that no matter what names the kwargs contain, there cannot be a collision with the name used to denote the number of attempts made.
however this does not work when the function itself takes attempts
as a keyword argument
@tryagain
def other(a,b,attempts=c):
...
raise Exception
other(x,y,attempts=z)
In this example,if other is run, it will run z times and not MAXIMUM times (note that for this bug to happen, the keyword argument must be explicitly used in the call !).
You can specify decorator parameter, something along the lines of this:
import logging
MAXIMUM = 5
def tryagain(attempts=MAXIMUM):
def __retrier(func):
def retrier(*args,**kwargs):
nonlocal attempts
while True:
try:
return func(*args,**kwargs)
except Exception as e:
attempts -= 1
if attempts > 0:
print('Failed, attempts left=', attempts)
continue
else:
print('Giving up')
raise
return retrier
return __retrier
@tryagain(5) # <-- this specifies number of attempts
def fun(attempts='This is my parameter'): # <-- here the function specifies its own `attempts` parameter, unrelated to decorator
raise Exception(attempts)
fun()