Skip to content

Commit a7a9c86

Browse files
fix: ignore an empty object default instead of dropping the schema
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`.
1 parent 4a2f3db commit a7a9c86

4 files changed

Lines changed: 77 additions & 1 deletion

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
default: patch
3+
---
4+
5+
# Do not drop a schema over an empty object default
6+
7+
A property whose type is a model schema could not carry `default: {}`. The parser 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. The generator warned, dropped the enclosing schema and every endpoint that referenced it, and still exited 0.
8+
9+
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.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from end_to_end_tests.functional_tests.helpers import (
2+
with_generated_client_fixture,
3+
with_generated_code_imports,
4+
)
5+
6+
7+
@with_generated_client_fixture(
8+
"""
9+
components:
10+
schemas:
11+
Holder:
12+
type: object
13+
properties:
14+
extras:
15+
anyOf:
16+
- type: object
17+
additionalProperties: true
18+
- type: "null"
19+
default: {}
20+
"""
21+
)
22+
@with_generated_code_imports(".models.Holder", ".types.UNSET")
23+
class TestEmptyObjectDefaultOnAUnionMember:
24+
"""An empty object default on a union member leaves the enclosing schema in place. The property generates
25+
with no default."""
26+
27+
def test_model_generates_without_the_default(self, Holder, UNSET):
28+
assert Holder().extras is UNSET
29+
30+
def test_explicit_value_is_kept(self, Holder):
31+
assert Holder.from_dict({"extras": {"a": 1}}).to_dict() == {"extras": {"a": 1}}
32+
33+
34+
@with_generated_client_fixture(
35+
"""
36+
components:
37+
schemas:
38+
Free:
39+
type: object
40+
additionalProperties: true
41+
Holder:
42+
type: object
43+
properties:
44+
extras:
45+
allOf:
46+
- $ref: "#/components/schemas/Free"
47+
default: {}
48+
"""
49+
)
50+
@with_generated_code_imports(".models.Holder", ".types.UNSET")
51+
class TestEmptyObjectDefaultOnAReferencedModel:
52+
"""The same holds when the model carrying the default is referenced through an ``allOf``."""
53+
54+
def test_model_generates_without_the_default(self, Holder, UNSET):
55+
assert Holder().extras is UNSET

‎openapi_python_client/parser/properties/model_property.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,11 @@ def build(
128128

129129
@classmethod
130130
def convert_value(cls, value: Any) -> Value | PropertyError | None:
131+
if isinstance(value, dict) and not value:
132+
# An empty object adds nothing to the default, and rejecting it drops the whole schema.
133+
return None
131134
if value is not None:
132-
return PropertyError(detail="ModelProperty cannot have a default value") # pragma: no cover
135+
return PropertyError(detail="ModelProperty cannot have a default value")
133136
return None
134137

135138
def __attrs_post_init__(self) -> None:

‎tests/test_parser/test_properties/test_model_property.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,15 @@ def test_get_base_type_string(self, model_property_factory):
5454
m = model_property_factory()
5555
assert m.get_base_type_string() == PythonCode("MyClass")
5656

57+
def test_convert_value_passes_none_through(self, model_property_factory):
58+
assert model_property_factory().convert_value(None) is None
59+
60+
def test_convert_value_ignores_an_empty_object_default(self, model_property_factory):
61+
assert model_property_factory().convert_value({}) is None
62+
63+
def test_convert_value_rejects_a_non_empty_default(self, model_property_factory):
64+
assert isinstance(model_property_factory().convert_value({"a": 1}), PropertyError)
65+
5766

5867
class TestBuild:
5968
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)