Skip to content

Commit a3f7c33

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

File tree

6 files changed

+36
-39
lines changed

6 files changed

+36
-39
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: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -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
@@ -413,7 +395,7 @@ def test_property_from_data_str_enum(self, enum_property_factory, config):
413395
values={"A": "A", "B": "B", "C": "C"},
414396
class_info=Class(name="ParentAnEnum", module_name="parent_an_enum"),
415397
value_type=str,
416-
default="ParentAnEnum.B",
398+
default=Value("ParentAnEnum.B"),
417399
)
418400
assert schemas != new_schemas, "Provided Schemas was mutated"
419401
assert new_schemas.classes_by_name == {
@@ -450,7 +432,7 @@ def test_property_from_data_str_enum_with_null(
450432
)
451433
none_property = none_property_factory(name="my_enum_type_0", required=required)
452434
assert prop == union_property_factory(
453-
name=name, default="ParentAnEnum.B", inner_properties=[none_property, enum_prop]
435+
name=name, default=Value("ParentAnEnum.B"), inner_properties=[none_property, enum_prop]
454436
)
455437
assert schemas != new_schemas, "Provided Schemas was mutated"
456438
assert new_schemas.classes_by_name == {
@@ -786,7 +768,9 @@ def test_datetime_format(self, date_time_property_factory, config):
786768
name=name, required=required, data=data, schemas=Schemas(), config=config, parent_name=""
787769
)
788770

789-
assert p == date_time_property_factory(name=name, required=required, default=f"isoparse('{data.default}')")
771+
assert p == date_time_property_factory(
772+
name=name, required=required, default=Value(f"isoparse('{data.default}')")
773+
)
790774

791775
def test_datetime_bad_default(self, config):
792776
from openapi_python_client.parser.properties import property_from_data
@@ -814,7 +798,9 @@ def test_date_format(self, date_property_factory, config):
814798
name=name, required=required, data=data, schemas=Schemas(), config=config, parent_name=""
815799
)
816800

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

819805
def test_date_format_bad_default(self, config):
820806
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)