GitHub
08/31/2026, 3:33 PMuuid module isn't available in the stream map function namespace, and it can't simply be added, since simpleeval blocks module objects outright (FeatureNotAvailable: Sorry, modules are not allowed).
Some of this can be done today, but only through awkward workarounds:
• Validation is possible with pure string manipulation — checking length, hyphen positions, and stripping the hex/hyphen character set — but the resulting expressions are long, hard to read, and easy to get subtly wrong. A canonical-shape check ends up looking like:
__filter__: >-
len(value) == 36
and value[8] == '-' and value[13] == '-'
and value[18] == '-' and value[23] == '-'
and value.strip('0123456789abcdef-') == ''
It works, but it can't express stricter notions like "is a real v4 UUID," and the intent isn't obvious to a reader.
• Generation has no equivalent workaround — there's no way to produce a random UUID from within an expression.
First-class UUID callables would replace the string manipulation with something readable, and make generation possible at all.
Proposal
Register a small set of UUID callables in the stream map function namespace. At minimum:
• uuid4() — generate a random UUID (returned as a str), for surrogate/synthetic keys.
• UUID(value) — parse/validate a UUID string (uuid.UUID), enabling validation and canonicalization.
import uuid
functions = {
# ...existing entries...
"uuid4": lambda: str(uuid.uuid4()),
"UUID": uuid.UUID,
}
Returning uuid4() as a str avoids leaking a UUID object into the record (which downstream JSON serialization / target type handling may not expect).
Example usage
Generate a surrogate key:
stream_maps:
my_stream:
surrogate_id: uuid4()
Validate / filter to real UUIDs (pairs well with a try_cast-style helper so bad input doesn't raise):
__filter__: try_cast(value, UUID) is not None
Canonicalize an incoming UUID string (lowercased, hyphenated) via round-trip:
id: str(UUID(id))
Design considerations
• Validation is already possible, just awkwardly — the string-manipulation approach above works today, so for validation this is largely about ergonomics and correctness; generation, by contrast, has no workaround at all.
• Module can't be exposed directly — simpleeval rejects module objects, so functionality must be registered as named callables (as above).
• Return str, not UUID objects — keeps records JSON-serializable and consistent with how targets expect string keys.
• UUID() raises on bad input — on its own it can't be used safely in an expression (no `try`/`except`). It's most useful alongside an exception-handling helper; see the related request for a try_cast helper.
• Determinism — uuid4() is random, so it's non-reproducible across re-runs. If a stable/deterministic ID is desirable, a uuid5(namespace, name)-style callable could be considered as a follow-up.
---
Follows this Slack discussion - would go hand-in-hand with #3693.
🤖 Generated with Claude
meltano/sdkGitHub
08/31/2026, 3:33 PM