Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Python Generator Configuration

Both python-httpx and python-requests accept the same options. Generated clients require Python 3.12 or newer.

[generators.python-httpx]
package_name = "my_client"
untagged_union_strategy = "firstMatch"

[generators.python-requests]
package_name = "my_client"
untagged_union_strategy = "strict"

The equivalent CLI override is:

openapi-nexus generate --input openapi.yaml --output client \
  --generators python-httpx \
  --generator-config python-httpx.untagged_union_strategy=strict

package_name

Overrides the generated import package and project name. The default is derived from the API title.

untagged_union_strategy

Controls conversion of untagged oneOf and anyOf values, not general schema validation. Both keywords use this policy; strict does not impose oneOf cardinality on every anyOf elsewhere in a specification.

ValueMember selectionNo matchPublic representation
firstMatchFirst concrete match, in schema orderPreserve the original valueExisting dataclass/primitive union
strictReject multiple concrete matchesRaise ValueErrorExisting dataclass/primitive union
rawDo not select or convert the union subtreePreserve the original valueDynamic object

The default is firstMatch. Concrete alternatives take precedence over opaque Any/empty-object fallbacks, even when the fallback appears first. Strict counts original concrete members before calling any converter; an opaque fallback does not create false ambiguity. Selected nested unions retain their own policy and errors rather than silently trying another member. Nullable wrappers permit null without inventing an extra authored member.

Matching uses required fields, present optional fields, JSON kinds, and retained finite enum/literal constraints. Booleans are not numbers despite Python’s True == 1. Arrays, dictionary values, aliases, and productive recursion retain the same conversion boundaries. Formats and numeric/string bounds do not serve as member-selection validators.

For tagged members inside an untagged union, outbound matching follows the existing public payload representation. The selected tagged converter restores its wire wrapper only after selection; strict ambiguity counting never calls a converter to probe whether a member matches.

Conversion entrypoints

Existing dataclass constructors and to_dict / from_dict methods remain public. For a named untagged union Choice, its module also provides choice_from_dict(value) and choice_to_dict(value). These helpers accept or return arbitrary JSON values, not only dictionaries; their names align with the existing Python tagged-union convention. They are not added to package barrels.

Model fields, JSON HTTP payloads, success responses, and lazy error bodies use the same conversion plan. Direct typed members can therefore be used in union fields without manual conversion before json.dumps. Required union request bodies containing None are encoded as JSON null, not an absent HTTP body. Optional request bodies retain the existing convention that None means omit.

In raw mode, supply JSON-compatible values yourself; generated dataclasses inside a raw subtree are deliberately not auto-serialized. Ordinary containing objects still use their dataclass conversion. Default unmatched values are kept as an escape hatch, so annotations describe the normal matching path rather than proving validation. Choose strict to reject unmatched inputs.

Dictionary/additional-property model preservation and multi-member allOf composition are separate concerns and are not redesigned by this option.

Additional properties

An object with explicit additionalProperties gets typed mutable additional_properties storage, including a pure dictionary with no named fields. The storage is a keyword-only dataclass field with an independent empty-dict default. from_dict separates known wire keys from extension keys; to_dict flattens the entries back into the same JSON object, without a storage wrapper.

value = MixedMap(
    known_field="fixed",
    additional_properties={"keep_key": NestedValue(wire_value="nested")},
)
value.to_dict()  # {"known_field": "fixed", "keep_key": {"wire_value": "nested"}}

Extension keys remain verbatim, even when they resemble renamed Python fields. Opaque values (true or {}) keep arbitrary JSON, including null entries and large integers. Typed values use the declared model/container/alias converters; recursive references and union policies use the same boundaries as ordinary values. The storage does not make a pure dictionary a dict subtype: use its additional_properties field and normal from_dict / to_dict entrypoints.

If an authored field already uses the Python name additional_properties, the storage name gains trailing underscores until it is distinct. Authored fields are not renamed. Supplying an extension key equal to a declared wire key raises ValueError on serialization, even when that named field is currently omitted; extensions never overwrite or impersonate named fields.

Objects without explicit additional properties and closed objects keep their existing behavior. Ordinary named-field codecs and optional-null omission are unchanged. This is not new JSON Schema validation or an allOf/tagged-wrapper representation redesign.

Value conversion follows the retained schema facts. General inline composed additional-property schemas currently lowered as opaque values remain opaque; this storage repair does not change that shared lowering behavior.