Skip to content

Instantly share code, notes, and snippets.

@adityaarakeri
Created September 19, 2019 17:14
Show Gist options
  • Save adityaarakeri/086fd438e8f286158448042b4cc1443a to your computer and use it in GitHub Desktop.
Save adityaarakeri/086fd438e8f286158448042b4cc1443a to your computer and use it in GitHub Desktop.

Revisions

  1. adityaarakeri created this gist Sep 19, 2019.
    200 changes: 200 additions & 0 deletions retry_decorator.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,200 @@
    import time
    from functools import wraps


    def retry(ExceptionToCheck, tries=4, delay=3, backoff=2, logger=None):
    """Retry calling the decorated function using an exponential backoff.
    http://www.saltycrane.com/blog/2009/11/trying-out-retry-decorator-python/
    original from: http://wiki.python.org/moin/PythonDecoratorLibrary#Retry
    :param ExceptionToCheck: the exception to check. may be a tuple of
    exceptions to check
    :type ExceptionToCheck: Exception or tuple
    :param tries: number of times to try (not retry) before giving up
    :type tries: int
    :param delay: initial delay between retries in seconds
    :type delay: int
    :param backoff: backoff multiplier e.g. value of 2 will double the delay
    each retry
    :type backoff: int
    :param logger: logger to use. If None, print
    :type logger: logging.Logger instance
    """
    def deco_retry(f):

    @wraps(f)
    def f_retry(*args, **kwargs):
    mtries, mdelay = tries, delay
    while mtries > 1:
    try:
    return f(*args, **kwargs)
    except ExceptionToCheck, e:
    msg = "%s, Retrying in %d seconds..." % (str(e), mdelay)
    if logger:
    logger.warning(msg)
    else:
    print msg
    time.sleep(mdelay)
    mtries -= 1
    mdelay *= backoff
    return f(*args, **kwargs)

    return f_retry # true decorator

    return deco_retry

    # USE CASES

    # 1 ALWAYS FAIL CASE
    @retry(Exception, tries=4)
    def test_fail(text):
    raise Exception("Fail")

    test_fail("it works!")


    # 2 ALWAYS SUCCESS CASE
    @retry(Exception, tries=4)
    def test_success(text):
    print "Success: ", text

    test_success("it works!")

    # 3 RANDOM FAIL CASE
    import random

    @retry(Exception, tries=4)
    def test_random(text):
    x = random.random()
    if x < 0.5:
    raise Exception("Fail")
    else:
    print "Success: ", text

    test_random("it works!")

    # 4 MULTPIPLE EXCEPTIONS CASE
    import random

    @retry((NameError, IOError), tries=20, delay=1, backoff=1)
    def test_multiple_exceptions():
    x = random.random()
    if x < 0.40:
    raise NameError("NameError")
    elif x < 0.80:
    raise IOError("IOError")
    else:
    raise KeyError("KeyError")

    test_multiple_exceptions()

    ####################
    # UNIT TESTS

    import logging
    import unittest

    from decorators import retry


    class RetryableError(Exception):
    pass


    class AnotherRetryableError(Exception):
    pass


    class UnexpectedError(Exception):
    pass


    class RetryTestCase(unittest.TestCase):

    def test_no_retry_required(self):
    self.counter = 0

    @retry(RetryableError, tries=4, delay=0.1)
    def succeeds():
    self.counter += 1
    return 'success'

    r = succeeds()

    self.assertEqual(r, 'success')
    self.assertEqual(self.counter, 1)

    def test_retries_once(self):
    self.counter = 0

    @retry(RetryableError, tries=4, delay=0.1)
    def fails_once():
    self.counter += 1
    if self.counter < 2:
    raise RetryableError('failed')
    else:
    return 'success'

    r = fails_once()
    self.assertEqual(r, 'success')
    self.assertEqual(self.counter, 2)

    def test_limit_is_reached(self):
    self.counter = 0

    @retry(RetryableError, tries=4, delay=0.1)
    def always_fails():
    self.counter += 1
    raise RetryableError('failed')

    with self.assertRaises(RetryableError):
    always_fails()
    self.assertEqual(self.counter, 4)

    def test_multiple_exception_types(self):
    self.counter = 0

    @retry((RetryableError, AnotherRetryableError), tries=4, delay=0.1)
    def raise_multiple_exceptions():
    self.counter += 1
    if self.counter == 1:
    raise RetryableError('a retryable error')
    elif self.counter == 2:
    raise AnotherRetryableError('another retryable error')
    else:
    return 'success'

    r = raise_multiple_exceptions()
    self.assertEqual(r, 'success')
    self.assertEqual(self.counter, 3)

    def test_unexpected_exception_does_not_retry(self):

    @retry(RetryableError, tries=4, delay=0.1)
    def raise_unexpected_error():
    raise UnexpectedError('unexpected error')

    with self.assertRaises(UnexpectedError):
    raise_unexpected_error()

    def test_using_a_logger(self):
    self.counter = 0

    sh = logging.StreamHandler()
    logger = logging.getLogger(__name__)
    logger.addHandler(sh)

    @retry(RetryableError, tries=4, delay=0.1, logger=logger)
    def fails_once():
    self.counter += 1
    if self.counter < 2:
    raise RetryableError('failed')
    else:
    return 'success'

    fails_once()


    if __name__ == '__main__':
    unittest.main()