Skip to content

Commit e957fc5

Browse files
l0lawrenceCopilot
andcommitted
Fix single-body JSON overload skip for models-mode: typeddict spread 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
1 parent 025eceb commit e957fc5

2 files changed

Lines changed: 76 additions & 6 deletions

File tree

packages/http-client-python/generator/pygen/preprocess/__init__.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,12 @@ def add_overloads_for_body_param(yaml_data: dict[str, Any]) -> None:
9494
continue
9595
if body_type.get("type") == "model" and body_type.get("base") == "json":
9696
yaml_data["overloads"].append(add_overload(yaml_data, body_type, for_flatten_params=True))
97-
# When a TypedDict overload was inserted, it replaces the single-body JSON
98-
# overload, so skip it. When TypedDict generation is disabled, no TypedDict
99-
# overload exists and we keep the single-body raw-JSON overload (pre-TypedDict
100-
# behavior) by falling through.
101-
if any(t.get("base") == "typeddict" for t in body_parameter["type"]["types"]):
97+
# When a TypedDict-style overload was inserted, it replaces the single-body
98+
# JSON overload, so skip it. add_body_param_type sets this flag for both
99+
# models-mode: dpg (generate-typeddict on) and models-mode: typeddict. When
100+
# TypedDict generation is disabled the flag is absent and we keep the
101+
# single-body raw-JSON overload (pre-TypedDict behavior) by falling through.
102+
if body_parameter["type"].get("jsonOverloadReplacedByTypeddict"):
102103
continue
103104
yaml_data["overloads"].append(add_overload(yaml_data, body_type))
104105
content_type_param = next(p for p in yaml_data["parameters"] if p["wireName"].lower() == "content-type")
@@ -389,6 +390,9 @@ def _insert_typeddict_overload(
389390
existing_td: Optional[dict[str, Any]],
390391
) -> None:
391392
"""Insert a typeddict type into the body parameter's combined types."""
393+
# Mark that a TypedDict-style overload now stands in for the single-body JSON
394+
# overload, so add_overloads_for_body_param knows to skip re-adding it.
395+
body_parameter["type"]["jsonOverloadReplacedByTypeddict"] = True
392396
if origin_type == "model":
393397
td_type = existing_td or {**source, "base": "typeddict"}
394398
body_parameter["type"]["types"].insert(1, td_type)
@@ -487,7 +491,9 @@ def add_body_param_type(
487491

488492
if is_typeddict_only and original:
489493
# In typeddict-only mode, the original dpg model already renders
490-
# as a TypedDict — reference it directly, no copy needed.
494+
# as a TypedDict — reference it directly, no copy needed. It also
495+
# replaces the single-body JSON overload.
496+
body_parameter["type"]["jsonOverloadReplacedByTypeddict"] = True
491497
if origin_type == "model":
492498
body_parameter["type"]["types"].insert(1, original)
493499
else:

packages/http-client-python/tests/unit/test_typeddict_overloads.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,70 @@ def test_spread_body_opt_out_keeps_json_overload():
150150
assert not any(t for t in code_model["types"] if t.get("base") == "typeddict")
151151

152152

153+
def test_spread_body_dpg_typeddict_on_skips_single_json_overload():
154+
"""dpg + generate-typeddict on: the TypedDict overload replaces the single-body JSON one."""
155+
plugin = _plugin("dpg")
156+
clid = "Contoso.Widget"
157+
original = _dpg_body_parameter("Widget", clid)["type"]
158+
spread_body = _json_spread_body_parameter("CreateRequest", clid)
159+
yaml_data = {
160+
"name": "create",
161+
"bodyParameter": spread_body,
162+
"parameters": [_content_type_param()],
163+
"overloads": [],
164+
"responses": [],
165+
"exceptions": [],
166+
}
167+
code_model = {"types": [original, spread_body["type"]]}
168+
169+
plugin.add_body_param_type(code_model, spread_body)
170+
add_overloads_for_body_param(yaml_data)
171+
172+
# Combined types: [json_model, typeddict, binary]; the single-body JSON overload is
173+
# replaced by the TypedDict, so only the flattened JSON overload remains.
174+
assert spread_body["type"].get("jsonOverloadReplacedByTypeddict") is True
175+
json_overloads = [o for o in yaml_data["overloads"] if o["bodyParameter"]["type"].get("base") == "json"]
176+
assert all(o["bodyParameter"].get("flattened") for o in json_overloads)
177+
assert not any(not o["bodyParameter"].get("flattened") for o in json_overloads)
178+
179+
180+
def test_spread_body_typeddict_mode_skips_single_json_overload():
181+
"""models-mode: typeddict spread bodies must NOT keep the single-body JSON overload.
182+
183+
The inserted overload is the original ``base: dpg`` model (it renders as a TypedDict
184+
via models-mode), so a ``base == "typeddict"`` sniff would miss it. The flag set by
185+
``add_body_param_type`` ensures the single-body JSON overload is still skipped.
186+
"""
187+
plugin = _plugin("typeddict")
188+
clid = "Contoso.Widget"
189+
original = _dpg_body_parameter("Widget", clid)["type"]
190+
spread_body = _json_spread_body_parameter("CreateRequest", clid)
191+
yaml_data = {
192+
"name": "create",
193+
"bodyParameter": spread_body,
194+
"parameters": [_content_type_param()],
195+
"overloads": [],
196+
"responses": [],
197+
"exceptions": [],
198+
}
199+
code_model = {"types": [original, spread_body["type"]]}
200+
201+
plugin.add_body_param_type(code_model, spread_body)
202+
add_overloads_for_body_param(yaml_data)
203+
204+
assert spread_body["type"]["type"] == "combined"
205+
assert spread_body["type"].get("jsonOverloadReplacedByTypeddict") is True
206+
# typeddict-only mode omits the binary overload; the inserted overload is the
207+
# original dpg model referenced directly.
208+
assert spread_body["type"]["types"][0]["base"] == "json"
209+
assert spread_body["type"]["types"][1] is original
210+
# Exactly the flattened JSON overload plus the original-model overload; the
211+
# single-body JSON overload must be absent.
212+
json_overloads = [o for o in yaml_data["overloads"] if o["bodyParameter"]["type"].get("base") == "json"]
213+
assert len(json_overloads) == 1
214+
assert json_overloads[0]["bodyParameter"].get("flattened") is True
215+
216+
153217
def _dpg_body_parameter(name: str, cross_lang_id: str) -> dict:
154218
"""A JSON dpg-model body parameter for the given model name and cross-language id."""
155219
model_type = {

0 commit comments

Comments
 (0)