fix(codegen/python): emit nullable Option<T> fields as optional in flattened tagged-enum models#170
Merged
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
📖 Documentation Preview: https://reflectapi-docs-preview-pr-170.partly.workers.dev Updated automatically from commit c02e9fa |
…attened tagged-enum models The per-variant model paths for structs with a flattened internally-tagged enum (and the standard flatten path) hand-rolled field optionality from `field.required` alone, ignoring the Option<T> special case that resolve_field_optionality already implements. As a result a nullable field whose key serde omits when None was emitted as a required key (`amount: str | None`, no default), so validating a valid payload raised `pydantic.ValidationError: Field required`. Fields that were not required got a doubled union (`str | None | None`). Route all five field-emission sites through resolve_field_optionality so flattened-enum variant models match standalone struct output. Also add python codegen snapshots to assert_builder_snapshot! so the namespace test samples cover python like the other languages, and add repro samples for both the standalone and flattened internally-tagged enum shapes. Fixes #167
d642e67 to
5876dff
Compare
This was referenced Jun 11, 2026
Merged
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.
Fixes #167
Root cause
The bug is one step removed from the issue's minimal repro: a standalone internally-tagged enum already generates correctly, because that render path routes field optionality through
resolve_field_optionality(which treats anyOption<T>field as an optional Python key, since serde omits the key forNone).The failing shape is a struct with a
#[serde(flatten)]-ed internally-tagged enum — e.g.OffersSearchOfferflattening an offer-details enum, producing per-variant models likeOffersSearchOfferProvisional. Those models are generated byrender_struct_with_flattened_internal_enum/render_struct_with_flatten_standard, which hand-rolled optionality fromfield.requiredalone and ignored theOption<T>special case. Two symptoms:Option<T>withoutskip_serializing_if→amount: str | Nonerequired, no default →pydantic.ValidationError: Field requiredon a valid payload that omits the keyOption<T>withskip_serializing_if→ doubled unionnote: str | None | None = None@MattGson note: your minimal repro only fails when the enum is flattened into a parent struct — worth sanity-checking that matches the offers code shape.
Fix
Route all five inline field-emission sites in
reflectapi/src/codegen/python.rsthroughresolve_field_optionality:render_struct_with_flattened_internal_enumrender_struct_with_flatten_standard| Nonebug forOption<Enum>)Field optionality is now decided in exactly one place, so flattened-enum variant models match standalone struct output.
Tests
assert_snapshot!, covering schema JSON, TypeScript, Rust, OpenAPI, and Python.assert_input_snapshot!/assert_output_snapshot!/assert_snapshot!; the one gap wasassert_builder_snapshot!(the 4 namespace samples), which now snapshots python too (appended last so existing snapshot indices don't churn).generic_flatten_*python snapshots updated — each is the same one-line correction (if_else: X | Nonegaining= None); those committed snapshots had been silently capturing the bug.reflectapi-python-runtime:model_validate({"type": "variant", ...})with omitted optional keys now succeeds on variant models directly and through the discriminated-union root, and populated keys still parse.