Skip to content

feat(python): add modelSerializationMethods option - #147

Open
2ynn wants to merge 1 commit into
qynn/feat/python-preserve-unknown-fieldsfrom
qynn/feat/python-model-serialization-methods
Open

2ynn wants to merge 1 commit into
qynn/feat/python-preserve-unknown-fieldsfrom
qynn/feat/python-model-serialization-methods

Conversation

@2ynn

@2ynn 2ynn commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Why

Python SDK models have no short way to turn a model into a dict or JSON string with the field names the API uses. Users have to call model_dump(by_alias=True, exclude_unset=True, ...) themselves.

What changed

Add python.modelSerializationMethods (default false). When enabled, every generated model gains:

  • to_dict(*, mode="python", use_api_names=True, exclude_unset=True, exclude_defaults=False, exclude_none=False, warnings=True)
  • to_json(*, indent=2, use_api_names=True, exclude_unset=True, exclude_defaults=False, exclude_none=False, warnings=True)

Supporting changes:

  • Model serializers emit Python field names when use_api_names=False. With the option on, model_dump(by_alias=False) also emits Python field names; the default model_dump() keeps emitting API names.
  • to_dict and to_json leave filtered fields out even with exclude_none=True. Other dumps keep the exclude_none exception from fix(python): honour include/exclude filters in model dumps #149, which request bodies rely on.
  • Const fields are marked as set in model_post_init, so exclude_unset keeps them. Error-data models (<Name>Data) get the same model_post_init.
  • The unknown variant of an open union dumps as its raw payload in to_dict and to_json, so the helpers return the data the server sent instead of the SDK's fallback object. Parsing that output gives the same unknown variant only when unknown union variants are allowed, as for responses; a plain model_validate rejects the unrecognized discriminator value. use_api_names and the exclude_* options do not apply inside the raw payload, which has no schema. model_dump and request bodies are unchanged.
  • A model field named to_dict or to_json gets a trailing underscore. SDK group accessors, global-parameter and server-variable constructor arguments, and flattened method parameters keep their names, because only models reserve these methods. Security constructor arguments follow the security model's field names, so they get the underscore too.
  • Method parameter documentation (SDK docs, Markdown parameter tables and docstrings) names parameters with the same sanitizer as the method signature. Previously a flattened toJson parameter was documented as to_json_ while the method accepts to_json. This also applies with the option disabled, for parameter names that are reserved differently for parameters and model fields: the review SDK's flattened type parameter was documented as type while the method accepts type_, and is now documented as type_.
  • Usage snippets name global parameters the same way the SDK constructor does. This also applies with the option disabled.
  • With preserveUnknownFields also enabled, both helpers keep unknown fields, subject to the limitations listed in feat(python): add preserveUnknownFields option #146.

Test coverage

  • The unknown-fields fragment gains a group named toJson with a toJson query parameter, an object with toDict/toJson properties, a const object with a defaulted field, and a required const in the error body. Its discriminated Choice union becomes a component, used by choice and by a new choices array.
  • tertiary (option enabled alone): helper defaults and options, field names, excluded fields, consts (including on error data), reserved names, the default model_dump() of nested models and model lists, and unknown open-union variants at the top level, nested in a model and inside a list.
  • quaternary (enabled with preserveUnknownFields, flattened parameters): helper round trips that keep unknown fields, unknown open-union variants nested in a model and inside a list alongside unknown fields, a flattened to_json parameter whose signature, docstring and Markdown table agree, plus checks that unrecognized enums serialize without warnings.
  • secondary (option disabled): the helper names are not reserved.

Stack

  1. fix(python): serialize unrecognized open enum values #145: unrecognized enum serialization
  2. fix(python): honour include/exclude filters in model dumps #149: honour include/exclude filters in model dumps
  3. feat(python): add preserveUnknownFields option #146: preserveUnknownFields
  4. This PR: modelSerializationMethods

Testing

  • TARGET=primary make test-pythonv2 (option disabled): 724 passed.
  • TARGET=secondary make test-pythonv2 (option disabled): 333 passed, 1 skipped.
  • TARGET=tertiary make test-pythonv2: 257 passed, 1 skipped. The nested model_dump() test fails without the serializer fix, and the error-data const test fails without model_post_init on error data.
  • TARGET=quaternary make test-pythonv2: 296 passed, 1 skipped.
  • TARGET=review make test-pythonv2: 36 passed, 6 skipped.
  • TARGET=primary make test-typescriptv2 (fragment change): 802 passed.
  • make check-template-pythonv2 and prettier pass.
  • TARGET=review make build-pythonv2: integrity validation passes; the review SDK gains modelSerializationMethods: false in gen.yaml, and the flattened type parameter of two methods is now documented as type_ in their docstrings and Markdown tables, matching the signature.
  • git diff --check passes.

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 Oct 23, 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.

All reported issues were addressed across 17 files

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

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

Comment thread templates/templates/pythonv2/includes/sanitization.ts
Comment thread templates/templates/pythonv2/auxiliary/{{getSourcePath}}/types/basemodel.py.stmpl Outdated
Comment thread templates/templates/pythonv2/auxiliary/{{getSourcePath}}/types/basemodel.py.stmpl Outdated
@2ynn
2ynn force-pushed the qynn/feat/python-model-serialization-methods branch from 067fd08 to f3586d1 Compare October 23, 2026 15:55
@2ynn
2ynn force-pushed the qynn/feat/python-preserve-unknown-fields branch from a47683e to 189b725 Compare October 23, 2026 15:55
@speakeasy-generator-bot

speakeasy-generator-bot Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Generator Snapshot Testing

Selector(s) Result Summary
snapshot-python ✅ Snapshot run completed

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

Snapshot results are summarized above. Additional run context is linked for maintainers with access.

@2ynn
2ynn marked this pull request as ready for review October 23, 2026 16:08
@2ynn
2ynn requested a review from a team as a code owner October 23, 2026 16:08
@2ynn
2ynn requested review from AshGodfrey and removed request for a team October 23, 2026 16:08
@2ynn
2ynn force-pushed the qynn/feat/python-preserve-unknown-fields branch from 189b725 to 56a8cb5 Compare October 23, 2026 17:19
@2ynn
2ynn force-pushed the qynn/feat/python-model-serialization-methods branch from f3586d1 to cf1a6c5 Compare October 23, 2026 17:19
@2ynn
2ynn force-pushed the qynn/feat/python-preserve-unknown-fields branch from 56a8cb5 to 876df5f Compare October 23, 2026 18:05
@2ynn
2ynn force-pushed the qynn/feat/python-model-serialization-methods branch from cf1a6c5 to f19c32e Compare October 23, 2026 18:05
Add python.modelSerializationMethods (default false). When enabled,
generated models gain to_dict() and to_json(), which dump with API field
names and exclude unset fields by default, and expose Pydantic's
exclude_defaults, exclude_none and warnings options plus use_api_names.

- model serializers emit Python field names when use_api_names=False,
  and the helpers leave filtered fields out even with exclude_none
- consts are marked as set so exclude_unset keeps them, in models and
  error data models
- the unknown variant of an open union dumps as its raw payload in
  to_dict() and to_json(); use_api_names and the exclude options do not
  apply inside the raw payload, which has no schema
- model fields named to_dict or to_json gain a trailing underscore;
  SDK group accessors, global and server-variable constructor arguments
  and method parameters keep their names
- method parameter docs and docstrings use the same sanitizer as the
  method signature
- usage snippets name global parameters like the SDK constructor does

The tertiary variant covers the option alone and the quaternary
variant covers it together with preserveUnknownFields.
@2ynn
2ynn force-pushed the qynn/feat/python-model-serialization-methods branch from f19c32e to 96a37ed Compare October 24, 2026 00:17

@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 24 files

Confidence score: 5/5

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

Shadow auto-approve: would require human review. Adds optional modelSerializationMethods (to_dict/to_json) with serializer and naming/test changes. Dismissed default-model_dump-alias finding unresolved: serializer keys off by_alias; claim that default model_dump keeps API names needs human verification.

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

@TristanSpeakEasy TristanSpeakEasy left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good, nice to have the helpers preserve the raw payload for unknown union variants without changing request serialization.

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.

2 participants