Python 3.14.8 urllib: poll a Sume job with only the stdlib

Python 3.14.8 shipped 30 September 2026. A urllib-only loop for GET /v1/jobs/:id/status with a 20-minute client deadline and no third-party packages.

5 min readSume
All posts

Short answer

You do not need requests to poll Sume. urllib.request with a timeout, a while loop bounded by time.monotonic(), and time.sleep(next_poll_after_seconds) covers it. The Python 3.14.8 page gives 30 September 2026 as the release date and describes around 354 bugfixes, build improvements and documentation changes since 3.14.7.

The same page says the release addressed nine security vulnerabilities, naming SSL context handling, tarfile extraction filters, zipfile decompression and urllib credential management among them. That is a reason to keep a small poller on a current patch release rather than to pin an old interpreter.

What the 3.14.8 page says

Only the security note is relevant to a stdlib HTTP client. The rest is general maintenance.

Python 3.14.8 facts (read 2026-10-03)
ItemDetail
Release date30 September 2026
Sizearound 354 changes from 142 contributors since 3.14.7
Securitynine vulnerabilities addressed, including urllib credential management
BundledExpat 2.8.5; OpenSSL 3.5.9 for certain platforms

A loop with a client-side deadline

Sume's docs call this the official equivalent of a client subscribe(): submit with async, poll the status, read the result only when it completed. The deadline lives in your process and can be minutes, because it is not an HTTP hold. Run SUME_API_KEY=... python3 poll.py job_123.

import json, os, sys, time, urllib.request

def get(url):
    req = urllib.request.Request(
        url, headers={"Authorization": "Bearer " + os.environ["SUME_API_KEY"]})
    with urllib.request.urlopen(req, timeout=20) as res:
        return json.load(res)["data"]

base = f"https://api.sume.com/v1/jobs/{sys.argv[1]}"
deadline = time.monotonic() + 20 * 60
while time.monotonic() < deadline:
    s = get(base + "/status")
    if s["terminal"]:
        break
    time.sleep(s.get("next_poll_after_seconds") or 2)
else:
    raise SystemExit("deadline passed; the job is still running and still billing")

if s["sume_status"] == "completed":
    print(json.dumps(get(base + "/result"), indent=2))
else:
    print("terminal:", s["sume_status"])

Failure paths worth handling

urllib raises HTTPError on a non-2xx answer, so a 429 surfaces as an exception with a Retry-After header, and a 404 not_found means the job belongs to another workspace or another member's key. Catch HTTPError around get if you want to sleep and continue on 429 instead of exiting.

GET /v1/jobs/:id/result answers 409 job_not_completed for a failed or canceled job, which is why the script branches on sume_status before calling it.

What Sume does and does not do

Sume keeps the job running and billing after your deadline passes, and exposes POST /v1/jobs/:id/cancel for a job that has not started generation. After generation starts the cancel route answers 409 job_generation_already_started and the job runs to completion.

The Developer API has no streaming transport, so a loop like this one, or a webhook, is the way to learn the outcome.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume