Python urllib timeout vs Sume's 30 second sync wait
A client read timeout shorter than Sume's sync wait fails before the server answers. A tested demo of the timeout behavior and how to pick a margin above 30 s.

If you submit in sync mode with a wait of up to 30 seconds, your client's read timeout must be longer than the wait, or your code gives up while the server is still holding the connection. A client timeout does not cancel the job, so you have then lost the response of a job that still runs and bills.
The demo uses a local server that sleeps two seconds, standing in for a longer wait, and compares two client timeouts.
The demo
With a 1 second timeout the client raised TimeoutError after 1.0 seconds. With a 5 second timeout it received the response, in about 2 to 3 seconds in the run.
Note that urllib's timeout is not a total deadline. It applies to individual socket operations, such as connect and each read, so a server that trickles bytes can keep a request alive longer than the number you passed. For sync mode the server sends nothing until it has the answer or its wait ends, so the read timeout behaves like the deadline you expect. If you need a hard total limit, run the call in a thread or an executor with its own overall timeout, and treat expiry the same way as a client timeout: the job may exist.
import http.server, threading, time, urllib.request
class Slow(http.server.BaseHTTPRequestHandler):
def do_GET(self):
time.sleep(2) # stands in for a 30 s sync wait
self.send_response(200); self.end_headers(); self.wfile.write(b"{}")
def log_message(self, *a): pass
srv = http.server.HTTPServer(("127.0.0.1", 0), Slow)
threading.Thread(target=srv.serve_forever, daemon=True).start()
url = f"http://127.0.0.1:{srv.server_port}/"
for timeout in (1, 5): # real code: 5 -> 35 against wait_timeout_seconds=30
t0 = time.time()
try:
urllib.request.urlopen(url, timeout=timeout)
print(f"timeout={timeout}s: got a response after {time.time()-t0:.1f}s")
except TimeoutError:
print(f"timeout={timeout}s: TimeoutError after {time.time()-t0:.1f}s")Choosing the timeout
Sume documents that wait_timeout_seconds is clamped to 0..30, and that a timed-out wait is still a 2xx with the job id plus sync.timed_out or sync.capacity_exhausted. So a client with a read timeout somewhat above 30 seconds should always get a response. The margin is yours to pick; the docs do not name one.
| Situation | What to do |
|---|---|
| Your timeout fires before the server answers | Job may exist; look it up before submitting again |
| Response is 2xx with sync.timed_out | Poll the job id; do not resubmit |
| You resubmit with the same Idempotency-Key | Returns the original job |
| You resubmit without a key | May create a second paid job |
Why the key matters here
The lost response is the case Idempotency-Key exists for. Send one on every submit, and a retry after your own timeout will return the original job instead of creating another.
Tradeoffs
A long read timeout ties up a worker thread for up to 30 seconds. If you submit many jobs, use async or webhook mode and avoid the wait altogether.
Sources
Related posts
More in Developers
- Python video fallback ladder when a model id is gone
A short Python ladder posts to Sume's /v1/videos with the first id that exists and moves on after a 404 model_not_found. Needs SUME_API_KEY, and runs.
- R httr2: submit an AI video job, poll it, and save the MP4 (Wan 3.0)
An R script posts a Wan 3.0 job to Sume with httr2, loops on /v1/jobs/{id}/status using next_poll_after_seconds and writes the finished clip to disk.
- Rails Sidekiq job that polls an AI video API with perform_in
A Sidekiq worker reads Sume's /v1/jobs/{id}/status once, then reschedules itself with perform_in using next_poll_after_seconds until the job is terminal.
- Rails Active Job that polls an AI video API: retry_job and wait
An Active Job on Solid Queue reads Sume's job status once and calls retry_job with wait from next_poll_after_seconds until the video job is terminal.
Written by Sume