Retry and Timeouts

pypresscart retries transient failures with exponential backoff + jitter. This page covers the retry model, what’s retryable, and how to tune it.

What gets retried

Condition

Retried?

Why

429 Too Many Requests

Rate limited — back off and retry

500 Internal Server Error

Possibly transient

502 Bad Gateway

Edge/upstream flap

503 Service Unavailable

Server asking for a break

504 Gateway Timeout

Transient upstream slowness

Other 5xx (e.g. 501, 505)

Not expected to be transient

400 / 401 / 403 / 404

Your payload/token/resource — won’t fix itself

Network errors (timeout, connection reset, DNS failures)

Often transient

Everything else raises immediately.

Backoff formula

delay = min(retry_backoff_max, retry_backoff_base * (2 ** attempt))
if jitter > 0:
    delay += delay * jitter * random()

Where attempt is 0-indexed (0 for the first retry).

With defaults (base=0.25, max=4.0, jitter=0.1), you get approximately:

Attempt

Target delay

With +10% jitter

0

0.25s

0.25–0.275s

1

0.50s

0.50–0.55s

2

1.00s

1.00–1.10s

3

2.00s

2.00–2.20s

4+

4.00s (cap)

4.00–4.40s

Retry-After handling

If the server returns Retry-After on a 429 or 5xx, the library uses that value (capped by retry_backoff_max) instead of the computed delay. Both integer-seconds and HTTP-date formats are accepted — the library parses seconds; unparseable values fall back to the computed delay.

Attempt count

max_retries is additional attempts beyond the first. The total request count is max_retries + 1.

client = PresscartClient(api_token="pc_...", max_retries=3)
# First try + 3 retries = up to 4 requests.

Disabling retry

For deterministic tests or scripts that must fail fast:

client = PresscartClient(api_token="pc_...", max_retries=0)

Aggressive retry for batch jobs

client = PresscartClient(
    api_token="pc_...",
    max_retries=8,
    retry_backoff_base=0.5,
    retry_backoff_max=30.0,
    retry_jitter=0.2,
)

Total worst-case wait: ~min(sum(0.5 * 2**i), cap) for i in range(8) ≈ 60–90s of backoff for a single call.

Timeouts

timeout applies to each individual attempt (not the total elapsed time across retries). A request that retries 3× with a 30s timeout can take up to ~90s + backoff.

For strict end-to-end deadlines, wrap the call:

import time
from pypresscart import PresscartError

deadline = time.monotonic() + 60
try:
    result = client.orders.list()
except PresscartError:
    if time.monotonic() > deadline:
        raise TimeoutError("exceeded deadline")
    raise

Or enforce via an external watchdog (signal alarm, anyio cancel scope, etc.).

Idempotency

Presscart’s API is:

  • Idempotent for GET, DELETE, PUT, PATCH — safe to retry.

  • Not idempotent for POST — retrying a POST /orders/checkout after a network timeout could double-create an order.

pypresscart retries network failures on all methods, including POST. If you need stricter POST semantics, set max_retries=0 for those calls specifically — build a second “idempotent-only” client:

strict_client = PresscartClient(api_token="pc_...", max_retries=0)
strict_client.orders.create_checkout(body)  # no retries

Observing retries

pypresscart doesn’t emit retry events. To see them, attach a session hook:

import requests

sess = requests.Session()
sess.hooks["response"].append(
    lambda r, *a, **kw: print(f"{r.status_code} {r.request.method} {r.url}")
)
client = PresscartClient(api_token="pc_...", session=sess)

Every retried attempt fires the hook, so you’ll see repeated log lines for a 429 storm.