Skip to content

UIDs

Client-side 11-char DHIS2 UID generator + validator. Same algorithm as org.hisp.dhis.common.CodeGenerator.java (leading letter + 10 alphanumeric chars, secrets-driven entropy), so values minted locally are accepted by every DHIS2 write endpoint without a server-side rename. Avoids a /api/system/id round-trip.

When to reach for it

  • Pre-generating UIDs for parent + child objects before the parent is created (the programTrackedEntityAttributes join table is one example — DHIS2 imports it as part of the parent's PUT body, so the child UID has to exist client-side first).
  • Building a bulk metadata bundle where every object's id is known up front (save_bulk / import_bundle flows).
  • Snapshotting a future-state UID into a fixture file for round-trip tests.

The two read-side helpers (generate_uids(count) for bulk, is_valid_uid(s) for guarding wire data) complete the surface.

Worked example

from dhis2w_client import generate_uid, generate_uids, is_valid_uid

# Single fresh UID, perfect for one-off metadata creates.
new_id = generate_uid()
assert is_valid_uid(new_id)
print(new_id)  # e.g. 'aB3xY7zK1pq' — 11 chars, starts with a letter

# 100 distinct UIDs for a bulk import.
ids = generate_uids(100)
assert len(set(ids)) == 100

# Guard wire data before treating a string as a UID.
candidate = "user-friendly-name"
if not is_valid_uid(candidate):
    print(f"{candidate!r} is not a valid DHIS2 UID — needs the 11-char shape")

Pre-generating linked UIDs

The typical use is "I want to PUT a parent that references children that don't exist yet":

from dhis2w_client import DataElement, generate_uid
from dhis2w_core.client_context import open_client
from dhis2w_core.profile import profile_from_env

new_de_id = generate_uid()
new_dataset_id = generate_uid()

async with open_client(profile_from_env()) as client:
    # Create the DE with the pre-generated ID...
    await client.data_elements.create(
        uid=new_de_id, name="My DE", short_name="MyDE", value_type="INTEGER",
    )
    # ...then create the DataSet that references it.
    await client.data_sets.create(
        uid=new_dataset_id, name="My DS", short_name="MyDS",
        period_type="Monthly", data_elements=[new_de_id],
    )

Property-based round-trip tests of generate_uid / generate_uids / is_valid_uid live in packages/dhis2w-client/tests/test_parser_properties.py.

uids

Client-side DHIS2 UID generator — mirrors dhis2w-core/CodeGenerator.java.

DHIS2 UIDs are 11-character strings over [0-9A-Za-z]. The first character is always a letter ([A-Za-z]), the remaining ten are any alphanumeric. The regex ^[A-Za-z][A-Za-z0-9]{10}$ is the canonical validation form, used across the DHIS2 codebase.

Upstream reference

dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/CodeGenerator.java

Generating client-side avoids a round-trip to /api/system/id for every new UID — useful when minting bulk payloads for /api/metadata imports. Uses secrets.choice (CSPRNG) so UIDs are unguessable — which matters for share-with-user workflows where the UID acts as a capability.

Attributes

UID_LENGTH = 11 module-attribute

Fixed DHIS2 UID length (11 characters).

UID_LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' module-attribute

First-character alphabet — 52 letters, upper + lower.

UID_ALPHABET = '0123456789' + UID_LETTERS module-attribute

Full 62-character alphanumeric alphabet — digits + upper + lower.

UID_RE = re.compile('^[A-Za-z][A-Za-z0-9]{10}$') module-attribute

Canonical DHIS2 UID regex — matches what dhis2w-core validates on write.

Functions

generate_uid()

Return one fresh DHIS2 UID.

The first character is drawn from the 52-letter alphabet; the remaining ten from the 62-character alphanumeric set. Uses secrets.choice for cryptographic randomness (matches dhis2w-core/CodeGenerator.java's security-sensitive path).

Source code in packages/dhis2w-client/src/dhis2w_client/v42/uids.py
def generate_uid() -> str:
    """Return one fresh DHIS2 UID.

    The first character is drawn from the 52-letter alphabet; the remaining
    ten from the 62-character alphanumeric set. Uses `secrets.choice` for
    cryptographic randomness (matches `dhis2w-core/CodeGenerator.java`'s
    security-sensitive path).
    """
    first = secrets.choice(UID_LETTERS)
    rest = "".join(secrets.choice(UID_ALPHABET) for _ in range(UID_LENGTH - 1))
    return first + rest

generate_uids(count)

Return count fresh DHIS2 UIDs. Collisions are statistically negligible.

Source code in packages/dhis2w-client/src/dhis2w_client/v42/uids.py
def generate_uids(count: int) -> list[str]:
    """Return `count` fresh DHIS2 UIDs. Collisions are statistically negligible."""
    if count < 0:
        raise ValueError(f"count must be >= 0, got {count}")
    return [generate_uid() for _ in range(count)]

is_valid_uid(candidate)

Return True iff candidate matches DHIS2's canonical UID format.

Source code in packages/dhis2w-client/src/dhis2w_client/v42/uids.py
def is_valid_uid(candidate: str) -> bool:
    """Return True iff `candidate` matches DHIS2's canonical UID format."""
    return bool(UID_RE.match(candidate))