Skip to content

Retries, timeouts, hooks

RetryPolicy

Defaults retry HTTP {429, 502, 503, 504} (and gRPC {UNAVAILABLE, DEADLINE_EXCEEDED, RESOURCE_EXHAUSTED}) up to 3 times with exponential backoff + jitter. Retry-After is honoured.

RetryPolicy dataclass

RetryPolicy(attempts: int = 3, backoff: float = 0.25, backoff_cap: float = 8.0, jitter: float = 0.1, retry_statuses: frozenset[int] = DEFAULT_RETRY_STATUSES, retry_methods: frozenset[str] = (lambda: DEFAULT_RETRY_METHODS)(), retry_grpc_statuses: frozenset[str] = (lambda: DEFAULT_RETRY_GRPC_STATUSES)(), respect_retry_after: bool = True)

Exponential-backoff retry policy for transient HTTP/gRPC failures.

Each retry attempt waits min(backoff_cap, backoff * 2 ** (attempt - 1)) + random.uniform(0, jitter) seconds before firing. When the server sends a Retry-After header (HTTP) and respect_retry_after is True, the effective delay is max(backoff, retry_after) — server hints extend backoff but never shorten it.

Note: retries replay the whole request, including the body. The server does not support range/resume uploads, so a 502 at the tail end of a large PDF upload triggers a full re-upload on the next attempt. For very large payloads over unstable links, keep attempts low and let your own pipeline manage the retry budget.

PARAMETER DESCRIPTION
attempts

Total request attempts before giving up. 1 disables retries entirely. Default 3 (one initial + two retries).

TYPE: int DEFAULT: 3

backoff

Base delay in seconds for the first retry. Doubles each subsequent attempt up to backoff_cap.

TYPE: float DEFAULT: 0.25

backoff_cap

Maximum exponential delay (seconds) before jitter is added. Caps the geometric growth.

TYPE: float DEFAULT: 8.0

jitter

Upper bound (seconds) of uniform random jitter added to each delay. Prevents thundering-herd reconnects.

TYPE: float DEFAULT: 0.1

retry_statuses

HTTP status codes considered transient. Defaults to {429, 502, 503, 504}.

TYPE: frozenset[int] DEFAULT: DEFAULT_RETRY_STATUSES

retry_methods

HTTP methods eligible for retry. Defaults to {"GET", "POST"}; OCR uploads are POST.

TYPE: frozenset[str] DEFAULT: (lambda: DEFAULT_RETRY_METHODS)()

retry_grpc_statuses

gRPC status names eligible for retry. Defaults to {"UNAVAILABLE", "DEADLINE_EXCEEDED", "RESOURCE_EXHAUSTED"}. INTERNAL is deliberately excluded — it signals a server-side bug, not a transient blip.

TYPE: frozenset[str] DEFAULT: (lambda: DEFAULT_RETRY_GRPC_STATUSES)()

respect_retry_after

When True, honour Retry-After headers as a lower bound on the next attempt's delay. Disable to keep client-driven backoff regardless of server hints.

TYPE: bool DEFAULT: True

Example
from turboocr import Client, RetryPolicy

policy = RetryPolicy(attempts=5, backoff=0.5, backoff_cap=10.0)
client = Client(retry=policy)
METHOD DESCRIPTION
should_retry_status
should_retry_method
should_retry_grpc_status_name
delay_for
delay_with_retry_after
ATTRIBUTE DESCRIPTION
attempts

TYPE: int

backoff

TYPE: float

backoff_cap

TYPE: float

jitter

TYPE: float

retry_statuses

TYPE: frozenset[int]

retry_methods

TYPE: frozenset[str]

retry_grpc_statuses

TYPE: frozenset[str]

respect_retry_after

TYPE: bool

attempts class-attribute instance-attribute

attempts: int = 3

backoff class-attribute instance-attribute

backoff: float = 0.25

backoff_cap class-attribute instance-attribute

backoff_cap: float = 8.0

jitter class-attribute instance-attribute

jitter: float = 0.1

retry_statuses class-attribute instance-attribute

retry_statuses: frozenset[int] = DEFAULT_RETRY_STATUSES

retry_methods class-attribute instance-attribute

retry_methods: frozenset[str] = field(default_factory=lambda: DEFAULT_RETRY_METHODS)

retry_grpc_statuses class-attribute instance-attribute

retry_grpc_statuses: frozenset[str] = field(default_factory=lambda: DEFAULT_RETRY_GRPC_STATUSES)

respect_retry_after class-attribute instance-attribute

respect_retry_after: bool = True

should_retry_status

should_retry_status(code: int) -> bool

should_retry_method

should_retry_method(method: str) -> bool

should_retry_grpc_status_name

should_retry_grpc_status_name(status_name: str) -> bool

delay_for

delay_for(attempt: int) -> float

delay_with_retry_after

delay_with_retry_after(attempt: int, retry_after_header: str | None) -> float

Timeouts

Two layers:

  • Client-wide: Client(timeout=30.0, ...) sets the per-request default.
  • Per-call: client.recognize_image("page.png", timeout=15.0) overrides the default for that single call. A per-call timeout=None means "no per-call override — use the client default".

For finer control, pass a pre-built httpx.Timeout via http_client=:

import httpx
from turboocr import Client

http = httpx.Client(
    base_url="http://localhost:8000",
    timeout=httpx.Timeout(connect=2.0, read=60.0, write=60.0, pool=30.0),
)
client = Client(http_client=http)

Hooks

on_request / on_response are httpx event hooks invoked around every request. Use them for OpenTelemetry spans, request counters, or simple stdout tracing.

import httpx
from turboocr import Client

def on_request(request: httpx.Request) -> None:
    print(f"-> {request.method} {request.url.path}")

def on_response(response: httpx.Response) -> None:
    print(f"<- {response.status_code} {response.request.url.path}")

client = Client(on_request=on_request, on_response=on_response)

See docs/12_hooks_and_logging.py for a runnable version.