Skip to content

fix: preserve Python additional properties - #178

Merged
adamcavendish merged 1 commit into
mainfrom
adamcavendish/python-additional-properties
Nov 12, 2026
Merged

adamcavendish merged 1 commit into
mainfrom
adamcavendish/python-additional-properties

Conversation

@adamcavendish

Copy link
Copy Markdown
Collaborator

Summary

Preserve explicit additional properties in Python httpx and requests clients,
including both pure dictionaries and objects with named fields.

Problem

The IR already retains dictionary value facts, but Python object emission used
only named properties. A mixed-object round-trip therefore silently discarded
every extension entry. Pure dictionaries became empty dataclasses without normal
from_dict / to_dict conversion.

Behavior

Models now expose typed, mutable, keyword-only additional_properties storage
with an independent empty-dict default. Conversion separates known wire fields
from extension entries and writes them back as one flat JSON object.

value = MixedMap(
    known_field="fixed",
    additional_properties={"keep_key": NestedValue(wire_value="nested")},
)
value.to_dict()  # {"known_field": "fixed", "keep_key": {"wire_value": "nested"}}
Situation Behavior
Pure dictionary Normal dataclass storage and conversion, not an empty model
Opaque extension values Keep JSON trees, null entries and large integers
Typed extension values Use declared model, enum, alias and container codecs
Renamed-looking keys Stay verbatim; only exact declared wire keys are separated
Storage-name collision Append underscores to storage; keep authored fields intact
Declared wire-key collision Raise ValueError, even when the named field is omitted

Aliases, recursive dictionaries, containing models and union boundaries retain
the same typed value conversions. Dictionary models participate in outbound
matching through their declared storage, without invoking codecs to count union
candidates. Existing firstMatch, strict and raw policies still govern union
values. HTTP JSON requests, successful responses and lazy typed error bodies use
the same codecs, including native decode failures and error caching.

Factory and typing helper references remain distinct from authored names such as
field, dict, additionalProperties and Any. Factory type references are
deferred so recursive model imports stay usable.

Boundaries

This covers both Python transports only. Existing named constructor parameters,
closed/implicit objects and ordinary field codecs remain intact; extension
storage is an additive keyword-only field. Existing tagged helper keyword
parameters and ordinary optional/nullable field omission remain unchanged.
Generated source and dependency manifests are consumed without compatibility
patches.

Shared IR/lowering, other-language generators, multi-member allOf composition
and tagged-wrapper extension storage are not redesigned. General inline composed
additional-property schemas that already lower as opaque values remain opaque;
that existing loss of type facts is a separate lowering repair. This change
converts the retained value facts, not a new JSON Schema validation policy.

- Generate typed mutable extension storage for explicit additional-property facts
  in both Python transports instead of dropping entries or emitting empty models.
- Expose keyword-only additional_properties fields with independent empty defaults
  and flatten their entries through normal from_dict and to_dict conversion.
- Retain verbatim extension keys, opaque nulls and large integers while converting
  declared model, enum, nullable, container, alias and recursive value types.
- Keep authored field names intact, disambiguate extension storage names and reject
  extension keys that would overwrite or impersonate declared wire fields.
- Reuse planned value codecs for dictionary fields, HTTP payloads and lazy errors,
  preserving union policies, native exceptions and cached error-body failures.
- Match dictionary models and mixed-object extension values through explicit
  dataclass metadata without serializing candidates during union selection.
- Defer typed factories across recursive imports and keep built-in factory and Any
  helper identities distinct from authored fields and models.
- Preserve existing tagged helper keyword parameters and ordinary nullable-field
  omission while keeping authored union-null semantics intact.
- Retain native transport/declaration consumers and exact render/golden contracts
  without changing generated dependencies, shared lowering or other backends.
- Keep multi-member allOf, tagged-wrapper extension storage and opaque inline
  composition facts outside this object-model preservation repair.
@codecov

codecov Bot commented Nov 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.95833% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/generators/python/additional_properties.rs 98.70% 1 Missing ⚠️
src/generators/python/value.rs 97.50% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@adamcavendish
adamcavendish merged commit 6dd9e5d into main Nov 12, 2026
31 checks passed
@adamcavendish
adamcavendish deleted the adamcavendish/python-additional-properties branch November 12, 2026 12:21
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