Level 1 Retry a failing call
Network calls and database queries fail now and then for reasons that go away by themselves. Build a reusable retry helper, Retrier, that re-runs a function until it succeeds or runs out of attempts.
Retrier(max_attempts: int, delay: float = 0, sleep=None)
max_attemptsis the total number of calls allowed (the first try counts). If it is less than 1, the constructor raisesValueError.delayis how long to wait between attempts, in seconds.sleepis the function used to wait;Nonemeanstime.sleep. The tests pass a fake that only records the waits, so never wait any other way.
Methods:
call(fn, *args, **kwargs): callfn(*args, **kwargs). If it returns, return its result right away. If it raises an exception, waitdelaywithsleep(delay)and try again, up tomax_attemptscalls in total. There is no wait after the last failed attempt. If every attempt fails, re-raise the last exception (the same exception object).last_attempts(an attribute or property): how many timesfnwas called during the most recentcall, whether it succeeded or not;0before the firstcall.
A Retrier can be reused for many calls; each call starts fresh.
waits = []
r = Retrier(max_attempts=4, delay=0.5, sleep=waits.append)
calls = iter([ConnectionError("down"), ConnectionError("down"), "ok"])
def flaky():
x = next(calls)
if isinstance(x, Exception):
raise x
return x
r.call(flaky) # "ok"
waits # [0.5, 0.5]
r.last_attempts # 3
r.call(int, "12") # 12 (arguments are passed through)
r.last_attempts # 1
Show hint
loop over the attempts and remember the last exception. There's no point sleeping after the final attempt.