Review of 566dc7c found the inheritance path silently changing decoded data
in several shapes. Fixes, most severe first:
- A `oneOf` + `discriminator` union holder is no longer force-built as a class.
It declares no properties, so `--inheritance` produced `class Button: pass`
and every `list[Button]` payload decoded into it, dropping each variant's
fields. It stays a union alias; only bases with their own properties become
classes. Verified: pydantic again decodes `CallbackButton(payload=...)` and
`isinstance(x, ButtonBase)` still holds.
- The pinned discriminator tag is reserved against the subtype's existing field
names. A sibling property whose wire name only differs in case (`Type` vs
`type`) snake-cases to the same identifier, and the two class attributes
collapsed into one -- destroying the tag, with ruff reporting nothing.
- A subtype that restates an inherited property only to attach prose, or to
relax it to nullable, now inherits it instead of emitting an override.
`v: str | None` over the base's `v: str` is an `[assignment]` error under
`mypy --strict`, so `--inheritance --check` failed on ordinary specs. Genuine
narrowings (a `Literal` tag over a `str`) are kept.
- The base class is resolved from the schema, not from the half-built
`_declarations` registry. A base whose own body refers back to its subtype
(a recursive hierarchy) has no entry yet, so inheritance silently degraded to
a field merge based on nothing but graph traversal order.
- Keyword-only constructors are limited to the models in a hierarchy. One
`allOf` subtype used to flip every model in the package, breaking positional
construction for models with no relation to it.
- A discriminated base kept as a class emits its mapping as a comment. No
serializer resolves a subtype from a base-class annotation on its own, and
`IRModel.discriminator` was read by nobody, so that was dropped on the floor.
- `IRBodyField.description` (and `IRParameter.description`) now reach the
generated code as PEP 258 attribute docstrings. Both were carried through the
IR and rendered nowhere.
Also: `IRModel.base` -> `base_model`, so it can't be confused with
`IREnum.base` (the enum's "str"/"int" value type); soft keywords use an
allow-list over `keyword.issoftkeyword` so a future Python's new soft keyword
stays guarded; `_ordered_declarations` breaks an inheritance cycle instead of
emitting a class before its base; the two discriminator-mapping loops share one
helper; `_inherited_ref` is computed once and passed down.
Verified: both file layouts x all three serializers generate code that passes
`ruff check --isolated` and `mypy --strict` (only the pre-existing
`BaseMethod.__init_subclass__` no-untyped-call remains).
Three additions, driven by generating a hand-written client's shape:
--inheritancerendersallOf: [{$ref: Base}, ...]as a real base class instead of merging the parent's fields into every subtype. A discriminated base stays a model (its own properties survive) and its mapped subtypes inherit from it, re-declaring only the discriminator tag. Declarations are emitted parent-first so aclass Sub(Base)statement resolves, and model constructors become keyword-only because a subclass may pin an inherited field to a default while adding required fields of its own.IRBodyField.descriptioncarries the schema description of a spread request body field, which was silently dropped.IRParameteralready had it.sanitize_identifierno longer suffixes soft keywords:typeis a legal attribute name and an extremely common spec field, sotype_was noise._stays reserved.Verified on a real 130-schema spec: both file layouts x all three serializers generate code that passes
ruff --isolatedandmypy --strict.Description
Please include a summary of the change and specify which issue is being addressed. Additionally, provide relevant motivation and context.
Fixes # (issue number)
Type of change
Please delete options that are not relevant.
Checklist
uv run ruff checkanduv run ruff format --checkshow no errors)uv run mypy