[python] Add DPG TypedDict generation opt-out - #11372
[python] Add DPG TypedDict generation opt-out#11372l0lawrence with Copilot wants to merge 11 commits into
Conversation
|
Azure Pipelines: Successfully started running 1 pipeline(s). 1 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
Co-authored-by: l0lawrence <100643745+l0lawrence@users.noreply.github.com>
|
@copilot if we dont generate the typeddict overload we should generate the JSON overload |
Co-authored-by: l0lawrence <100643745+l0lawrence@users.noreply.github.com>
commit: |
|
All changed packages have been documented.
Show changes
|
Python emitter diffBaseline No changes to generated output. Rendered diff: inline on the run summary, or the emitter-diff-html artifact. Informational check (eng/emitter-diff); does not block the PR. |
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 753f3788-98a9-4e39-8655-9c6a3ce90536
…alse When generate-typeddict: false, the opt-out now performs a true revert to pre-TypedDict behavior by restoring the standalone raw-JSON dict @overload (and JSON in the impl Union) for both the explicit/model body path and the spread path, instead of only dropping the TypedDict overload. - add_overloads_for_body_param: only skip the single-body JSON overload when a TypedDict overload was actually inserted; otherwise keep it. - add_body_param_type: insert the raw-JSON (any-object) overload via new _insert_json_overload helper when TypedDict generation is disabled. - Update test_typeddict_overloads.py opt-out tests to assert the restored [model, JSON, binary] / flattened+JSON+binary overload sets. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 753f3788-98a9-4e39-8655-9c6a3ce90536
There was a problem hiding this comment.
Pull request overview
This PR adds a new @typespec/http-client-python emitter option (generate-typeddict) to let users opt out of generating TypedDict request-body overloads while keeping models-mode: dpg, preserving default behavior (true) for back-compat.
Changes:
- Added
generate-typeddictoption surfaced via the emitter’s TypeScript options schema and the Python generator’sOptionsDictdefaults. - Updated the Python preprocess plugin to conditionally insert TypedDict vs raw-JSON overload types based on
generate-typeddict. - Added unit tests and updated docs/reference material to cover and describe the new option.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| website/src/content/docs/docs/emitters/clients/http-client-python/reference/emitter.md | Documents the new generate-typeddict option in the website emitter reference. |
| packages/http-client-python/tests/unit/test_typeddict_overloads.py | Adds regression tests for disabling TypedDict overload generation in DPG mode. |
| packages/http-client-python/tests/unit/test_options_dict.py | Adds unit coverage for the option defaulting to true and being settable to false. |
| packages/http-client-python/README.md | Documents the new generate-typeddict option in the package README. |
| packages/http-client-python/generator/pygen/preprocess/init.py | Implements the opt-out behavior and adjusts overload generation logic. |
| packages/http-client-python/generator/pygen/init.py | Adds the option default (generate-typeddict: True) to OptionsDict. |
| packages/http-client-python/emitter/src/lib.ts | Adds the option to the TypeScript emitter options interface and JSON schema. |
| .chronus/changes/python-disable-typeddict-generation-2026-7-24-12-59-0.md | Adds a Chronus changelog entry for the new feature. |
| def test_spread_body_opt_out_keeps_json_overload(): | ||
| """Spread bodies revert to pre-TypedDict: flattened + single-body JSON + binary.""" | ||
| plugin = _plugin("dpg", **{"generate-typeddict": False}) | ||
| spread_body = _json_spread_body_parameter("CreateRequest", "Contoso.Widget") | ||
| yaml_data = { | ||
| "name": "create", | ||
| "bodyParameter": spread_body, | ||
| "parameters": [_content_type_param()], | ||
| "overloads": [], | ||
| "responses": [], | ||
| "exceptions": [], | ||
| } | ||
| code_model = {"types": [spread_body["type"]]} | ||
|
|
||
| plugin.add_body_param_type(code_model, spread_body) | ||
| add_overloads_for_body_param(yaml_data) | ||
|
|
||
| assert spread_body["type"]["type"] == "combined" | ||
| assert spread_body["type"]["types"][0]["base"] == "json" | ||
| assert spread_body["type"]["types"][1] == {"type": "binary"} | ||
| assert len(yaml_data["overloads"]) == 3 | ||
| json_overloads = [o for o in yaml_data["overloads"] if o["bodyParameter"]["type"].get("base") == "json"] | ||
| # Both a flattened (keyword params) overload AND a single-body raw-JSON overload. | ||
| assert any(o["bodyParameter"].get("flattened") for o in json_overloads) | ||
| assert any(not o["bodyParameter"].get("flattened") for o in json_overloads) | ||
| assert not any(t for t in code_model["types"] if t.get("base") == "typeddict") |
…bodies The skip condition sniffed for a combined-type member with base == typeddict, but in models-mode: typeddict a spread body inserts the original base: dpg model as its overload (it renders as a TypedDict via models-mode). That made the sniff false, so the single-body JSON overload was wrongly kept, regressing the prior behavior where the TypedDict overload replaced it. Track this explicitly with a jsonOverloadReplacedByTypeddict flag set by add_body_param_type wherever a TypedDict-style overload is inserted (both the generate-typeddict dpg path via _insert_typeddict_overload and the typeddict-only spread branch). add_overloads_for_body_param now checks the flag instead of sniffing base == typeddict, so: - models-mode: dpg (generate-typeddict on) and models-mode: typeddict both skip the single-body JSON overload, and - only the generate-typeddict: false opt-out keeps it (pre-TypedDict behavior). Addresses the PR reviewer comment. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 753f3788-98a9-4e39-8655-9c6a3ce90536
…e_body_json param Refactor the single-body JSON overload skip decision from a persisted `jsonOverloadReplacedByTypeddict` YAML side-channel flag to an explicit function parameter. `add_body_param_type` now returns whether a TypedDict-style overload was inserted in place of the single-body raw-JSON overload, and the single caller passes that to `add_overloads_for_body_param` as `skip_single_body_json`. This removes the implicit cross-function coupling and gives one source-of-truth decision point. Pure refactor: generated output is byte-identical across all configs (dpg+generate-typeddict on/off, models-mode: typeddict) - verified by regenerating parameters/body-optionality both ways and diffing all files. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 753f3788-98a9-4e39-8655-9c6a3ce90536
|
You can try these changes here
|
DPG mode in the Python emitter always generated TypedDict request-body overloads, with no
tspconfigescape hatch. This adds an explicit emitter option so callers can keepmodels-mode: dpgwhile disabling automatic TypedDict generation.New emitter option
generate-typeddictto@typespec/http-client-pythontrueto preserve current behaviortspconfig.yamlto opt out of DPG TypedDict auto-generationGenerator behavior
generate-typeddictfalseTests
generate-typeddict: falsesuppresses TypedDict copy generation in DPG modeDocs
Example: