How to generate a random UUID which is reproducible (with a seed) in Python

Question:

The uuid4() function of Python’s module uuid generates a random UUID, and seems to generate a different one every time:

In [1]: import uuid

In [2]: uuid.uuid4()
Out[2]: UUID('f6c9ad6c-eea0-4049-a7c5-56253bc3e9c0')

In [3]: uuid.uuid4()
Out[3]: UUID('2fc1b6f9-9052-4564-9be0-777e790af58f')

I would like to be able to generate the same random UUID every time I run a script – that is, I’d like to seed the random generator in uuid4(). Is there a way to do this? (Or achieve this by some other means)?

What I’ve tried so far

I’ve to generate a UUID using the uuid.UUID() method with a random 128-bit integer (from a seeded instance of random.Random()) as input:

import uuid
import random

rd = random.Random()
rd.seed(0)
uuid.UUID(rd.getrandbits(128))

However, UUID() seems not to accept this as input:

Traceback (most recent call last):
  File "uuid_gen_seed.py", line 6, in <module>
    uuid.UUID(rd.getrandbits(128))
  File "/usr/lib/python2.7/uuid.py", line 133, in __init__
    hex = hex.replace('urn:', '').replace('uuid:', '')
AttributeError: 'long' object has no attribute 'replace'

Any other suggestions?

Asked By: Kurt Peek

||

Answers:

Almost there:

uuid.UUID(int=rd.getrandbits(128))

This was determined with the help of help:

>>> help(uuid.UUID.__init__)
Help on method __init__ in module uuid:

__init__(self, hex=None, bytes=None, bytes_le=None, fields=None, int=None, version=None) unbound uuid.UUID method
    Create a UUID from either a string of 32 hexadecimal digits,
    a string of 16 bytes as the 'bytes' argument, a string of 16 bytes
    in little-endian order as the 'bytes_le' argument, a tuple of six
    integers (32-bit time_low, 16-bit time_mid, 16-bit time_hi_version,
    8-bit clock_seq_hi_variant, 8-bit clock_seq_low, 48-bit node) as
    the 'fields' argument, or a single 128-bit integer as the 'int'
    argument.  When a string of hex digits is given, curly braces,
    hyphens, and a URN prefix are all optional.  For example, these
    expressions all yield the same UUID:

    UUID('{12345678-1234-5678-1234-567812345678}')
    UUID('12345678123456781234567812345678')
    UUID('urn:uuid:12345678-1234-5678-1234-567812345678')
    UUID(bytes='x12x34x56x78'*4)
    UUID(bytes_le='x78x56x34x12x34x12x78x56' +
                  'x12x34x56x78x12x34x56x78')
    UUID(fields=(0x12345678, 0x1234, 0x5678, 0x12, 0x34, 0x567812345678))
    UUID(int=0x12345678123456781234567812345678)

    Exactly one of 'hex', 'bytes', 'bytes_le', 'fields', or 'int' must
    be given.  The 'version' argument is optional; if given, the resulting
    UUID will have its variant and version set according to RFC 4122,
    overriding the given 'hex', 'bytes', 'bytes_le', 'fields', or 'int'.
Answered By: Alex Hall

Based on alex’s solution, the following would provide a proper UUID4:

random.seed(123210912)
a = "%32x" % random.getrandbits(128)
rd = a[:12] + '4' + a[13:16] + 'a' + a[17:]
uuid4 = uuid.UUID(rd)
Answered By: Perennial

Faker makes this easy

>>> from faker import Faker
>>> f1 = Faker()
>>> f1.seed(4321)
>>> print(f1.uuid4())
cc733c92-6853-15f6-0e49-bec741188ebb
>>> print(f1.uuid4())
a41f020c-2d4d-333f-f1d3-979f1043fae0
>>> f1.seed(4321)
>>> print(f1.uuid4())
cc733c92-6853-15f6-0e49-bec741188ebb
Answered By: citynorman

This is based on a solution used here:

import hashlib
import uuid

m = hashlib.md5()
m.update(seed.encode('utf-8'))
new_uuid = uuid.UUID(m.hexdigest())
Answered By: user10229295

Gonna add this here if anyone needs to monkey patch in a seeded UUID. My code uses uuid.uuid4() but for testing I wanted consistent UUIDs. The following code is how I did that:

import uuid
import random

# -------------------------------------------
# Remove this block to generate different
# UUIDs everytime you run this code.
# This block should be right below the uuid
# import.
rd = random.Random()
rd.seed(0)
uuid.uuid4 = lambda: uuid.UUID(int=rd.getrandbits(128))
# -------------------------------------------

# Then normal code:

print(uuid.uuid4().hex)
print(uuid.uuid4().hex)
print(uuid.uuid4().hex)
print(uuid.uuid4().hex)

Since the straight-forward solution hasn’t been posted yet to generate consistent version 4 UUIDs:

import random
import uuid

rnd = random.Random()
rnd.seed(123) # NOTE: Of course don't use a static seed in production

random_uuid = uuid.UUID(int=rnd.getrandbits(128), version=4)

where you can see then:

>>> random_uuid.version
4

This doesn’t just "mock" the version information. It creates a proper UUIDv4:

The version argument is optional; if given, the resulting UUID will have its variant and version number set according to RFC 4122, overriding bits in the given hex, bytes, bytes_le, fields, or int.

Python 3.8 docs

Answered By: h345k34cr

Simple solution based on the answer of @user10229295, with a comment about the seed.
The Edit queue was full, so I opened a new answer:

import hashlib
import uuid

seed = 'Type your seed_string here' #Read comment below

m = hashlib.md5()
m.update(seed.encode('utf-8'))
new_uuid = uuid.UUID(m.hexdigest())

Comment about the string ‘seed’:
It will be the seed from which the UUID will be generated: from the same seed string will be always generated the same UUID. You can convert integer with some significance as string, concatenate different strings and use the result as your seed. With this you will have control on the UUID generated, which means you will be able to reproduce your UUID knowing the seed you used: with the same seed, the UUID generated from it will be the same.

Answered By: Andrea Baldino

Quickstart

if your goal is a reproducible UUID, here’s one concise approach

import uuid
seeded_uuid = uuid.UUID(bytes=b"z123456789101112") # 7a313233-3435-3637-3839-313031313132

How does this work internally ?

Using binary strings allows almost anything to act as a seed. You could also use alternative deterministic hashes that will take some data and give you a 32 byte string representing that data. There’s a lot more sophistication underneath the uuid call but at it’s core here’s how a seeded uuid call works

initial_seed = b"z123456789101112"

# use this function to validate initial seed
is_valid = lambda x: len(x) == 16 and isinstance(x, bytes)

# for each byte get its unicode int value, convert to hex and concatenate as string
hex_rep = "".join([f"{b:x}" for b in initial_seed]) # 7a313233343536373839313031313132

# for a uuid, storing an int representation unlocks O(1) comparision
int_rep = int(hex_rep, base=16)  # 162421256209101963464626711665304482098

# string representation for readability
str_rep = f"
{hex_rep[0:8]}-
{hex_rep[8:12]}-
{hex_rep[12:16]}-
{hex_rep[16:20]}-
{hex_rep[20:]}"  # 7a313233-3435-3637-3839-313031313132

Answered By: auxeon
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.