feat: Expose UUID generation and parsing in stream map expressions
kind/Featuregood first issueAccepting Pull Requestsvaluestream/SDKStream Maps
### Feature scope
Taps (catalog, state, tests, etc.)
### Description
## Problem
Working with UUIDs in stream map expressions can be useful in specific cases — generating surrogate keys, producing synthetic IDs, or validating that an incoming field is a real UUID. There's no first-class support for this today: the `uuid` 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:
```yaml
__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.
```python
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:
```yaml
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):
```yaml
__filter__: try_cast(value, UUID) is not None
```
Canonicalize an incoming UUID string (lowercased, hyphenated) via round-trip:
```yaml
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](https://meltano.slack.com/archives/C069CQNHDNF/p1783571869802079) - would go hand-in-hand with #3693.
🤖 Generated with [Claude](https://claude.ai)
0 条评论