fix: ignore an empty object default instead of dropping the schema - #1449
alexander-wenzel-dev wants to merge 1 commit into
Conversation
Adds a [tool.uv.sources] override resolving openapi-python-client from alexander-wenzel-dev/openapi-python-client at branch pin/mealie-mcp-combined. The branch carries two unmerged generator fixes (upstream PRs openapi-generators/openapi-python-client#1448 and openapi-generators/openapi-python-client#1449) that unblock the Recipe-Input / Recipe-Output models and the PUT/PATCH recipe endpoints in the generated client. This commit only changes resolution; the generated client tree is untouched and gets regenerated in a separate PR. Removal plan is inline in pyproject.toml.
de81a94 to
a7d33d6
Compare
a7d33d6 to
fb00a27
Compare
A property whose type is a model schema could not carry `default: {}`.
`ModelProperty.convert_value` rejected it with `ModelProperty cannot have a
default value`, or with `Value {} is not valid, only None is allowed` when the
model sat in a union. That error propagated as a warning, removed the enclosing
schema and every endpoint that referenced it, and still exited 0.
An empty object carries no information for a model default, so it is now
ignored and the property generates with no default. This is how an inline
`default: {}` on a property and a bare `$ref` with a sibling `default` already
behave. A non-empty default is still rejected.
Tests: a functional test in `end_to_end_tests/functional_tests` covers the
empty object default on a union member and on a model referenced through an
`allOf`. Unit tests cover `ModelProperty.convert_value`.
fb00a27 to
a7a9c86
Compare
{} on freeform object schemas|
Rebased onto main at v0.29.1, and the approach is reduced. The earlier version of this PR accepted This version does less. An empty object carries no information for a model default, so It also lines up with what the generator already does elsewhere. An inline If you would rather see the default honoured, that needs On Python 3.14 with the locked ruff, |
fix: support default
{}on freeform object schemasSummary
A freeform object schema (
type: object,additionalProperties: true,no declared properties) inside an
anyOfcould not carry adefault: {}. The parser rejected the default, which silently droppedthe enclosing schema and every endpoint that referenced it. This PR
accepts the empty-dict default and generates a working empty-container
initializer.
Problem
A schema declared as
type: objectwithadditionalProperties: trueand no declared properties (often appearing inside an
anyOfwithnull) could not carry adefault: {}.ModelProperty.convert_valuerejected the default with
ModelProperty cannot have a default value,which propagated up as a warning and silently dropped the enclosing
schema and every endpoint that referenced it.
Minimal reproduction
Before this PR:
ModelWithFreeformDefaultis missing from thegenerated client; the only signal is a warning on stderr.
Fix
Three coordinated changes in
model_property.pyandunion.py:ModelProperty.convert_valueaccepts{}on a freeform modeland emits a
ClassName()constructor call. Whenrequired_propertieshas not yet been populated, the check fallsback to the raw schema (
data.properties,data.allOf,data.anyOf,data.oneOf) so a referenced schema still gets thecorrect decision before its properties have been processed.
Import promotion in
ModelProperty. A default value forces thegenerated client to import the inner model at runtime rather than
only under
TYPE_CHECKING, otherwise the default initializer wouldNameErrorat class-definition time.get_importsandget_lazy_importsare updated so inner-property imports arepromoted when the property has a non-
Nonedefault.Import promotion in
UnionProperty. The same promotion appliesto unions that carry a default — every inner property's lazy
imports become runtime imports so the union's default expression
resolves at class-definition time.
Tests
end_to_end_tests/functional_tests/generated_code_execution/test_freeform_object_defaults.py— uses the inline spec above, asserts the model is instantiable
and that the default
extrasis the empty container.tests/test_parser/test_properties/test_model_property.py:test_get_imports_with_defaultandtest_get_lazy_imports_with_defaultcover the import-promotion branches.
test_convert_valueandtest_convert_value_unprocessedcoverconvert_value's new accept/reject decisions, including thepre-processing fallback path.
Local verification
All green on Python 3.14. 100% coverage on both changed files
(
model_property.py,union.py) when measured across the fullupstream suite. Golden-record snapshot tests pass without
regeneration — the import-promotion paths are only reachable when a
model has a non-
Nonedefault, which previously errored, so noexisting snapshot can depend on the old behaviour.
Risk
ModelProperty.convert_valuechanges from a@classmethodto aninstance method. A grep across
openapi_python_client/,tests/,and
end_to_end_tests/finds zeroModelProperty.convert_value(...)class-level call sites; every caller uses
prop.convert_value(...)via the
PropertyProtocol. The signature change is invisible toexisting call sites.
{}is accepted, and only ona model that is genuinely freeform (no
properties,allOf,anyOf, oroneOf). Any other default value still returnsPropertyErrorwith the original message, so models with requiredfields can't slip through with an empty default.
pdm regen.Related
Companion PR: #1448 — a second independent fix for the
title-collision case surfaced from the same downstream spec. The two
PRs share no code paths and can land in either order.