Skip to content

fix: convert Python untagged union members - #173

Merged
adamcavendish merged 1 commit into
mainfrom
adamcavendish/python-union-conversion
Nov 11, 2026
Merged

adamcavendish merged 1 commit into
mainfrom
adamcavendish/python-union-conversion

Conversation

@adamcavendish

Copy link
Copy Markdown
Collaborator

Summary

Convert Python untagged-union members consistently in both httpx and requests
clients, preserving their existing dataclass and API names.

Problem

The generated union was only a type alias. Its containing models and HTTP methods
treated members as identity values: typed requests failed with Object of type ClientTool is not JSON serializable, while decoded responses retained dictionaries
instead of the declared dataclasses. Finite enum constraints also need to select
the actual member rather than a different object with the same required keys.

Behavior

Policy Selection Unmatched value
firstMatch First concrete match in schema order Preserve the original value
strict Reject multiple original concrete matches Use an opaque fallback or error
raw Keep union subtrees dynamic and unconverted Preserve the original value

firstMatch is the default. Concrete candidates precede opaque fallbacks. Matching
retains finite values, Python boolean/number distinctions, nullable boundaries,
and productive recursion. Delegation occurs after selection, so nested strict
errors are not swallowed by an outer alternative or nullable wrapper.

The shared Python path covers model fields, aliases, nested arrays/maps, JSON and
multipart payloads, success responses, tagged payload delegates, and lazy cached
error bodies. Required union bodies containing None are sent as JSON null.
Both transports have native consumers and strict public-declaration contracts.

Compatibility

Existing constructors, method names, and public union aliases remain available.
Named union modules add *_from_dict / *_to_dict helpers without adding internal
helpers to package barrels. raw deliberately changes a union subtree's public
type to object; callers supply JSON-compatible values themselves. Optional
request bodies keep the existing convention that None means omit.

This PR is Python-only. Other languages' emitters and output remain unchanged.
Dictionary/additional-property preservation and multi-member allOf modeling
remain separate repairs; these matching policies are not full schema validation.

@codecov

codecov Bot commented Nov 11, 2026 •

Copy link
Copy Markdown

- Share model emission and enum-aware union conversion across httpx and requests
  so typed members serialize to JSON and inbound values become dataclasses.
- Expose firstMatch, strict, and raw policies while retaining concrete-member
  priority, original delegate boundaries, finite null facts, and recursive guards.
- Match outbound tagged members against their public payload representation,
  count original matches before conversion, and load model dependencies lazily.
- Apply the same conversion to nested containers, aliases, JSON and multipart
  requests, response metadata, tagged payloads, and cached lazy error bodies.
- Preserve public model and API names, keep raw subtrees dynamically typed, and
  send required nullable union bodies as JSON null rather than omitting them.
- Share multipart/error emission and recursive annotation planning across both
  transports without changing the output of unmigrated generators.
- Retain exact render and golden contracts plus native transport and declaration
  consumers, including acyclic imports and typed tagged-member round trips.
- Make each backend's golden-build entrypoint own golden, declaration and native
  contracts with one active-interpreter environment and a 3.12 static target.
@adamcavendish
adamcavendish force-pushed the adamcavendish/python-union-conversion branch from 35b9cdc to d3cb7fd Compare November 11, 2026 06:32
@adamcavendish
adamcavendish merged commit 6216ec4 into main Nov 11, 2026
31 checks passed
@adamcavendish
adamcavendish deleted the adamcavendish/python-union-conversion branch November 11, 2026 06:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant