This is my first decorator in Python! I found some of it on the internet but have tweaked it to our needs.
Here is the couple concerns of mine:
- Multiple python version compatibility
- Is grabbing the
self
orarg[0]
the best way to get the instance of the class? - Any other general improvements!
Here is the decorator.
import logging
import time
def retry_and_catch(exceptions, tries=5, logger=None, level=logging.ERROR, logger_attr=None, delay=0, backoff=0):
"""
Retries function up to amount of tries.
Backoff disabled by default.
:param exceptions: List of exceptions to catch
:param tries: Number of attempts before raising any exceptions
:param logger: Logger to print out to.
:param level: Log level.
:param logger_attr: Attribute on decorated class to get the logger ie self._logger you would give "_logger"
:param delay: initial delay seconds
:param backoff: backoff multiplier
"""
def deco_retry(f):
def f_retry(*args, **kwargs):
max_tries = tries
d = delay
exs = tuple(exceptions)
log = logger
while max_tries > 1:
try:
return f(*args, **kwargs)
except exs as e:
message = "Exception {} caught, retrying {} more times.".format(e.message, max_tries)
# Get logger from cls instance of function
# Grabbing 'self'
instance = args[0]
if not log and logger_attr and hasattr(instance, logger_attr):
log = getattr(instance, logger_attr, None)
if log:
log.log(level, message)
else:
print(message)
# Sleep current delay
if d:
time.sleep(d)
# Increment delay
if backoff:
d *= backoff
max_tries -= 1
return f(*args, **kwargs) # Final attempt will not catch any errors.
return f_retry
return deco_retry
Here is an example usage
class MyClass(object):
_logger = logging.getLogger()
@retry_and_catch([ValueError], logger_attr='_logger', delay=1, backoff=2)
def my_func(self):
raise ValueError('Unable to run my_func')
c = MyClass()
print(c.my_func())