Create Stable and Random UUIDs in Python
Build a reproducible UUID from a namespace and normalized name, and distinguish it from a random UUID.
Example check: Worked and guided code samples are checked against CPython 3.13. Practice uses Pyodide 0.29.5 (Python 3.13) in your browser. How we check lessons
Video lesson: Create Stable and Random UUIDs in Python
The preview is stored on this site. YouTube loads only when you press Play. Watch on YouTube
Jump to a chapter
Read the video transcript
Select a timestamp to watch that moment on YouTube.
- 00:00
Can uuid.UUID turn the plain label guest into a new identifier? Run that exact call first. The failure will separate parsing an existing UUID from generating a new random or repeatable one. The parser raises ValueError for guest because it is not formatted UUID text. Putting an arbitrary label into a parser does not create an identifier. If an API sends a UUID string, parsing can validate its shape. If an application needs to assign one, it must choose a generating function before storing the record. A human-facing label and a storage identifier have different jobs.
- 00:41
The label guest may later be renamed, translated, or shown with new capitalization; a record's stable key should not silently change with its display text. The failed parser call teaches us to decide whether an incoming value is already an ID or merely the input to an ID policy. If deriving an ID from a mutable label, keep a record of the original mapping so a rename does not orphan links and references. The diagnosis generates a version five UUID from NAMESPACE_URL and guest, prints the formatted ID, then parses it back. The printed version is five. Repeating the same namespace and name reproduces the same value; uuid4 instead
- 01:24
creates a random new identifier. Neither kind should be mistaken for an authorization secret. Parsing the generated string and reading its version verifies structure, not provenance. Many different namespace-and-name pairs produce valid version-five UUID strings. A parser cannot tell which original label was used, and a UUID is not a reversible encoding of that label. If the application must display guest later, store the label in a separate field. If it must verify a claimed mapping, recompute from its known namespace and canonical name and compare the resulting identifier.
- 02:05
Namespaces let two applications reuse the same label without accidentally sharing one identifier. A URL namespace and a DNS namespace are different inputs even for guest. The displayed version number is encoded in the UUID format, but it does not tell us whether the label was normalized sensibly. That policy is outside the library's parser and constructor. The fixed make_stable_id strips surrounding whitespace and casefolds the name before uuid5. Its prepared comparison prints True for Ada and Ada with spaces. The normalization rule belongs to this application.
- 02:45
If we later change that rule after storing IDs, the same person's derived ID could change and break lookups. Casefold has broader effects than lowercasing ASCII letters. In Python, The German word for street with a sharp s and uppercase STRASSE both casefold to strasse, which may be an intended equivalence or an unwanted name collision. Stripping a string of only spaces also produces an empty name, and uuid5 will still return an identifier for it unless the application rejects blank labels first. Decide and test those business rules before storing IDs, rather than expecting
- 03:25
the UUID library to judge whether a human name is valid. There is a deliberate collision in the application-level naming rule: spaced Ada and plain Ada collapse to one canonical string. That is desired for this exercise and potentially wrong for another directory of users. Save the canonical name or the chosen normalization version with records, so a future migration can explain how an existing stable ID was derived. The verifier prints True for two normalized spellings, then version five, then True when comparing Ada with Grace as different IDs. Each result checks one part of the contract.
- 04:06
We avoid a test demanding two uuid4 calls always differ, since a random collision is possible in principle. Stable mapping is what this function promises. A realistic test also persists the ID and looks it up after reloading the data. That catches accidental changes in namespace or normalization between runs, which a same-process equality test cannot. Store unique identifiers under a database uniqueness constraint to detect unexpected collisions or conflicting mappings. If a user changes the visible Ada label to Grace, decide whether the record keeps its existing ID or receives a new one; do not let an unplanned recomputation decide for
- 04:47
you. If two different people both choose the visible label Ada, deriving from that label alone maps both to one UUID. Use an immutable upstream key as the name or assign a new record ID with uuid4. A name-based UUID is appropriate only when the canonical name is genuinely unique within the chosen namespace. Document that uniqueness rule and test a duplicate before accepting a stable-ID scheme.
Understand the concept
A UUID is a structured identifier, not an arbitrary label. uuid.UUID parses a textual representation of an existing UUID; it cannot turn the word guest into one. Use uuid.uuid4() when you want a new random identifier. Use uuid.uuid5(namespace, name) when the same namespace and name must produce the same identifier every time. The two functions solve different problems, so decide whether replay or novelty is part of the requirement.
The broken run calls uuid4 twice and prints False because each call generates a fresh random identifier. That works for new IDs, but not for a stable mapping. A separate uuid.UUID('guest') example raises ValueError because it tries to parse arbitrary text as an existing UUID. Select the constructor whose behavior matches your use case. Never treat a UUID as a password or access token.
uuid5 uses a namespace UUID and a Unicode name to derive a reproducible result. The same pair gives the same identifier, while a different name gives another. Normalize input consistently if labels with surrounding spaces or case differences should refer to one entity. In this exercise, strip surrounding whitespace and use casefold before deriving the ID. This is an application rule, not something uuid5 does on its own.
A UUID string can be parsed back with uuid.UUID, and its version field indicates which format was generated. A stable UUID should not reveal secrets, because anyone who knows the namespace and name can derive it too. Test deterministic equality and parsing rather than assuming two random UUIDs must differ in every conceivable run. For records requiring unpredictable authorization, use a separately generated secret token with a lifecycle.
- UUID parsing expects a UUID representation
- uuid4 generates a random identifier
- uuid5 is deterministic for namespace and name
- A UUID is not an access token
See it step by step
Read the code, predict the output, then compare it with the result.
01. Parse a generated UUID
import uuid
value = uuid.uuid5(uuid.NAMESPACE_URL, 'ada')
print(uuid.UUID(str(value)).version)
5The version field confirms the parsed identifier came from the version 5 construction.
02. Derive the same ID twice
import uuid
a = uuid.uuid5(uuid.NAMESPACE_URL, 'ada')
b = uuid.uuid5(uuid.NAMESPACE_URL, 'ada')
print(a == b)
TrueThe same namespace and name yield the same UUID every time.
03. Normalize an application label
import uuid
def stable(name):
return uuid.uuid5(uuid.NAMESPACE_URL, name.strip().casefold())
print(stable(' Ada ') == stable('ADA'))
TrueThe application normalizes labels before uuid5; the library does not do that for you.
A closer look
Follow the reasoning, inspect each result, then try the suggested changes in the console below.
Choose whether the ID should repeat
A UUID is a structured identifier, not an arbitrary label. uuid.UUID parses a textual representation of an existing UUID; it cannot turn the word guest into one. Use uuid.uuid4() when you want a new random identifier. Use uuid.uuid5(namespace, name) when the same namespace and name must produce the same identifier every time. The two functions solve different problems, so decide whether replay or novelty is part of the requirement.
Generate a version 5 UUID, turn it into text, then parse the text back into an object. The version value is five. Parsing is useful when an API hands you an existing identifier, while creation is the earlier step that produced that identifier. This distinction clears up the error from the opening demo. The string is a representation, not a secret.
import uuid
value = uuid.uuid5(uuid.NAMESPACE_URL, 'ada')
print(uuid.UUID(str(value)).version)
5- Create a UUID using uuid5.
- Parse its text and inspect version.
See how namespace and name combine
The broken run calls uuid4 twice and prints False because each call generates a fresh random identifier. That works for new IDs, but not for a stable mapping. A separate uuid.UUID('guest') example raises ValueError because it tries to parse arbitrary text as an existing UUID. Select the constructor whose behavior matches your use case. Never treat a UUID as a password or access token.
The two calls use identical namespace and name inputs, so equality is guaranteed by the uuid5 algorithm. A version 4 call has a different requirement: create a fresh random identifier. Do not test uuid4 by asserting two draws can never collide; test structural properties and the interface instead. Use deterministic version 5 only when repeated mapping is actually desired.
import uuid
a = uuid.uuid5(uuid.NAMESPACE_URL, 'ada')
b = uuid.uuid5(uuid.NAMESPACE_URL, 'ada')
print(a == b)
True- Hold both namespace and name constant.
- Check equality of repeated results.
Normalize before deriving an ID
uuid5 uses a namespace UUID and a Unicode name to derive a reproducible result. The same pair gives the same identifier, while a different name gives another. Normalize input consistently if labels with surrounding spaces or case differences should refer to one entity. In this exercise, strip surrounding whitespace and use casefold before deriving the ID. This is an application rule, not something uuid5 does on its own.
The normalization function strips surrounding whitespace and applies casefold. Now Ada with spaces and ADA map to the same input name. Decide this rule before IDs become persistent, since changing it later changes derived identifiers. A namespace also needs to remain stable. If users should have opaque credentials, issue a separate token rather than relying on UUID obscurity.
import uuid
def stable(name):
return uuid.uuid5(uuid.NAMESPACE_URL, name.strip().casefold())
print(stable(' Ada ') == stable('ADA'))
True- Normalize labels before generation.
- Keep identifier rules stable after publication.
Try it in Python
Edit the example and run it. Python starts in your browser the first time you click Run.
Python console
Ready to runNeed input()? Add one value per line
Your output appears here.
Need a hint?
Normalize the name first, call uuid.uuid5 with NAMESPACE_URL, and convert the UUID object to str.
Complete the task and select Check task to verify your code.
Quick quiz
Three questions. You can change your answers and try again.
Typical mistakes
Everyone meets these errors. See what causes them and how to fix them.
Parsing a plain label
uuid.UUID('guest')uuid.uuid5(uuid.NAMESPACE_URL, 'guest')What happens: ValueError: badly formed hexadecimal UUID string
The UUID constructor parses an existing identifier; uuid5 derives one from a namespace and name.
Using uuid4 for a stable lookup
return str(uuid.uuid4())return str(uuid.uuid5(uuid.NAMESPACE_URL, normalized_name))What happens: Repeated calls do not reproduce the same mapping.
Choose uuid5 for a stable namespace/name mapping and normalize the name consistently.