Skip to content

Commit 770ebc2

Browse files
committed
Validate all default types
1 parent 4a7f603 commit 770ebc2

File tree

4 files changed

+37
-52
lines changed

4 files changed

+37
-52
lines changed

openapi_python_client/parser/properties/enum_property.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,12 @@ def convert_value(self, value: Any) -> Value | PropertyError | None:
164164
return PropertyError(detail=f"Value {value} is not valid for enum {self.name}")
165165
return PropertyError(detail=f"Cannot convert {value} to enum {self.name} of type {self.value_type}")
166166

167+
def default_to_raw(self) -> ValueType | None:
168+
if self.default is None:
169+
return None
170+
key = self.default.split(".")[1]
171+
return self.values[key]
172+
167173
def get_base_type_string(self, *, quoted: bool = False) -> str:
168174
return self.class_info.name
169175

openapi_python_client/parser/properties/merge_properties.py

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,11 @@
99
from ..errors import PropertyError
1010
from . import FloatProperty
1111
from .any import AnyProperty
12-
from .enum_property import EnumProperty, ValueType
12+
from .enum_property import EnumProperty
1313
from .int import IntProperty
1414
from .list_property import ListProperty
1515
from .property import Property
1616
from .protocol import PropertyProtocol
17-
from .schemas import Class
1817
from .string import StringProperty
1918

2019
PropertyT = TypeVar("PropertyT", bound=PropertyProtocol)
@@ -81,31 +80,25 @@ def _merge_same_type(prop1: Property, prop2: Property) -> Property | None | Prop
8180
return _merge_common_attributes(prop1, prop2)
8281

8382

84-
def _merge_string(prop1: StringProperty, prop2: StringProperty) -> StringProperty:
83+
def _merge_string(prop1: StringProperty, prop2: StringProperty) -> StringProperty | PropertyError:
8584
return _merge_common_attributes(prop1, prop2)
8685

8786

8887
def _merge_numeric(prop1: Property, prop2: Property) -> IntProperty | None | PropertyError:
8988
"""Merge IntProperty with FloatProperty"""
9089
if isinstance(prop1, IntProperty) and isinstance(prop2, (IntProperty, FloatProperty)):
91-
result = _merge_common_attributes(prop1, prop2)
90+
return _merge_common_attributes(prop1, prop2)
9291
elif isinstance(prop2, IntProperty) and isinstance(prop1, (IntProperty, FloatProperty)):
9392
# Use the IntProperty as a base since it's more restrictive, but keep the correct override order
94-
result = _merge_common_attributes(prop2, prop1, prop2)
93+
return _merge_common_attributes(prop2, prop1, prop2)
9594
else:
9695
return None
97-
if result.default is not None:
98-
if isinstance(result.default, float) and not result.default.is_integer():
99-
return PropertyError(detail=f"default value {result.default} is not valid for an integer property")
100-
return result
10196

10297

10398
def _merge_with_enum(prop1: PropertyProtocol, prop2: PropertyProtocol) -> EnumProperty | PropertyError:
10499
if isinstance(prop1, EnumProperty) and isinstance(prop2, EnumProperty):
105100
# We want the narrowest validation rules that fit both, so use whichever values list is a
106101
# subset of the other.
107-
values: dict[str, ValueType]
108-
class_info: Class
109102
if _values_are_subset(prop1, prop2):
110103
values = prop1.values
111104
class_info = prop1.class_info
@@ -128,7 +121,7 @@ def _merge_with_enum(prop1: PropertyProtocol, prop2: PropertyProtocol) -> EnumPr
128121
)
129122

130123

131-
def _merge_common_attributes(base: PropertyT, *extend_with: PropertyProtocol) -> PropertyT:
124+
def _merge_common_attributes(base: PropertyT, *extend_with: PropertyProtocol) -> PropertyT | PropertyError:
132125
"""Create a new instance based on base, overriding basic attributes with values from extend_with, in order.
133126
134127
For "default", "description", and "example", a non-None value overrides any value from a previously
@@ -140,10 +133,16 @@ def _merge_common_attributes(base: PropertyT, *extend_with: PropertyProtocol) ->
140133
"""
141134
current = base
142135
for override in extend_with:
136+
if isinstance(override, EnumProperty):
137+
override_default = current.convert_value(override.default_to_raw())
138+
else:
139+
override_default = current.convert_value(override.default)
140+
if isinstance(override_default, PropertyError):
141+
return override_default
143142
current = evolve(
144143
current, # type: ignore # can't prove that every property type is an attrs class, but it is
145144
required=current.required or override.required,
146-
default=override.default or current.default,
145+
default=override_default or current.default,
147146
description=override.description or current.description,
148147
example=override.example or current.example,
149148
)

tests/test_parser/test_properties/test_merge_properties.py

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ def test_merge_basic_attributes_same_type(
2121
model_property_factory,
2222
):
2323
basic_props = [
24-
boolean_property_factory(default=True),
25-
int_property_factory(default=1),
26-
float_property_factory(default=1.5),
27-
string_property_factory(default="x"),
24+
boolean_property_factory(default="True"),
25+
int_property_factory(default="1"),
26+
float_property_factory(default="1.5"),
27+
string_property_factory(default=StringProperty.convert_value("x")),
2828
list_property_factory(),
2929
model_property_factory(),
3030
]
@@ -68,17 +68,17 @@ def test_incompatible_types(
6868

6969
def test_merge_int_with_float(int_property_factory, float_property_factory):
7070
int_prop = int_property_factory(description="desc1")
71-
float_prop = float_property_factory(default=2, description="desc2")
71+
float_prop = float_property_factory(default="2", description="desc2")
7272

7373
assert merge_properties(int_prop, float_prop) == (
7474
evolve(int_prop, default=float_prop.default, description=float_prop.description)
7575
)
7676
assert merge_properties(float_prop, int_prop) == (evolve(int_prop, default=float_prop.default))
7777

78-
float_prop_with_non_int_default = evolve(float_prop, default=2.5)
78+
float_prop_with_non_int_default = evolve(float_prop, default="2.5")
7979
error = merge_properties(int_prop, float_prop_with_non_int_default)
8080
assert isinstance(error, PropertyError), "Expected invalid default to error"
81-
assert "default value" in error.detail
81+
assert error.detail == "Invalid int value: 2.5"
8282

8383

8484
def test_merge_with_any(
@@ -91,17 +91,16 @@ def test_merge_with_any(
9191
):
9292
original_desc = "description"
9393
props = [
94-
boolean_property_factory(default=True, description=original_desc),
95-
int_property_factory(default=1, description=original_desc),
96-
float_property_factory(default=1.5, description=original_desc),
97-
string_property_factory(default="x", description=original_desc),
94+
boolean_property_factory(default="True", description=original_desc),
95+
int_property_factory(default="1", description=original_desc),
96+
float_property_factory(default="1.5", description=original_desc),
97+
string_property_factory(default=StringProperty.convert_value("x"), description=original_desc),
9898
model_property_factory(description=original_desc),
9999
]
100100
any_prop = any_property_factory()
101101
for prop in props:
102-
expected_result = evolve(prop, description=original_desc, default=prop.default)
103-
assert merge_properties(any_prop, prop) == expected_result
104-
assert merge_properties(prop, any_prop) == expected_result
102+
assert merge_properties(any_prop, prop) == prop
103+
assert merge_properties(prop, any_prop) == prop
105104

106105

107106
def test_merge_enums(enum_property_factory, config):
@@ -134,9 +133,9 @@ def test_merge_enums(enum_property_factory, config):
134133

135134
def test_merge_string_with_string_enum(string_property_factory, enum_property_factory):
136135
values = {"A": "A", "B": "B"}
137-
string_prop = string_property_factory(default="default1", description="desc1", example="example1")
136+
string_prop = string_property_factory(default="A", description="desc1", example="example1")
138137
enum_prop = enum_property_factory(
139-
default="default2",
138+
default="test.B",
140139
description="desc2",
141140
example="example2",
142141
values=values,
@@ -147,17 +146,17 @@ def test_merge_string_with_string_enum(string_property_factory, enum_property_fa
147146
assert merge_properties(enum_prop, string_prop) == evolve(
148147
enum_prop,
149148
required=True,
150-
default=string_prop.default,
149+
default="test.A",
151150
description=string_prop.description,
152151
example=string_prop.example,
153152
)
154153

155154

156155
def test_merge_int_with_int_enum(int_property_factory, enum_property_factory):
157156
values = {"VALUE_1": 1, "VALUE_2": 2}
158-
int_prop = int_property_factory(default=100, description="desc1", example="example1")
157+
int_prop = int_property_factory(default=1, description="desc1", example="example1")
159158
enum_prop = enum_property_factory(
160-
default=200,
159+
default="test.VALUE_1",
161160
description="desc2",
162161
example="example2",
163162
values=values,
@@ -166,7 +165,7 @@ def test_merge_int_with_int_enum(int_property_factory, enum_property_factory):
166165

167166
assert merge_properties(int_prop, enum_prop) == evolve(enum_prop, required=True)
168167
assert merge_properties(enum_prop, int_prop) == evolve(
169-
enum_prop, required=True, default=int_prop.default, description=int_prop.description, example=int_prop.example
168+
enum_prop, required=True, description=int_prop.description, example=int_prop.example
170169
)
171170

172171

tests/test_parser/test_properties/test_model_property.py

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -403,25 +403,6 @@ def test_reference_not_processed(self, model_property_factory, config):
403403

404404
assert isinstance(result, PropertyError)
405405

406-
def test_conflicting_properties_same_types(self, model_property_factory, string_property_factory, config):
407-
data = oai.Schema.model_construct(
408-
allOf=[oai.Reference.model_construct(ref="#/First"), oai.Reference.model_construct(ref="#/Second")]
409-
)
410-
schemas = Schemas(
411-
classes_by_reference={
412-
"/First": model_property_factory(
413-
required_properties=[], optional_properties=[string_property_factory(default="abc")]
414-
),
415-
"/Second": model_property_factory(
416-
required_properties=[], optional_properties=[string_property_factory(default="def")]
417-
),
418-
}
419-
)
420-
421-
result = _process_properties(data=data, schemas=schemas, class_name="", config=config, roots={"root"})
422-
423-
assert isinstance(result, PropertyError)
424-
425406
def test_allof_string_and_string_enum(
426407
self, model_property_factory, enum_property_factory, string_property_factory, config
427408
):

0 commit comments

Comments
 (0)