Skip to content

fix(python): resolve enum discriminator tags in dict union input - #204

Merged
2ynn merged 2 commits into
mainfrom
fix/python-enum-discriminator-dict-tag
Nov 9, 2026
Merged

2ynn merged 2 commits into
mainfrom
fix/python-enum-discriminator-dict-tag

Conversation

@2ynn

@2ynn 2ynn commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Why

With enumFormat: enum, a closed discriminated union fails validation on Python 3.11+ when it receives a dict whose discriminator holds an enum member instead of a raw string. On 3.10 the same input works:

sdk.unions.nullable_one_of_schema_post(
    request={"type": shared.TypedObject1Type.OBJ1, "value": "one"}
)
# 3.11+: union_tag_invalid
#   Input tag 'TypedObject1Type.OBJ1' found using <lambda>() does not match any of the expected tags: 'obj1', 'obj2'

Generated usage snippets build request dicts this way when enumFormat: enum, so copying them reproduces the error.

get_discriminator (utils/annotations.py) builds the discriminator from a dict with f"{field[key]}". Python 3.11 changed Enum.__format__ so that a mixed-in (str, Enum) or (int, Enum) member formats as ClassName.MEMBER. Before 3.11 it formatted as its value. The attribute branches of the same function already unwrapped .value; only the dict branch was affected.

What changed

  • get_discriminator converts discriminator values through a nested to_discriminator helper, which uses .value for enum members. The dict branch and both attribute branches use it, and the two duplicated attribute-branch blocks are gone. Behavior is unchanged on Python 3.10, where mixed-in enums already formatted as their value.
  • Forward-compatible (open) unions are not affected. parse_open_union looks the value up in a dict keyed by string, and a (str, Enum) member hashes and compares equal to its value.
  • New secondary test fragment oneof-enum-tag-nested.yaml adds oneOfEnumTagNestedPost. Its request body uses a oneOf, told apart by a single-value enum type property, once as an object property and once as array items. The oneOf is only used in a request, so it stays closed and goes through get_discriminator.
  • New pythonv2 secondary tests (enumFormat: enum) send enum-member discriminators in dicts at the top level (existing nullableOneOfSchemaPost) and nested (new operation).
  • Review SDK regenerated: only utils/annotations.py and its gen.lock checksum change.

Testing

  • New tests fail on Python 3.12 with the previous annotations.py and pass on 3.10. With the fix they pass on 3.10, 3.11, 3.12 and 3.13.
  • TARGET=secondary make test-pythonv2: 384 passed, 1 skipped (local Python 3.10). CI covers 3.10–3.14.
  • TARGET=review make test-pythonv2: passed. TARGET=primary make test-pythonv2: passed (run before the helper was renamed; no behavior change since).
  • TARGET=secondary make build-<target> for go, typescriptv2, csharp, javav2, php, ruby, cli and mcp-typescript: passed, since the new fragment applies to every target's secondary variant. Their test suites, and terraform, postman and unity, were not run locally.
  • make lint: passed.

Public-safety check

  • This change contains no credentials, customer documents, private repository URLs, private filesystem paths, or unredacted private logs.
  • Title, body, comments, and commit messages name no customers or customer-derived identifiers, private paths or trackers, or workflow provenance, and are understandable without private context (.claude/skills/public-repo-communication/SKILL.md).
  • Generated fixtures and review SDK changes are public-safe.
  • I reviewed git diff --check.

@github-actions github-actions Bot added the python Trigger (106) snapshot tests for python label Nov 9, 2026

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 7 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Shadow auto-approve: would auto-approve. Fixes Python union discriminator handling so enum members in dict inputs use their .value, correcting Python 3.11+ behavior, with secondary tests for top-level and nested cases. Focused bug fix that preserves non-enum behavior and is pinned by tests.

View guided diff | Turn on auto-fix | Re-trigger cubic

@2ynn
2ynn marked this pull request as ready for review November 9, 2026 20:37
@2ynn
2ynn requested a review from a team as a code owner November 9, 2026 20:37
@2ynn
2ynn requested a review from TristanSpeakEasy November 9, 2026 20:37
get_discriminator formatted dict tag values with an f-string. Since
Python 3.11, formatting a mixed-in (str, Enum) member yields
"ClassName.MEMBER" instead of its value, so closed discriminated unions
rejected dict input carrying an enum member as the tag with
union_tag_invalid. Unwrap enum values in the dict branch, as the
attribute branches already did, through a shared helper.
@2ynn
2ynn force-pushed the fix/python-enum-discriminator-dict-tag branch from e7dccb5 to 1bb17ce Compare November 9, 2026 20:46

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 10 reviewed files. 1 file intentionally excluded from review.

Shadow auto-approve: would not auto-approve because issues were found.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread internal/features/tests.go Resolved
@speakeasy-generator-bot

Copy link
Copy Markdown

Generator Snapshot Testing

Snapshot tests are running.

Snapshot results: https://github.com/speakeasy-api/openapi-generation-snapshots/issues/161#issuecomment-6751158594

The enum discriminator tag tests cover a pythonv2 regression, not a
cross-target feature, so they no longer register test IDs or add skip
entries to other targets.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 12 files (changes from recent commits).

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Shadow auto-approve: would auto-approve. Fixes Python union discriminator handling so enum members in dict inputs use their .value instead of Python 3.11+ Enum repr formatting, with new secondary tests covering top-level and nested cases. The behavior change is narrowly scoped, preserves non-enum behavior, and is pinned by tests.

View guided diff | Turn on auto-fix | Re-trigger cubic

@2ynn
2ynn added this pull request to the merge queue Nov 9, 2026
Merged via the queue into main with commit 319da9c Nov 9, 2026
203 of 204 checks passed
@2ynn
2ynn deleted the fix/python-enum-discriminator-dict-tag branch November 9, 2026 21:19
@github-actions github-actions Bot locked and limited conversation to collaborators Nov 9, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

python Trigger (106) snapshot tests for python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants