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
attemptstimes 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, ifbefore_retryis given, it is called asbefore_retry(n, exc), wherenis the number of the attempt that just failed (the first is1) andexcis 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 anyattempts < 1) raisesValueErrorstraight 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