fix(python): type-check and send all arguments of body-variant overloads
Why
With bodyVariantOverloads, pythonv2 flattens each variant of a oneOf-of-objects request body into keyword arguments. Several calls that type-check were silently wrong at runtime:
- A trailing
**body_kwargs: Anyoverload accepted any keyword, so misspelled or unknown arguments, and typos inside nested TypedDict arguments, type-checked and were dropped. - On operations that also have path, query or header parameters, the flattened call keyed the assembled request by the body schema name instead of the request field name, and parameters passed alongside
request=were ignored. - A body property whose argument name is sanitized in the method signature (
type→type_, orretries→retries_when it clashes with a per-call option) was dropped from the request body.
What changed
- Replace the
**body_kwargs: Anyoverload with one**kwargs: Unpack[<Variant>TypedDict]overload per variant, emitted after every expanded overload. A variant gets one only when its expanded overload is narrower than its TypedDict (splitstream, discriminatorLiteral, renamed key) and no TypedDict key collides with a named method parameter. Unpack overloads after the first carry# pyright: ignore[reportOverlappingOverload]. - Add the keyword-only non-body parameters to the
request=overload, and apply path, query and header arguments that are neitherUNSETnorNoneover the values inrequest. - Key the assembled request by each parameter's pydantic alias and the body field name, so flattened calls on operations with parameters, or with
x-speakeasy-name-overrideparameters, validate and send them. - Map renamed argument names back to the model field names before unmarshalling the body.
- Raise
ValueErrorwhenrequestand body keyword arguments are both supplied; the body arguments were ignored. - Type optional nullable parameters in overloads as
OptionalNullable[T], keep nullable property types in expanded overloads, and acceptNonefor optional properties.
Known limitation: a variant whose property names collide with method parameters (retries, timeout_ms, server_url, ...) gets no Unpack overload, so spreading its TypedDict with ** does not type-check. The expanded overload (retries_=...) still works.
Fixture and test IDs
The shared fixture tests/specs/fragments/primary/body-variant-overloads.yaml and the shared test IDs come from #188. This PR:
- adds a nullable optional query parameter (
cursor) tobodyVariantOverloadsDiscriminated, to coverOptionalNullable[T]in overloads; - records the shared test IDs in pythonv2 and removes them from its skip list:
unions-body-variant-overloads-discriminatedunions-body-variant-overloads-non-discriminatedunions-body-variant-overloads-request-bodyunions-body-variant-overloads-non-body-argumentsunions-body-variant-overloads-renamed-argument
Testing
- New
tests/primary/test_body_variant_overloads.py:- runs pyright over invalid and valid call snippets;
- checks the overlap suppression placement;
- over a mock transport, checks the
request=plus body argumentsValueError, and that each variant's body,request=vs flattened bodies (includingtype_), renamed body arguments, and path/query/header arguments reach the HTTP request.
TARGET=primary make test-pythonv2: 812 passed, 0 failed, before rebasing onto #188. Not rerun since; the rebase adds the fixture operations from #188 (optional body, parameter-name clash, server URL, open-enum union).make check-template-pythonv2andmake lintpass after the rebase.- Not rerun after rebasing onto
main: pythonv2review.
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 bodyVariantOverloads calls in the pythonv2 generator that type-checked but were silently wrong at runtime: unknown or misspelled keyword arguments were accepted and dropped, path/query/header arguments passed alongside request= were ignored, flattened body fields were ignored when request= was supplied, and body fields renamed in method signatures (type_, retries_) never reached the request.
- Replaces the catch-all
**body_kwargs: Anyoverload with per-variant**kwargs: Unpack[<Variant>TypedDict]overloads, so type checkers now reject unknown keys, nested TypedDict typos, and mixed-variant fields. - Applies non-body parameter arguments over the
request=values, keys the assembled request by pydantic alias and body field name, and maps renamed argument names back to model field names before unmarshalling. - Raises
ValueErrorwhen flattened body keywords are supplied together withrequest=. - Optional arguments accept
Noneand optional nullable parameters are typed asOptionalNullable[T].
Known limitation
Variants whose property names collide with method parameters (retries, timeout_ms, server_url) get no Unpack overload, so spreading their TypedDict with ** does not type-check; the expanded overload (retries_=...) still works.