Skip to content

fix(codegen): emit single-value Literals for discriminator fields#10

Merged
caballeto merged 1 commit into
mainfrom
chore/end-1098-chunk1-typed-internals
Apr 24, 2026
Merged

fix(codegen): emit single-value Literals for discriminator fields#10
caballeto merged 1 commit into
mainfrom
chore/end-1098-chunk1-typed-internals

Conversation

@caballeto
Copy link
Copy Markdown
Contributor

Summary

  • datamodel-codegen was emitting single-value enums as StrEnum classes (kind: Kind where Kind(StrEnum) had one entry). Pydantic 2.12+ rejects this when the parent is a discriminated union: PydanticUserError: Model 'X' needs field 'kind' to be of type Literal.
  • Add --enum-field-as-literal one and --use-one-literal-as-default to scripts/typegen.sh so single-value enums collapse to kind: Literal["..."] = "...". This both satisfies the discriminator requirement and removes redundant ceremony for callers.
  • Regenerate _generated.py (-310/+78 lines: noisy single-value enum classes collapse into inline literals).
  • Flip one negative test to reflect the new defaulting behaviour (with a comment explaining when to flip it back).

Context

This is the SDK-side fix paired with devhelmhq/mono#283, which introduces a sealed AuditMetadata hierarchy on the API side that currently has only one variant (MemberRoleChangedMetadata). Without this fix, the spec-evolution harness on the mono PR fails immediately on import devhelm._generated.

The mono PR's harness clones surface repos by branch-name match (PR_BRANCHchore/end-1098-chunk1-typed-internals), so this branch will be paired automatically.

Why default the discriminator?

For a single-variant union there is exactly one valid discriminator value. Forcing callers to repeat it (kind="member_role_changed") is busywork — they already chose the model class. When a second variant is added to the spec, the codegen will drop the default automatically and we'll flip the negative test back. The test comment documents this.

Test plan

  • uv run pytest -q → 707 passed (was: 706 passed + 1 failed before regen)
  • uv run mypy src/ → no issues
  • uv run ruff check . → clean
  • Local re-run of the mono spec-evolution harness against this branch → 16 passed, 1 xfailed (was 27+ failures with PydanticUserError during regen)
  • CI green on this PR
  • CI green on devhelmhq/mono#283 spec-evolution job (this branch picked up via PR_BRANCH match)

Made with Cursor

Pydantic 2.12+ requires a discriminated-union member's discriminator field
to be typed as `Literal[...]`. datamodel-codegen was emitting it as a
single-value `StrEnum` (e.g. `kind: Kind` where `Kind(StrEnum)` had one
entry), which causes Pydantic to refuse to build the union with:

    PydanticUserError: Model 'X' needs field 'kind' to be of type `Literal`

This affects any sealed hierarchy on the API side that currently has only
one variant (and trips immediately when an oneOf is exposed before the
second variant lands, e.g. `AuditMetadata` introduced in mono PR #283).

Fix:
  - scripts/typegen.sh: pass `--enum-field-as-literal one` and
    `--use-one-literal-as-default` to datamodel-codegen.
  - Single-value enums now render as `kind: Literal["..."] = "..."`,
    which both satisfies the discriminator requirement and lets callers
    omit the obvious value.
  - Regenerate src/devhelm/_generated.py (net -310/+78 lines: most of
    the noisy single-value StrEnum classes collapse into inline Literals).
  - Update one negative test (`TestConfirmationPolicyNegative.test_missing_type`)
    to reflect the new defaulting behaviour and document the rollback
    when a second variant is added.

No public-API changes for callers that already passed the discriminator
explicitly. Branch name matches the mono PR so the spec-evolution harness
on devhelmhq/mono#283 picks it up automatically.

Made-with: Cursor
@caballeto caballeto merged commit ff0e4c6 into main Apr 24, 2026
4 checks passed
@caballeto caballeto deleted the chore/end-1098-chunk1-typed-internals branch April 24, 2026 17:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant