Problem 568021 · medium · Level 05 Advanced Algorithms & Graphs

Try Again, but Only for the Right Failures

py-decorators · py-exceptions · py-closures · decorator factory · retries

A data loader talks to a server that sometimes drops the connection. The fix is to try again, but only for failures that trying again can cure: a ConnectionError is worth a second attempt, a KeyError from a bug in the loader is not. Write a decorator factory retry(attempts, *, on=Exception, before_retry=None), used like this:

@retry(3, on=(ConnectionError, TimeoutError))
def download(url):
    ...
  • The decorated function calls the original with the same positional and keyword arguments, at most attempts times in total, and returns the first successful result.
  • A failure whose exception is an instance of on (a type or a tuple of types, subclasses included) is retried while attempts remain. Before each retry, if before_retry is given, it is called as before_retry(n, exc), where n is the number of the attempt that just failed (the first is 1) and exc is its exception.
  • A failure of any other type is raised at once, without further attempts.
  • When every attempt has failed, the exception of the last attempt is raised.
  • retry(0) (or any attempts < 1) raises ValueError straight away, before any function is decorated.
  • Every call of the decorated function starts with a fresh set of attempts, and the decorated function keeps the original's __name__ and __doc__.

There is no waiting between attempts. Setup helpers, available with Run: flaky(script) (a function whose calls follow a script of values and exception classes), run_flaky(attempts, script, on=None), separate_calls(), with_arguments(), bad_attempts() and raises(fn, *args).

Examples

Input:  run_flaky(3, [ConnectionError, TimeoutError, "data"])
Output: ("data", 3, [(1, "ConnectionError"), (2, "TimeoutError")])

Input:  run_flaky(4, [ConnectionError, KeyError, "data"], on=ConnectionError)
Output: ("KeyError: 'call 2'", 2, [(1, "ConnectionError")])

Constraints

  • attempts <= 1000. No sleeping and no clock: the tests count calls.

Goals

  • Write a decorator that takes its own settings (a decorator factory)
  • Retry only on the chosen exception types and re-raise everything else at once
  • Report each retry through a callback instead of printing
Starting Python…