Skip to content

Commit 4046d03

Browse files
committed
workarounds for value/Value conversion inconsistencies
1 parent 1e995b7 commit 4046d03

File tree

6 files changed

+38
-43
lines changed

6 files changed

+38
-43
lines changed

openapi_python_client/parser/properties/int.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,13 @@ def convert_value(cls, value: Any) -> Value | None | PropertyError:
6060
return value
6161
if isinstance(value, str):
6262
try:
63-
int(value)
63+
value = float(value)
6464
except ValueError:
6565
return PropertyError(f"Invalid int value: {value}")
66-
return Value(value)
66+
if isinstance(value, float):
67+
as_int = int(value)
68+
if value == as_int:
69+
value = as_int
6770
if isinstance(value, int) and not isinstance(value, bool):
6871
return Value(str(value))
6972
return PropertyError(f"Invalid int value: {value}")

openapi_python_client/parser/properties/protocol.py

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -182,12 +182,4 @@ def default_to_raw(self) -> Any | None:
182182
d = self.default
183183
if not isinstance(d, Value):
184184
return d
185-
if d.startswith('"') or d.startswith("'"):
186-
return d[1:-1]
187-
if d == "true":
188-
return True
189-
if d == "false":
190-
return False
191-
if "." in d:
192-
return float(d)
193-
return int(d)
185+
return eval(str(d)) # This should be safe because we've already escaped string values

tests/conftest.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import inspect
12
from pathlib import Path
23
from typing import Any, Callable, Dict, Union
34

@@ -21,6 +22,7 @@
2122
UnionProperty,
2223
)
2324
from openapi_python_client.parser.properties.float import FloatProperty
25+
from openapi_python_client.parser.properties.protocol import Value
2426
from openapi_python_client.schema.openapi_schema_pydantic import Parameter
2527
from openapi_python_client.schema.parameter_location import ParameterLocation
2628

@@ -76,7 +78,19 @@ def _factory(**kwargs):
7678
if callable(defaults):
7779
defaults = defaults(kwargs)
7880
kwargs = {**defaults, **kwargs}
79-
return cls(**kwargs)
81+
# It's very easy to accidentally set "default" to a raw value rather than a Value in our test
82+
# code, which is never valid but mypy can't catch it for us. So we'll transform it here.
83+
default_value = kwargs.get("default")
84+
if default_value is not None and not isinstance(default_value, Value):
85+
# Some of our property classes have convert_value as a class method; others have it as
86+
# an instance method (because the logic requires knowing the state of the property). We
87+
# can only call it here if it's a class method.
88+
if inspect.ismethod(cls.convert_value) and cls.convert_value.__self__ is cls:
89+
kwargs["default"] = cls.convert_value(default_value)
90+
else:
91+
kwargs["default"] = Value(str(default_value))
92+
rv = cls(**kwargs)
93+
return rv
8094

8195
return _factory
8296

tests/test_parser/test_properties/test_init.py

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ def test_is_base_type(self, string_property_factory):
2323
assert string_property_factory().is_base_type is True
2424

2525
@pytest.mark.parametrize(
26-
"required,expected",
27-
[
26+
"required, expected",
27+
(
2828
(True, "str"),
2929
(False, "Union[Unset, str]"),
30-
],
30+
),
3131
)
3232
def test_get_type_string(self, string_property_factory, required, expected):
3333
p = string_property_factory(required=required)
@@ -374,24 +374,6 @@ def test_values_from_list_duplicate(self):
374374

375375

376376
class TestPropertyFromData:
377-
@pytest.mark.parametrize(
378-
"default_value, default_repr",
379-
["a", "'a'"],
380-
["'a'", "\\'a\\'"],
381-
['"a"', "'a'"],
382-
)
383-
def test_property_from_data_str_defaults(self, default_value, default_repr, config):
384-
from openapi_python_client.parser.properties import Schemas, property_from_data
385-
from openapi_python_client.schema import Schema
386-
387-
name = "my_string"
388-
data = Schema(type="string", default=default_value)
389-
prop, _ = property_from_data(name=name, data=data, schemas=Schemas(), config=config)
390-
assert isinstance(prop, StringProperty)
391-
assert isinstance(prop, Value)
392-
assert str(prop) == default_repr
393-
assert prop.default_to_raw == default_value
394-
395377
def test_property_from_data_str_enum(self, enum_property_factory, config):
396378
from openapi_python_client.parser.properties import Class, Schemas, property_from_data
397379
from openapi_python_client.schema import Schema
@@ -771,9 +753,7 @@ def test_no_format(self, string_property_factory, required, config):
771753
name=name, required=required, data=data, parent_name=None, config=config, schemas=Schemas()
772754
)
773755

774-
assert p == string_property_factory(
775-
name=name, required=required, default=StringProperty.convert_value("hello world")
776-
)
756+
assert p == string_property_factory(name=name, required=required, default="hello world")
777757

778758
def test_datetime_format(self, date_time_property_factory, config):
779759
from openapi_python_client.parser.properties import property_from_data
@@ -786,7 +766,9 @@ def test_datetime_format(self, date_time_property_factory, config):
786766
name=name, required=required, data=data, schemas=Schemas(), config=config, parent_name=""
787767
)
788768

789-
assert p == date_time_property_factory(name=name, required=required, default=f"isoparse('{data.default}')")
769+
assert p == date_time_property_factory(
770+
name=name, required=required, default=Value(f"isoparse('{data.default}')")
771+
)
790772

791773
def test_datetime_bad_default(self, config):
792774
from openapi_python_client.parser.properties import property_from_data
@@ -814,7 +796,9 @@ def test_date_format(self, date_property_factory, config):
814796
name=name, required=required, data=data, schemas=Schemas(), config=config, parent_name=""
815797
)
816798

817-
assert p == date_property_factory(name=name, required=required, default=f"isoparse('{data.default}').date()")
799+
assert p == date_property_factory(
800+
name=name, required=required, default=Value(f"isoparse('{data.default}').date()")
801+
)
818802

819803
def test_date_format_bad_default(self, config):
820804
from openapi_python_client.parser.properties import property_from_data

tests/test_parser/test_properties/test_merge_properties.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from openapi_python_client.parser.properties.float import FloatProperty
77
from openapi_python_client.parser.properties.int import IntProperty
88
from openapi_python_client.parser.properties.merge_properties import merge_properties
9+
from openapi_python_client.parser.properties.protocol import Value
910
from openapi_python_client.parser.properties.schemas import Class
1011
from openapi_python_client.parser.properties.string import StringProperty
1112

@@ -68,14 +69,14 @@ def test_incompatible_types(
6869

6970
def test_merge_int_with_float(int_property_factory, float_property_factory):
7071
int_prop = int_property_factory(description="desc1")
71-
float_prop = float_property_factory(default="2", description="desc2")
72+
float_prop = float_property_factory(default=2, description="desc2")
7273

7374
assert merge_properties(int_prop, float_prop) == (
74-
evolve(int_prop, default=float_prop.default, description=float_prop.description)
75+
evolve(int_prop, default=Value("2"), description=float_prop.description)
7576
)
76-
assert merge_properties(float_prop, int_prop) == (evolve(int_prop, default=float_prop.default))
77+
assert merge_properties(float_prop, int_prop) == evolve(int_prop, default=Value("2"))
7778

78-
float_prop_with_non_int_default = evolve(float_prop, default="2.5")
79+
float_prop_with_non_int_default = evolve(float_prop, default=Value("2.5"))
7980
error = merge_properties(int_prop, float_prop_with_non_int_default)
8081
assert isinstance(error, PropertyError), "Expected invalid default to error"
8182
assert error.detail == "Invalid int value: 2.5"

tests/test_parser/test_properties/test_union.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import openapi_python_client.schema as oai
22
from openapi_python_client.parser.errors import ParseError, PropertyError
33
from openapi_python_client.parser.properties import Schemas, UnionProperty
4+
from openapi_python_client.parser.properties.protocol import Value
45
from openapi_python_client.schema import DataType, ParameterLocation
56

67

@@ -19,7 +20,7 @@ def test_property_from_data_union(union_property_factory, date_time_property_fac
1920
name=name,
2021
required=required,
2122
inner_properties=[
22-
string_property_factory(name=f"{name}_type_0", default="'a'"),
23+
string_property_factory(name=f"{name}_type_0", default=Value("'a'")),
2324
date_time_property_factory(name=f"{name}_type_1"),
2425
],
2526
)

0 commit comments

Comments
 (0)