81

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?

Community
  • 1
  • 1
Kurt Peek
  • 52,165
  • 91
  • 301
  • 526
  • It obviously expects some kind of string, the mention of `hex` suggests you could obtain it by calling `hey(rd.getrandbits(128))`. However, you won't end up with a uuid4. – L3viathan Dec 16 '16 at 14:48
  • You need a method to produce a random hex digit (lowercase). You need a second method to produce a random pick from {8, 9, a, b}. Put these together in the right order, with the added characters '-' and '4' and you can make your own UUID4 method. – rossum Dec 16 '16 at 20:55

9 Answers9

51

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='\x12\x34\x56\x78'*4)
    UUID(bytes_le='\x78\x56\x34\x12\x34\x12\x78\x56' +
                  '\x12\x34\x56\x78\x12\x34\x56\x78')
    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'.
Alex Hall
  • 34,833
  • 5
  • 57
  • 89
  • 6
    This works, but doesn't (necessarily) return a UUID4 ([see also](http://stackoverflow.com/a/20342413/1016216)). – L3viathan Dec 16 '16 at 14:51
  • @L3viathan I don't see why not, can you explain? – Alex Hall Dec 16 '16 at 15:00
  • 8
    UUID4 doesn't mean it's completely random, a few bits are fixed. See [the section in the Wikipedia article](https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_.28random.29). I'm not saying this necessarily matters for OP, but I think it should be clarified, since they did mention UUID4. – L3viathan Dec 16 '16 at 15:02
  • @L3viathan how could one modify the UUID output to make it a valid (deterministic) UUID4? – pir Oct 29 '20 at 21:10
  • 7
    @pir Just give it the `version` parameter: `uuid.UUID(int=rd.getrandbits(128), version=4)` – L3viathan Oct 30 '20 at 07:16
29

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
citynorman
  • 4,918
  • 3
  • 38
  • 39
  • 1
    TypeError: Calling `.seed()` on instances is deprecated. Use the class method `Faker.seed()` instead. – gurel_kaynak Feb 22 '22 at 14:21
  • Worth clarifying that `Faker.uuid4` is using `uuid.UUID(int=rd.getrandbits(128), version=4)`. Link here: https://github.com/pytest-dev/pytest-randomly/blob/main/src/pytest_randomly/__init__.py. – skeller88 May 09 '23 at 05:28
21

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())
11

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

h345k34cr
  • 3,310
  • 5
  • 21
  • 28
  • Silly python naming/scoping. But most probably you don't want to overwrite imported uuid with uuid variable. – Scheintod Apr 20 '22 at 20:30
6

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)
5

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.

Andrea Baldino
  • 371
  • 3
  • 6
4

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)
Perennial
  • 438
  • 3
  • 8
0

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

auxeon
  • 15
  • 3
0

As shown in other answers, a UUID can be generated directly from a seed. However, there is a non-negligible possibility that the resulting UUID won't be unique if the seed is not unique (≈ cryptographically secure) since other applications could generate an UUID from the same seed.

I would suggest to combine the seed with a secret. This can be achieved with a HMAC. Here is a practical example:

import hmac
from hashlib import sha256
from uuid import UUID

secret = b'my-unique-secret'
seed = b'whatever'
h = hmac.new(secret, msg=seed, digestmod=sha256)
# digest should be truncated to 128 bits = 16 bytes
UUID(bytes=h.digest()[:16], version=4)
DurandA
  • 1,095
  • 1
  • 17
  • 35