Skip to content

Retries

Retries in Zapros are implemented as a handler — wrapping another handler to automatically retry failed requests with exponential backoff.

Setup

python
from zapros import (
    AsyncClient,
    RetryHandler,
    AsyncStdNetworkHandler,
)

client = AsyncClient(
    handler=RetryHandler(AsyncStdNetworkHandler())
)
python
from zapros import (
    Client,
    RetryHandler,
    StdNetworkHandler,
)

client = Client(handler=RetryHandler(StdNetworkHandler()))

Basic usage

By default, RetryHandler retries requests that fail with specific status codes or network errors:

python
from zapros import (
    AsyncClient,
    RetryHandler,
    AsyncStdNetworkHandler,
)

client = AsyncClient(
    handler=RetryHandler(AsyncStdNetworkHandler())
)

async with client:
    response = await client.request(
        "GET",
        "https://api.example.com/data",
    )
python
from zapros import (
    Client,
    RetryHandler,
    StdNetworkHandler,
)

client = Client(handler=RetryHandler(StdNetworkHandler()))

with client:
    response = client.request(
        "GET",
        "https://api.example.com/data",
    )

Default behavior

Status codes

The following status codes are retried automatically:

  • 429 - Too Many Requests (rate limit)
  • 500 - Internal Server Error
  • 502 - Bad Gateway
  • 503 - Service Unavailable
  • 504 - Gateway Timeout

Safe HTTP methods

Only idempotent methods are retried by default:

  • GET, HEAD, PUT, DELETE, OPTIONS, TRACE

Non-idempotent methods like POST and PATCH are not retried on status codes to avoid duplicate operations.

Network errors

Pre-transmission errors are always retried, regardless of HTTP method:

  • ConnectionError, ConnectionRefusedError
  • Connection timeouts and DNS errors
  • SSL/Certificate errors
python
client = AsyncClient(
    handler=RetryHandler(AsyncStdNetworkHandler())
)

async with client:
    response = await client.request(
        "POST",
        "https://api.example.com/create",
        json={"name": "test"},
    )

Configuration

Customize retry behavior with these parameters:

python
from zapros import (
    AsyncClient,
    RetryHandler,
    AsyncStdNetworkHandler,
)

client = AsyncClient(
    handler=RetryHandler(
        AsyncStdNetworkHandler(),
        max_attempts=5,
        backoff_factor=1.0,
        backoff_max=120.0,
        backoff_jitter=0.5,
    )
)

Parameters

  • max_attempts (default: 4) - Maximum number of attempts (including the initial request)
  • backoff_factor (default: 0.5) - Base delay multiplier for exponential backoff
  • backoff_max (default: 60.0) - Maximum delay between retries in seconds
  • backoff_jitter (default: 1.0) - Jitter factor to randomize delays (0.0 to 1.0)

Backoff calculation

Delay between retries follows exponential backoff:

delay = min(backoff_factor * (2 ** attempt), backoff_max)

With jitter applied to prevent thundering herd:

jitter_range = delay * backoff_jitter
delay = delay ± random(jitter_range)

Custom retry policy

Implement your own retry logic using the RetryPolicy protocol:

python
from zapros import (
    Request,
    Response,
    RetryHandler,
    AsyncStdNetworkHandler,
    AsyncClient,
)


class CustomRetryPolicy:
    def should_retry(
        self,
        *,
        request: Request,
        response: Response | None,
        error: Exception | None,
        attempt: int,
    ) -> bool:
        if error is not None:
            return True

        if response and response.status == 418:
            return True

        return False


client = AsyncClient(
    handler=RetryHandler(
        AsyncStdNetworkHandler(),
        policy=CustomRetryPolicy(),
        max_attempts=3,
    )
)

Retry on specific errors

python
from zapros import TimeoutError


class TimeoutRetryPolicy:
    def should_retry(
        self,
        *,
        request: Request,
        response: Response | None,
        error: Exception | None,
        attempt: int,
    ) -> bool:
        if error and isinstance(error, TimeoutError):
            return True
        return False

Retry POST requests

python
class AlwaysRetryPolicy:
    def should_retry(
        self,
        *,
        request: Request,
        response: Response | None,
        error: Exception | None,
        attempt: int,
    ) -> bool:
        if error:
            return True
        if response and response.status >= 500:
            return True
        return False


client = AsyncClient(
    handler=RetryHandler(
        AsyncStdNetworkHandler(),
        policy=AlwaysRetryPolicy(),
    )
)

async with client:
    response = await client.request(
        "POST",
        "https://api.example.com/create",
        json={"data": "value"},
    )

Combining with other handlers

Chain RetryHandler with other handlers like cookies or auth:

python
from zapros import (
    AsyncClient,
    RetryHandler,
    CookieHandler,
    AsyncStdNetworkHandler,
)

client = AsyncClient(
    handler=RetryHandler(
        CookieHandler(AsyncStdNetworkHandler()),
        max_attempts=3,
    )
)