Skip to content

Polling

Pass a timeout to a client verb and the verb polls until a success status arrives. Consumers who want polling as a separate step use the poll routine from the resq.http surface on an already-built response wrapper.

poll operates on an already-built wrapper — it never issues the first request and never references a client or adapter. It calls raise_for_status to check the status. On a bad status, it retries through the wrapper's own reload until success or until the timeout window elapses. The mode (sync/async) is determined by the wrapper's type: a Response runs a sync loop; an AsyncResponse runs an async loop (await it).

from resq.http import poll

The easy way — a verb with a timeout

Most consumers never call poll themselves: passing a timeout to a client verb makes the verb poll internally and return the already-polled wrapper.

r = client.get("/job/42", timeout=30, delay=2)   # the verb polls for you

Sync poll

Build the primary response with a non-polling verb (timeout left at None), then poll it as a separate call:

from resq import Requests
from resq.http import poll

client = Requests("https://api.example.com", adapter="requests", timeout=5)
r = client.get("/job/42")            # single request, a Response
r = poll(r, timeout=30, delay=2)     # poll up to 30s, 2s apart

The same r object is returned; its underlying is refreshed in place on each retry.

Async poll

from resq import Requests
from resq.http import poll

async with Requests("https://api.example.com", adapter="httpx", timeout=5) as client:
    r = await client.get("/job/42")        # single request, an AsyncResponse
    r = await poll(r, timeout=30, delay=2) # poll up to 30s, 2s apart

When you do not need poll directly

Reach for poll directly only when the primary request and the polling loop must be separate steps — for example, inspecting the primary response before deciding to poll, or polling a wrapper obtained from elsewhere.

Handling exhaustion

If the window elapses without a success-status response, the LAST response is returned (its status is the final non-2xx). No exception is raised — inspect ok / status_code, or call reload to retry:

r = poll(r, timeout=30, delay=2)
if not r.ok:
    ...                        # window elapsed, last status non-2xx
    r.reload()                 # sync; await r.reload() for an AsyncResponse

Behavior & preconditions

  • timeout is the polling window in seconds, measured from the start of the call (not per attempt). None disables polling and returns the wrapper unchanged with no status check.
  • delay sets the seconds between attempts and is ignored when timeout is None. The client verbs default it to 1.0; when calling poll directly, pass it explicitly.
  • Polling retries only on a bad status — whatever raise_for_status raises on the engine: 4xx/5xx for requests; any non-2xx for httpx, including unfollowed 3xx such as 304. Transport-level failures (connection, TLS, read-timeout) propagate immediately and are not retried.
  • poll does not raise on window expiry; it returns the last response.
  • The mode is fixed by the wrapper's type — a sync Response yields a sync loop, an AsyncResponse yields an async loop (await the call and reload).