fix(codegen): emit single-value Literals for discriminator fields#10
Merged
Conversation
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
datamodel-codegenwas emitting single-value enums asStrEnumclasses (kind: KindwhereKind(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.--enum-field-as-literal oneand--use-one-literal-as-defaulttoscripts/typegen.shso single-value enums collapse tokind: Literal["..."] = "...". This both satisfies the discriminator requirement and removes redundant ceremony for callers._generated.py(-310/+78 lines: noisy single-value enum classes collapse into inline literals).Context
This is the SDK-side fix paired with devhelmhq/mono#283, which introduces a sealed
AuditMetadatahierarchy on the API side that currently has only one variant (MemberRoleChangedMetadata). Without this fix, the spec-evolution harness on the mono PR fails immediately onimport devhelm._generated.The mono PR's harness clones surface repos by branch-name match (
PR_BRANCH→chore/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 issuesuv run ruff check .→ cleanPydanticUserErrorduring regen)Made with Cursor