-
RetryCategory — How a classified exception drives the retry loop.
comicbox.formats.base.online.retry¶
source module comicbox.formats.base.online.retry
Exponential-backoff retry decorator for online API calls.
Wraps a callable that talks to an upstream API. Retries transient errors (rate-limit, 5xx) with exponential backoff up to max_retries. Honors the upstream’s retry_after hint when present (mokkari sets this on RateLimitError). Permanent failures — auth errors, not-found responses, and requests the upstream rejected as malformed — are never retried.
Which failure is which is the source’s call, not this module’s: the bound instance’s classify_retry_exception (each source contributes one for its own client library; see OnlineSource.classify_retry_exception) returns a RetryCategory. Exceptions no classifier claims fall back to a conservative default: programmer/config errors (_NON_RETRIABLE) raise immediately, everything else retries on the generic schedule.
Classes
Functions
-
request_cancel — Wake every in-flight retry sleep and abandon its retry loop.
-
clear_cancel — Reset the cancel signal so later calls may retry normally.
-
is_cancelled — Whether a cancel has been requested.
-
interruptible_sleep — Sleep, waking early and raising if a cancel is requested.
-
with_retry — Wrap a callable with retry-on-rate-limit / 5xx; never retries auth errors.
source class RetryCategory()
Bases : Enum
How a classified exception drives the retry loop.
source request_cancel() → None
Wake every in-flight retry sleep and abandon its retry loop.
source clear_cancel() → None
Reset the cancel signal so later calls may retry normally.
source is_cancelled() → bool
Whether a cancel has been requested.
source interruptible_sleep(seconds: float) → None
Sleep, waking early and raising if a cancel is requested.
Raising (rather than returning early) is what stops the retry loop from immediately re-issuing the call it was backing off from.
Raises
source with_retry(*, max_retries: int | None = None, sleep: Callable[[float], None] = interruptible_sleep, max_wait_s: float = _MAX_TOTAL_WAIT_S) → Callable[[Callable[…, T]], Callable[…, T]]
Wrap a callable with retry-on-rate-limit / 5xx; never retries auth errors.
Rate-limit errors get a separate retry budget (_MAX_RATE_LIMIT_RETRIES) and a longer delay schedule (_RATE_LIMIT_SCHEDULE) than other retriable failures. The generic 1-2-4-8-16s schedule tops out at 31s of total wait, which is far too short for hourly-cap recovery (ComicVine’s 200/hr can require several minutes to clear). The rate-limit schedule (30s, 1m, 2m, 5m, 10m) gives that window time to slide forward.
When the exception carries a retry_after attribute (mokkari does this), we honor it directly — server hint always wins over our blind schedules.
max_wait_s bounds the TOTAL time one call may spend sleeping across all its attempts. Without it the attempt budgets alone allow a single call to block for the better part of an hour (rate-limit schedule) or several (an honored server hint per attempt).
A source that sets paces_rate_limit keeps the attempt budgets but does no sleeping of its own on a rate-limit error: its RateGate has already absorbed the server’s hint, and sleeping it again here would double the wait and then release every worker at the same instant.
max_retries left unset means “whatever the user configured”: online.tuning.retry_budget off the instance, or 5. Passing it explicitly pins that call site instead, for calls that should give up cheaply rather than spend a user’s whole budget — an optional prefetch, say, which has a working fallback.
The default sleep is interruptible_sleep, so a cancel wakes waiting calls everywhere rather than only in OnlineSession, which used to be the sole injector of a cancellable sleep. Pass sleep=time.sleep for an uninterruptible wait; a per-instance retry_sleep attribute still overrides both (_resolve_sleep).