Retry¶
The @retry decorator repeats a request automatically when it raises a selected exception or returns a selected HTTP status, waiting longer between each attempt. Stack it on a request method the same way as @AsyncSession.single_session or a hook decorator.
1from typing import Annotated, Any
2
3from ahttp_client import (
4 AsyncSession,
5 HTTPServerError,
6 Path,
7 Response,
8 get,
9 retry,
10)
11
12
13class GitHubService(AsyncSession):
14 async def after_request(self, response: Response) -> Response:
15 response.raise_for_status()
16 return response
17
18 @retry(
19 max_retries=3,
20 backoff_factor=0.5,
21 retry_on=(HTTPServerError, TimeoutError),
22 retry_on_status=(502, 503, 504),
23 max_delay=4.0,
24 )
25 @get("/users/{user}/repos")
26 async def list_repositories(
27 self, response: Response, user: Annotated[str, Path]
28 ) -> list[dict[str, Any]]:
29 return response.json()
Exception-based retries use retry_on. Calling
Response.raise_for_status() in a session-level after_request() hook
turns a 4xx or 5xx response into the matching
HTTPException subclass (see
Exception), which retry_on can catch. Alternatively,
raise_on=True on a request treats every status other than 200 as a
failure before the request result is returned.
Status-based retries use retry_on_status and do not require an exception
or an after_request() hook. Use it when a response status itself should
be retried while still allowing the final response to be returned normally:
@retry(max_retries=3, retry_on_status=(429, 502, 503, 504))
@get("/users/{user}/repos")
async def list_repositories(self, response: Response, user: Annotated[str, Path]):
return response.json()
Backoff and Retry Scope¶
The defaults retry up to three times after the initial request, triggered by
HTTPServerError. To make a returned 5xx response raise that exception,
use Response.raise_for_status() or raise_on=True. retry_on_status
defaults to an empty tuple.
The wait before retry attempt n is backoff_factor * 2 ** (n - 1) seconds, capped by max_delay when it is set. With the defaults above (backoff_factor=0.5, max_delay=4.0), the delays are 0.5, 1.0, 2.0, and 4.0 (capped) seconds.
retry_on accepts one exception class or a tuple, so transport-level
failures such as TimeoutError can be added alongside
HTTPServerError, or used in its place. retry_on_status likewise
accepts one HTTP status code or a tuple of status codes from 100 through 599.
Responses from attempts that will be retried are closed automatically. When
the retry budget is exhausted, the final matching-status response is not
pre-closed and proceeds through normal response handling.
Warning
Exception retries cover failures from request transport, a
session-level after_request() hook, and raise_on=True validation.
Status retries inspect the response before request-level after_hook
callbacks run. A request-level after_hook (see Pre Hooking / Post Hooking) runs
after the retry operation is already done, so anything it raises never gets
retried.
Note
max_retries, backoff_factor, and max_delay all need to be finite, non-negative values. Bad values raise TypeError or ValueError as soon as @retry is applied.
Warning
Automatic retries are limited to idempotent HTTP methods. Retrying a POST, PATCH, or another non-idempotent request can duplicate a server-side operation, so it must be explicitly enabled with retry_unsafe=True.
Decoration Order¶
@retry works whether it sits above or below @request (or @get, @post, etc.) on the same method — both orders end up with the same retry configuration:
# @retry above @request: applied directly to the built RequestCore.
@retry(max_retries=5, backoff_factor=0.2)
@get("/users/{user}/repos")
async def list_repositories(self, response: Response, user: Annotated[str, Path]):
return response.json()
# @retry below @request: stashed until @request builds the RequestCore.
@get("/users/{user}/repos")
@retry(max_retries=5, backoff_factor=0.2)
async def list_repositories(self, response: Response, user: Annotated[str, Path]):
return response.json()
Reference¶
- class RetryConfig¶
Configure retry attempts, exception/status matching, and exponential backoff.
- Parameters:
max_retries (int) – Maximum number of attempts after the initial request.
backoff_factor (float) – Multiplier used to calculate the exponential delay before each retry.
retry_on (tuple[type[Exception], ...]) – Exception classes that trigger another attempt.
max_delay (Optional[float]) – Maximum delay in seconds, or
Nonefor no upper bound.retry_unsafe (bool) – Whether retries are explicitly allowed for non-idempotent HTTP methods such as POST and PATCH.
retry_on_status (tuple[int, ...]) – HTTP response statuses from 100 through 599 that trigger another attempt without requiring an exception to be raised.
- Raises:
TypeError – If a retry count, delay, or retry filter has an invalid type.
ValueError – If a retry count or delay is negative or non-finite.
Notes
max_retriesexcludes the initial request. The delay before retry attemptnisbackoff_factor * 2 ** (n - 1)and is capped bymax_delaywhen an upper bound is configured.- async execute_async(make_request_func: Callable[[...], Awaitable[tuple[Response, Any]]]) tuple[Response, Any]¶
Execute an asynchronous request and retry matching failures.
- Parameters:
make_request_func (Callable[..., Awaitable[tuple[Response, Any]]]) – Zero-argument callable that returns the raw response and the response-pipeline result.
- Returns:
Raw response and processed result from the final attempt. A matching status response is returned when its retry budget is exhausted.
- Return type:
- Raises:
Exception – Re-raises a non-matching exception immediately or the last matching exception after the retry budget is exhausted.
- execute_sync(make_request_func: Callable[[...], tuple[Response, Any]]) tuple[Response, Any]¶
Execute a synchronous request and retry matching failures.
- Parameters:
make_request_func (Callable[..., tuple[Response, Any]]) – Zero-argument callable that returns the raw response and the response-pipeline result.
- Returns:
Raw response and processed result from the final attempt. A matching status response is returned when its retry budget is exhausted.
- Return type:
- Raises:
Exception – Re-raises a non-matching exception immediately or the last matching exception after the retry budget is exhausted.
- @retry(max_retries: int = 3, *, backoff_factor: float = 1.0, retry_on: tuple[type[Exception], ...] | type[Exception] = (<class 'ahttp_client.exception.HTTPServerError'>,), retry_on_status: tuple[int, ...] | int = (), max_delay: float | None = None, retry_unsafe: bool = False) RequestDecorator[Any, Any]¶
Decorate a request to retry on failure with exponential backoff.
- Parameters:
max_retries (int) – Maximum number of retry attempts after the initial request.
backoff_factor (float) – Multiplier applied to the exponential backoff delay.
delay = backoff_factor * 2 ** (attempt - 1)retry_on (tuple[type[Exception], ...] | type[Exception]) – Exception types that trigger a retry attempt. Defaults to
HTTPServerError(5xx responses).retry_on_status (tuple[int, ...] | int) – HTTP response statuses that trigger a retry attempt without an exception. Defaults to an empty tuple.
max_delay (Optional[float]) – Upper bound on the sleep delay in seconds.
Nonemeans no cap.retry_unsafe (bool) – Explicitly allow retries for non-idempotent methods. Defaults to
Falseso POST, PATCH, and other unsafe requests are not repeated accidentally.
- Returns:
Decorator that attaches the retry configuration to a request.
- Return type:
RequestDecorator[Any, Any]
- Raises:
TypeError – If a retry count, delay, or retry filter has an invalid type.
ValueError – If a retry count or delay is negative or non-finite.
Notes
Exceptions raised by request transport or a session-level
after_requesthook are eligible for retry.retry_on_statuschecks returned raw response statuses without requiring an exception. Request-levelafter_hookcallbacks run after the retry operation and are outside its scope.