Python 3.14 uuid.uuid7() as a Sume Idempotency-Key: when it is safe

uuid.uuid7() is new in Python 3.14 and makes a tidy Idempotency-Key for Sume submits, if you generate it once per intent and store it. A runnable stdlib sample.

4 min readSume
All posts

Yes, uuid.uuid7() works as a Sume Idempotency-Key, as long as you call it once per intent, save the value before the first send, and reuse that same value on every retry. The function is new: the Python uuid docs (read 2026-10-10) say it was added in Python 3.14 and follows RFC 9562 section 5.7.

The danger is not the UUID version. It is calling uuid7() inside the retry loop, which turns every retry into a new paid job.

What uuid7 gives you that uuid4 does not

A version 7 UUID starts with a 48-bit timestamp and adds a 42-bit counter, per the Python docs read on 2026-10-10. Two keys made a moment apart sort in creation order, which helps when you read keys back out of your own database or a log and want them in submit order. A version 4 UUID is random and carries no order.

Sume does not care which version you send. The API treats the header as an opaque string tied to the submit, and the job record echoes it back as idempotency_key, which is the column you join on when you need to find a job again after a crash. What matters to Sume is that the same string always means the same request.

A runnable stdlib sample

This file needs Python 3.14 and nothing else. It makes the key once, prints it so you can see what to persist, and retries only on the codes that can succeed later. Note that a 429 queue_full also lands in the retry set here, so keep tries small.

import json, os, time, urllib.error, urllib.request, uuid

BASE = "https://api.sume.com"
RETRYABLE = {408, 429, 500, 502, 503, 504}


def submit(body: dict, key: str, tries: int = 3) -> dict:
    req = urllib.request.Request(
        f"{BASE}/v1/image-1.0/generate",
        json.dumps(body).encode(),
        {"x-api-key": os.environ["SUME_API_KEY"],
         "Content-Type": "application/json", "Idempotency-Key": key},
    )
    for attempt in range(tries):
        try:
            with urllib.request.urlopen(req, timeout=30) as r:
                return json.load(r)["data"]
        except urllib.error.HTTPError as e:
            if e.code not in RETRYABLE or attempt == tries - 1:
                raise
        except (urllib.error.URLError, TimeoutError):
            if attempt == tries - 1:
                raise
        time.sleep(2**attempt)


key = str(uuid.uuid7())  # once per intent: save it before the first send
print("key", key)
print(submit({"prompt": "Matte black bottle on marble", "mode": "async"}, key))

Rules that keep a retry from becoming a second job

The errors page says to retry rate limits with backoff and an idempotency key, and the job docs describe 409 idempotency_conflict when the same key arrives with a different body. Both rules point at the same habit: bind a key to one exact request.

  • Create the key where the user intent is created, for example when a row is inserted, and store it on that row.
  • Reuse it for every retry of that request, including retries after a process restart.
  • Never reuse it for a different prompt or settings. A changed body under the same key gets a 409, and that is the API protecting you.
  • A uuid7 holds a creation time. If you do not want submit times visible in logs shared outside your team, use uuid4 instead.

When uuid7 is the wrong tool

If the same business event can be submitted from two machines, a random-looking UUID created on each machine defeats idempotency, because the two keys differ. Derive the key from the business id instead, for example promo-8823-v1, and the second machine sends the same string. Use uuid7() when exactly one place creates the intent.

One more habit pays off: log the key next to the request_id from the error envelope. When support or you must trace a double charge, those two strings connect your row to the API side in one search.

There is also the older-runtime case. Python 3.13 and earlier have no uuid7, so code that must run on both needs a fallback to uuid4, and a version check at startup is clearer than an AttributeError at the first submit.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume