Generate UUID v4 in Python β€” uuid.uuid4()

Β·Backend DeveloperΒ·Reviewed byDmitri VolkovΒ·Published

Use the free online UUID v4 Generator directly in your browser β€” no install required.

Try UUID v4 Generator Online β†’

Every time I need a collision-resistant identifier for a database row, API trace, or session token, the answer is generate UUID v4 in Python β€” one line, zero dependencies: uuid.uuid4(). Python's built-in uuid module uses os.urandom() for cryptographically secure randomness. For a quick UUID without writing code, the online UUID v4 generator works instantly. This guide covers the UUID object's attributes, bulk generation, JSON serialization, database storage, validation, uuid-utils (~10Γ— faster Rust-backed drop-in), and the four most common mistakes β€” all with Python 3.8+.

Key Takeaways
  • β†’uuid.uuid4() is built into Python's stdlib β€” import uuid is all you need, no pip install.
  • β†’The return value is a uuid.UUID object, not a string β€” use str(), .hex, or .bytes to pick the representation that fits your storage layer.
  • β†’UUID v4 uses 122 random bits from os.urandom() β€” cryptographically secure, no MAC address or timestamp exposure.
  • β†’For high-throughput services, pip install uuid-utils is a drop-in replacement that is ~10x faster, powered by Rust.
  • β†’Never pass uuid.uuid4 (without parentheses) as a default argument directly in a dataclass or Pydantic model β€” it will share a single UUID across all instances.

What is UUID v4?

A UUID (Universally Unique Identifier) is a 128-bit label formatted as 32 hexadecimal digits split into five groups by hyphens: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. Version 4 is the most widely used variant: 122 of those 128 bits are randomly generated, and the remaining 6 bits encode the version (4) and variant (RFC 4122). There is no timestamp and no host identifier β€” the identifier is entirely opaque and privacy-safe. The probability of two independently generated v4 UUIDs colliding is so small that for practical purposes it never happens, even across distributed systems generating millions of IDs per second.

Before Β· Python
After Β· Python
event_id = "evt-" + str(random.randint(100000, 999999))  # fragile, not unique
event_id = str(uuid.uuid4())  # 3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e

uuid.uuid4() β€” The Standard Way to Generate UUID v4 in Python

The uuid module is part of Python's standard library. Calling uuid.uuid4() returns a uuid.UUID object with a full set of attributes for different representations. Converting to a string with str() produces the canonical hyphenated format that APIs, databases, and HTTP headers expect.

Python 3.8+ β€” minimal example
import uuid

# Generate a UUID v4
request_id = uuid.uuid4()

print(request_id)           # 3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e
print(type(request_id))     # <class 'uuid.UUID'>
print(request_id.version)   # 4

# Convert to string for JSON / HTTP headers
print(str(request_id))      # "3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e"
print(request_id.hex)       # "3b1f8a9d2c4e4f6a8b0d5e7c9f1a3d2e" (no dashes)
print(request_id.bytes)     # b';...' (16 raw bytes)

A common real-world pattern is attaching a UUID to every outbound API request so you can correlate logs across services. Here is a minimal requests session wrapper that injects a fresh UUID into every call:

Python 3.8+ β€” per-request trace ID
import uuid
import requests

def call_api(endpoint: str, payload: dict) -> dict:
    trace_id = str(uuid.uuid4())
    headers = {
        "X-Request-ID": trace_id,
        "Content-Type": "application/json",
    }
    response = requests.post(endpoint, json=payload, headers=headers, timeout=10)
    response.raise_for_status()
    return {"trace_id": trace_id, "data": response.json()}

# result["trace_id"] lets you grep the exact request across all service logs
result = call_api("https://api.example.com/v1/orders", {"product_id": "prod_7x2k", "qty": 3})
print(result["trace_id"])  # e.g. "3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e"

When generating UUIDs in bulk β€” for example, pre-populating a batch of database rows β€” a list comprehension is idiomatic and readable:

Python 3.8+ β€” bulk generation
import uuid

# Pre-generate IDs for 1000 telemetry events
event_ids = [str(uuid.uuid4()) for _ in range(1000)]
print(f"Generated {len(event_ids)} unique IDs")
print(event_ids[0])   # e.g. "a1c2e3f4-..."
print(event_ids[-1])  # different value every time

Need a quick UUID without running any code? Use the online UUID v4 generator to copy a fresh value in one click, or bulk-generate hundreds at once β€” useful for seeding test databases or populating fixture files.

Note:uuid.uuid4() calls os.urandom(16) internally, then sets bits 6–7 of byte 8 to 10 (variant) and bits 12–15 of byte 6 to 0100 (version 4). The remaining 122 bits are random. This is why you cannot trust the version unless you parse with uuid.UUID().

UUID Object Attributes and Representations

The uuid.UUID object exposes multiple representations of the same 128-bit value. Choosing the right one for your storage layer prevents silent data corruption and wasted bytes.

Attribute / Method
Type
Description
uuid.UUID(hex=...)
UUID
Parse an existing UUID from a hex string, with or without dashes.
.hex
str
32-character lowercase hex string with no hyphens β€” compact storage format.
.int
int
128-bit integer representation of the UUID β€” useful for arithmetic and sorting.
.bytes
bytes
16-byte big-endian binary representation β€” most efficient storage size.
.bytes_le
bytes
16-byte little-endian binary β€” matches Microsoft GUID byte order.
.fields
tuple
Six-tuple of UUID fields: (time_low, time_mid, time_hi_version, clock_seq_hi_variant, clock_seq_low, node).
.version
int | None
UUID version number (1–5, or None for non-standard UUIDs).
.variant
str
UUID variant string β€” "specified in RFC 4122" for standard UUIDs.
str(uuid_obj)
str
Canonical 36-character string with four hyphens: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.
Python 3.8+ β€” all representations
import uuid

u = uuid.uuid4()

print(str(u))         # "3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e"  (36 chars)
print(u.hex)          # "3b1f8a9d2c4e4f6a8b0d5e7c9f1a3d2e"      (32 chars, no dashes)
print(u.bytes)        # b';Š...'                       (16 bytes, big-endian)
print(u.bytes_le)     # b'Š...'                       (16 bytes, little-endian)
print(u.int)          # 78823... (128-bit integer)
print(u.version)      # 4
print(u.variant)      # 'specified in RFC 4122'

# Round-trip: reconstruct from string
reconstructed = uuid.UUID("3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e")
print(reconstructed == u)  # True (if u was that value)

For PostgreSQL with psycopg2 or asyncpg, pass the UUID object directly β€” the driver handles the mapping to the native uuid column type. For SQLite, use str(u) (TEXT) or u.bytes (BLOB, 16 bytes vs 36 for the string). For storage efficiency at scale, .bytes is 55% smaller than the canonical string.

Validating and Parsing UUID v4 Strings in Python

Whenever a UUID arrives from user input, a URL path parameter, or an upstream API, you should validate it before using it as a database key. The idiomatic approach is to attempt construction with uuid.UUID() and catch ValueError. You can also enforce that the incoming value is specifically version 4 by checking .version.

Python 3.8+ β€” validation helper
import uuid

def parse_uuid4(raw: str) -> uuid.UUID:
    """
    Parse and validate a UUID v4 string.
    Raises ValueError for invalid format or wrong version.
    """
    try:
        u = uuid.UUID(raw)
    except ValueError as exc:
        raise ValueError(f"Invalid UUID format: {raw!r}") from exc

    if u.version != 4:
        raise ValueError(f"Expected UUID v4, got v{u.version}: {raw!r}")

    return u

# Usage in a FastAPI / Flask route handler
def get_order(order_id: str):
    try:
        uid = parse_uuid4(order_id)
    except ValueError as exc:
        return {"error": str(exc)}, 400

    # safe to use uid in a DB query now
    return {"order_id": str(uid), "status": "processing"}
Note:uuid.UUID() accepts strings with or without hyphens, and also accepts the urn:uuid: prefix. So "3b1f8a9d2c4e4f6a8b0d5e7c9f1a3d2e" (no dashes) and "3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e" both parse to the same object.

UUID v4 in JSON Payloads and API Responses

The JSON standard has no UUID type β€” a UUID in JSON is always a string. That means you must convert the uuid.UUID object to a string before passing it to json.dumps(). The cleanest approach is a custom JSONEncoder subclass so you never have to scatter str() calls through your codebase.

Python 3.8+ β€” custom JSONEncoder for UUID
import json
import uuid
from datetime import datetime

class ApiEncoder(json.JSONEncoder):
    """Serialize UUID and datetime objects in JSON responses."""
    def default(self, obj):
        if isinstance(obj, uuid.UUID):
            return str(obj)
        if isinstance(obj, datetime):
            return obj.isoformat()
        return super().default(obj)

# Realistic API response with nested UUIDs
api_response = {
    "request_id": uuid.uuid4(),
    "created_at": datetime(2026, 3, 14, 9, 41, 0),
    "order": {
        "id": uuid.uuid4(),
        "customer_id": uuid.uuid4(),
        "items": [
            {"product_id": uuid.uuid4(), "sku": "NVX-9000", "qty": 2},
        ],
    },
}

print(json.dumps(api_response, indent=2, cls=ApiEncoder))
# {
#   "request_id": "3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e",
#   "created_at": "2026-03-14T09:41:00",
#   "order": {
#     "id": "a1c2e3f4-...",
#     ...
#   }
# }

For a one-off serialization call, the default= hook is simpler than subclassing:

Python 3.8+ β€” default= hook (one-off)
import json, uuid

event_id = uuid.uuid4()
payload = {"event_id": event_id, "action": "checkout"}

# Pass a callable; called only for types json can't handle
json_str = json.dumps(payload, default=str)
print(json_str)  # {"event_id": "3b1f8a9d-...", "action": "checkout"}

When receiving a response from an external API, parse UUID strings back to objects so your code gets the full attribute set and type safety:

Python 3.8+ β€” parsing UUID from an API response
import json
import uuid
import requests

def fetch_shipment(shipment_id: str) -> dict:
    """Fetch a shipment and return with typed UUID fields."""
    response = requests.get(
        f"https://api.logistics.example.com/v2/shipments/{shipment_id}",
        headers={"Accept": "application/json"},
        timeout=10,
    )
    response.raise_for_status()
    data = response.json()

    # Parse the UUID fields back to uuid.UUID objects
    try:
        data["id"] = uuid.UUID(data["id"])
        data["carrier_id"] = uuid.UUID(data["carrier_id"])
    except (KeyError, ValueError) as exc:
        raise RuntimeError(f"Malformed shipment response: {exc}") from exc

    return data

To update UUID fields in a JSON file on disk β€” for example, rotating a correlation ID in a config or seed file β€” read, mutate, and write back atomically:

Python 3.8+ β€” read, update, and write a JSON file
import json, uuid

def rotate_correlation_id(path: str) -> str:
    """Replace or add 'correlation_id' in a JSON file. Returns the new UUID."""
    try:
        with open(path) as f:
            data = json.load(f)
    except FileNotFoundError:
        data = {}
    except json.JSONDecodeError as exc:
        raise ValueError(f"Invalid JSON in {path!r}: {exc}") from exc

    new_id = str(uuid.uuid4())
    data["correlation_id"] = new_id

    with open(path, "w") as f:
        json.dump(data, f, indent=2)

    return new_id

If you don't want to run a script every time you need to inspect a UUID from an API response, paste it directly into the UUID Decoder β€” it shows you the version, variant, and all fields without any code.

Generate UUID v4 from the Command Line with Python

Python's uuid module does not expose a standalone CLI subcommand like python -m json.tool, but a one-liner covers the same use case. These are useful in shell scripts, CI pipelines, and whenever you need a throwaway identifier without opening a REPL.

bash
# Single UUID v4
python3 -c "import uuid; print(uuid.uuid4())"
# 3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e

# No-dashes (hex) format β€” useful for filenames and env vars
python3 -c "import uuid; print(uuid.uuid4().hex)"
# 3b1f8a9d2c4e4f6a8b0d5e7c9f1a3d2e

# Generate 5 UUIDs for a batch seed script
python3 -c "import uuid; [print(uuid.uuid4()) for _ in range(5)]"

# Use in a shell variable
DEPLOY_ID=$(python3 -c "import uuid; print(uuid.uuid4())")
echo "Deploying with ID: $DEPLOY_ID"
Note:On macOS and most Linux distributions, uuidgen (a C utility) produces UUID v4 values and is faster for pure shell scripts. Use the Python one-liner when you are already in a Python-centric environment and want consistency with how UUIDs are generated in your application code.

High-Performance UUID v4 with uuid-utils

The standard library's uuid.uuid4() is fast enough for most applications β€” at a few microseconds per call it handles thousands of IDs per second comfortably. If you are generating UUIDs on the hot path of a high-throughput service (bulk inserts, per-event telemetry at scale, or request ID generation under heavy load), uuid-utils is a drop-in replacement backed by Rust that benchmarks at roughly 10x the speed of the stdlib.

bash β€” install
pip install uuid-utils
Python 3.8+ β€” uuid-utils drop-in replacement
# uuid_utils is a drop-in replacement for the stdlib uuid module
import uuid_utils as uuid

# Same API as stdlib
request_id = uuid.uuid4()
print(request_id)           # 3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e
print(str(request_id))      # canonical string
print(request_id.hex)       # no-dashes hex
print(request_id.version)   # 4

# Also supports v7 (time-ordered, great for DB primary keys)
time_ordered_id = uuid.uuid7()
print(time_ordered_id)      # starts with current-timestamp prefix
Warning:uuid-utils's default mode returns its own UUID object type, which is compatible with the stdlib in most cases. If you need strict isinstance(u, uuid.UUID) checks from the standard library, use the compat mode: import uuid_utils.compat as uuid. Compat mode is slightly slower than default but still faster than stdlib.

UUID v4 in Dataclasses and Pydantic Models

Python dataclasses and Pydantic models both support UUID fields natively. The key pattern when using UUID as an auto-generated default is to pass the function reference, not a call result β€” otherwise every instance shares the same UUID.

Python 3.10+ β€” dataclass with UUID default
from dataclasses import dataclass, field
import uuid

@dataclass
class WorkerJob:
    job_id: uuid.UUID = field(default_factory=uuid.uuid4)
    worker_id: str = "worker-01"
    payload: dict = field(default_factory=dict)

job1 = WorkerJob(payload={"task": "resize_image", "src": "uploads/img_4932.png"})
job2 = WorkerJob(payload={"task": "send_email",   "to":  "ops@example.com"})

print(job1.job_id)  # unique per instance
print(job2.job_id)  # different from job1.job_id
print(job1.job_id == job2.job_id)  # False
Python 3.10+ β€” Pydantic v2 model with UUID
from pydantic import BaseModel, Field
import uuid

class OrderEvent(BaseModel):
    event_id: uuid.UUID = Field(default_factory=uuid.uuid4)
    order_id: uuid.UUID
    status: str
    amount_cents: int

event = OrderEvent(
    order_id=uuid.UUID("a1c2e3f4-5b6d-4e7f-8c9d-0a1b2c3d4e5f"),
    status="payment_confirmed",
    amount_cents=4995,
)

print(event.model_dump_json(indent=2))
# {
#   "event_id": "3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e",
#   "order_id": "a1c2e3f4-5b6d-4e7f-8c9d-0a1b2c3d4e5f",
#   "status": "payment_confirmed",
#   "amount_cents": 4995
# }
# Pydantic v2 serializes uuid.UUID as string automatically

Common Mistakes When Generating UUID v4 in Python

I've seen all four of these patterns appear in code reviews and production incidents β€” they are easy to miss because they do not raise an error immediately.

❌ Calling uuid4 Without Parentheses as a Default

Problem: Passing uuid.uuid4 (the function object) as a default value in a dataclass or model without wrapping it in default_factory β€” Python evaluates the default once at class definition time, so every instance shares the same UUID.

Fix: Use default_factory=uuid.uuid4 in dataclasses or Field(default_factory=uuid.uuid4) in Pydantic so a fresh UUID is generated per instance.

Before Β· Python
After Β· Python
@dataclass
class Session:
    # WRONG: evaluated once, all instances share this UUID
    session_id: uuid.UUID = uuid.uuid4()
@dataclass
class Session:
    # CORRECT: factory called per instance
    session_id: uuid.UUID = field(default_factory=uuid.uuid4)
❌ Comparing a UUID Object to a Plain String

Problem: uuid.UUID objects do not equal plain strings, so session_id == '3b1f8a9d-...' always returns False even when the value matches β€” silently breaking lookups.

Fix: Always compare UUID to UUID: wrap the string with uuid.UUID() before comparing, or convert both sides to str().

Before Β· Python
After Β· Python
# Returns False even when values match
if record["session_id"] == "3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e":
    revoke_session(record)
target = uuid.UUID("3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e")
if record["session_id"] == target:   # both are uuid.UUID
    revoke_session(record)

# Or normalize everything to strings at the boundary:
if str(record["session_id"]) == str(target):
    revoke_session(record)
❌ Storing .hex Instead of str() and Losing Dashes

Problem: uuid_obj.hex produces a 32-character string without hyphens. If downstream code expects the canonical 36-character dashed format (most APIs and databases do), it will reject or silently misparse the value.

Fix: Use str(uuid_obj) for the canonical 36-character format unless you have an explicit requirement for the compact hex form.

Before Β· Python
After Β· Python
# Stores "3b1f8a9d2c4e4f6a8b0d5e7c9f1a3d2e" β€” no dashes
payload = {"correlation_id": request_id.hex}
# Stores "3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e" β€” standard format
payload = {"correlation_id": str(request_id)}
❌ Using random.random() or secrets.token_hex() When a UUID is Expected

Problem: random.random() is not cryptographically secure, and secrets.token_hex(16) produces a 32-character hex string that is not a valid UUID β€” downstream validators that call uuid.UUID() on it will raise ValueError.

Fix: Use uuid.uuid4() whenever the receiving system expects a UUID-formatted identifier. Use secrets.token_hex() only when you explicitly need a random token that is not UUID-shaped.

Before Β· Python
After Β· Python
import random, secrets

# Not a UUID β€” will fail uuid.UUID() validation
request_id = secrets.token_hex(16)   # "a1b2c3d4e5f6..."
session_id = str(random.random())    # "0.8273..." β€” not even close
import uuid

request_id = str(uuid.uuid4())  # "3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e"
# Valid UUID v4, cryptographically secure

UUID Generation Methods in Python β€” Quick Comparison

All methods below produce 128-bit identifiers but differ in entropy source, privacy characteristics, and whether they require a third-party install.

Method
Source
Uniqueness
Privacy-safe
Custom Types
Speed
Install
uuid.uuid4()
Random (os.urandom)
2ΒΉΒ²Β² random bits
βœ…
N/A
Standard
Built-in
uuid.uuid1()
Timestamp + MAC
Time + host
❌ (MAC exposed)
N/A
Standard
Built-in
uuid.uuid3(name)
MD5 hash
Deterministic
βœ…
namespace+name
Standard
Built-in
uuid.uuid5(name)
SHA-1 hash
Deterministic
βœ…
namespace+name
Standard
Built-in
uuid_utils.uuid4()
Rust os.urandom
2ΒΉΒ²Β² random bits
βœ…
N/A
~10Γ— faster
pip install uuid-utils
secrets.token_hex(16)
os.urandom
128 random bits
βœ…
N/A
Fast
Built-in
str(uuid.uuid4())
Random
2ΒΉΒ²Β² random bits
βœ…
N/A
Standard
Built-in

Use uuid.uuid4() for general-purpose unique identifiers in web applications, distributed systems, and database primary keys when sortability is not required. Use uuid.uuid5() (or v3) for deterministic IDs derived from a known namespace and name β€” for example, generating a stable ID for a canonical URL. Switch to uuid_utils.uuid7() when you need time-ordered IDs for database indexes (avoids page splits in B-tree indexes at high insert rates). Reach for uuid_utils.uuid4() when raw generation throughput is the bottleneck.

UUID v4 vs UUID v7 β€” Which Should You Use?

The most common practical question is whether to use UUID v4 or the newer UUID v7 for database primary keys. Here is the short answer: use UUID v4 by default; switch to UUID v7 only when index fragmentation is a measured problem.

UUID v4 values are fully random, which means inserts land at random positions in a B-tree index. At moderate insert rates (hundreds to low thousands per second) this is fine β€” the index fits in the buffer pool and random writes are cheap. At very high insert rates, random placement causes frequent page splits and cache misses, increasing write amplification and slowing queries.

UUID v7 embeds a millisecond-precision Unix timestamp in the most-significant bits, so rows inserted close together in time also land close together in the index. This gives B-tree indexes (PostgreSQL, MySQL, SQLite) behaviour closer to an auto-increment integer: new rows always append to the end of the index, eliminating page splits. The trade-off is that UUID v7 encodes a timestamp, which leaks creation time β€” avoid it for user-facing IDs where creation time is sensitive.

In Python, UUID v7 is not in the standard library yet (as of Python 3.12). Generate it with pip install uuid-utils and call uuid_utils.uuid7(). It returns an object with the same attribute set as uuid.UUID, so migration from v4 is a one-line change in the ID factory.

For a one-click alternative without any Python setup, paste your UUID string into the UUID v4 generator and validator β€” it generates, validates, and decodes all fields in the browser.

Frequently Asked Questions

How do I generate a UUID v4 in Python?

Call uuid.uuid4() from Python's built-in uuid module. It returns a UUID object β€” convert to string with str() when you need a text representation. The module ships with the standard library so no pip install is needed.

Python
import uuid

session_id = uuid.uuid4()
print(session_id)           # e.g. 3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e
print(str(session_id))      # same canonical string
print(session_id.hex)       # 3b1f8a9d2c4e4f6a8b0d5e7c9f1a3d2e (no dashes)

What is the difference between uuid.uuid4() and str(uuid.uuid4())?

uuid.uuid4() returns a UUID object that has attributes like .hex, .bytes, .int, and .version. str(uuid.uuid4()) converts that object to a 36-character canonical string immediately, discarding the object. Keep the object if you need multiple representations; convert to string at the boundary where you pass the value to a JSON payload, database, or HTTP header.

Python
import uuid

u = uuid.uuid4()
print(type(u))          # <class 'uuid.UUID'>
print(u.version)        # 4
print(u.hex)            # 32-char hex, no dashes
print(u.bytes)          # 16-byte binary
print(str(u))           # canonical 36-char string with dashes

Is uuid.uuid4() cryptographically secure?

Yes. Python's uuid.uuid4() uses os.urandom() internally, which reads from the operating system's cryptographically secure random number generator (/dev/urandom on Linux/macOS, CryptGenRandom on Windows). The 122 random bits make collision probability negligible for any realistic workload. Do not confuse it with random.random(), which is not cryptographically secure.

Python
import uuid, os

# uuid4 internally calls os.urandom(16)
raw = os.urandom(16)
# uuid4 sets the version and variant bits before returning
u = uuid.UUID(bytes=raw, version=4)
print(u)  # valid v4 UUID from raw random bytes

How do I validate that a string is a valid UUID v4 in Python?

Parse with uuid.UUID() and check the .version attribute. If the string is not a valid UUID, uuid.UUID() raises a ValueError β€” catch it to handle invalid input. This also validates that the format (dashes, length) is correct.

Python
import uuid

def is_valid_uuid4(value: str) -> bool:
    try:
        u = uuid.UUID(value)
        return u.version == 4
    except ValueError:
        return False

print(is_valid_uuid4("3b1f8a9d-2c4e-4f6a-8b0d-5e7c9f1a3d2e"))  # True
print(is_valid_uuid4("not-a-uuid"))                              # False
print(is_valid_uuid4("3b1f8a9d-2c4e-1f6a-8b0d-5e7c9f1a3d2e"))  # False (v1, not v4)

How do I store UUIDs in a PostgreSQL or SQLite database from Python?

With PostgreSQL (via psycopg2 or asyncpg), pass the UUID object directly β€” the driver adapts it to the native UUID type. With SQLite, which has no native UUID type, store as TEXT using str(uuid_obj) or as BLOB using uuid_obj.bytes. SQLAlchemy has a UUID column type that handles this automatically across dialects.

Python
import uuid
import sqlite3

conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE events (id TEXT PRIMARY KEY, name TEXT)")

event_id = uuid.uuid4()
conn.execute("INSERT INTO events VALUES (?, ?)", (str(event_id), "user_signup"))
conn.commit()

row = conn.execute("SELECT * FROM events").fetchone()
# Reconstruct UUID object from stored string
retrieved_id = uuid.UUID(row[0])
print(retrieved_id.version)  # 4

Can I generate multiple UUIDs at once in Python?

Yes β€” use a list comprehension or a generator. Each call to uuid.uuid4() is independent and guaranteed to produce a distinct value. For bulk generation where throughput matters, uuid-utils (Rust-backed) is around 10x faster than the standard library.

Python
import uuid

# Generate 5 unique trace IDs for a batch request
trace_ids = [str(uuid.uuid4()) for _ in range(5)]
for tid in trace_ids:
    print(tid)
# Each line is a distinct UUID v4
  • β†’UUID v4 Generator β€” Generate UUID v4 values instantly in the browser β€” no Python environment needed. Copy a single value or bulk-generate hundreds at once.
  • β†’UUID v7 Generator β€” Generate time-ordered UUID v7 values β€” sortable by creation time, ideal for database primary keys where index fragmentation matters.
  • β†’UUID Decoder β€” Inspect any UUID β€” version, variant, timestamp (v1/v7), and node fields β€” without writing a parser from scratch.
  • β†’JWT Decoder β€” Decode and inspect JWT tokens, which often carry UUID subject claims (sub) or jti identifiers alongside session UUIDs.
MS
Maria SantosBackend Developer

Maria is a backend developer specialising in Python and API integration. She has broad experience with data pipelines, serialisation formats, and building reliable server-side services. She is an active member of the Python community and enjoys writing practical, example-driven guides that help developers solve real problems without unnecessary theory.

DV
Dmitri VolkovTechnical Reviewer

Dmitri is a DevOps engineer who relies on Python as his primary scripting and automation language. He builds internal tooling, CI/CD pipelines, and infrastructure automation scripts that run in production across distributed teams. He writes about the Python standard library, subprocess management, file processing, encoding utilities, and the practical shell-adjacent Python that DevOps engineers use every day.