Skip to content

fix(python): preserve optional nested additional properties - #150

Draft
liutmelody wants to merge 2 commits into
speakeasy-api:mainfrom
liutmelody:mel/sdk_serialization
Draft

liutmelody wants to merge 2 commits into
speakeasy-api:mainfrom
liutmelody:mel/sdk_serialization

Conversation

@liutmelody

@liutmelody liutmelody commented Oct 23, 2026 •

Copy link
Copy Markdown

Why

An optional nullable object can disappear from a serialized Python request or response when it contains a nested model that allows additional properties. The typed attribute still exists, but model_dump() and model_dump_json() omit it.

The generated Field(init=False) default shadows Pydantic's internal __pydantic_extra__ slot. Nested union serialization rejects the unexpected field, selects the Unset fallback, and removes the value.

Minimal reproduction using the generated primary fixture:

from openapi.models.shared import OptionalNestedRequest

request = OptionalNestedRequest.model_validate({
    "configuration": {
        "options": {"kind": "structured", "custom_options": True}
    }
})
print(request.model_dump_json(by_alias=True))

Before: {} with no client exception.
After: {"configuration":{"options":{"kind":"structured","custom_options":true}}}.

What changed

Generate the __pydantic_extra__ default only under if TYPE_CHECKING:. At runtime, the model has only the type annotation, so Pydantic's extra-fields slot works as intended. Type checkers still see pydantic.Field(init=False), so constructors do not require a __pydantic_extra__ argument. Add request and response regression coverage using generated models, and regenerate the two affected review models and their checksums. Additional-property values still follow their declared schema.

Testing

Manual reproduction on generated models with Pydantic 2.11.2 and 2.14.1 confirms that optional nested values, extension keys, explicit nulls, and omitted values retain their intended serialization. Reverting the generator change drops the nested values again. mypy and pyright report no errors on the regenerated review SDK or when constructing the new models. Regenerated review models also reject invalid values in a typed additional-properties map.

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.

Summary by cubic

Fixes generated Python SDK serialization dropping optional nested objects that allow additional properties, so model_dump() and model_dump_json() now preserve them.

The Field(init=False) default on __pydantic_extra__ shadowed Pydantic's internal extra-fields slot, causing nested union serialization to fall back to Unset.

  • Declares __pydantic_extra__ without a runtime default, keeping Field(init=False) only under if TYPE_CHECKING: so constructors stay valid for type checkers.
  • Adds request/response regression tests using generated models and a new OpenAPI fixture.
  • Regenerates the affected Python v2 review models and their checksums.

Written for commit 6517a51. Summary will update on new commits.

View guided diff

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

Copy link
Copy Markdown


Thank you for your submission, we really appreciate it. Like many open-source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution. You can sign the CLA by just posting a Pull Request Comment same as the below format.


I have read the CLA Document and I hereby sign the CLA


You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python Trigger (106) snapshot tests for python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant