Honoring the Retry-After header with Tenacity
Tenacity allows you to retry anything. That’s great, but this generality also means that it simply doesn’t know anything about HTTP headers or status codes.
To support the Retry-After
HTTP header, we implement our own wait strategy wait_retry_after
that we’ll pass to the Retrying
controller. For the sake of simplicity, I retry on any status code >= 400 in the following snippet.
= Retrying(
retrying =retry_if_result(lambda result: result.status_code >= 400),
retry=wait_retry_after(
wait=1, min=4, max=10)
wait_exponential(multiplier
), )
Here is the implementation.
class wait_retry_after(wait_base):
def __init__(self, fallback: wait_base) -> None:
self.fallback = fallback
def __call__(self, retry_state: RetryCallState) -> float:
if outcome := retry_state.outcome:
= outcome.result()
response if response.status_code in (429, 503) and (
= response.headers.get("Retry-After")):
retry_after :return float(retry_after)
return self.fallback(retry_state)
If we get a 429
or 503
status code and a Retry-After
header on our response, this strategy returns the wait time indicated in the header, otherwise it executes a fallback strategy – in our case, the wait_exponential
we passed above.
In order for our strategy to work, we need to make sure to set the result on the retry state after each retry attempt, so that we have access to the response in our custom strategy. If you implement some HTTP client lib, this may go into your private _request
method that is called internally by each of your public methods.1
for attempt in retrying:
with attempt:
= self.session.request(...)
response if (not attempt.retry_state.outcome or
not attempt.retry_state.outcome.failed):
attempt.retry_state.set_result(response)
The user of your lib only needs to pass it a Retrying
object to configure the retry behavior.
I used the synchronous version here because it’s shorter, but you can do the same with async.↩︎