Skip to content

Commit 19fb418

Browse files
committed
chore: Refactor Value to store raw values
1 parent 0bdd8ba commit 19fb418

34 files changed

+221
-204
lines changed

openapi_python_client/parser/properties/any.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,13 @@ def build(
3333

3434
@classmethod
3535
def convert_value(cls, value: Any) -> Value | None:
36+
from .string import StringProperty
37+
3638
if value is None or isinstance(value, Value):
3739
return value
38-
return Value(str(value))
40+
if isinstance(value, str):
41+
return StringProperty.convert_value(value)
42+
return Value(python_code=str(value), raw_value=value)
3943

4044
name: str
4145
required: bool

openapi_python_client/parser/properties/boolean.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@ def convert_value(cls, value: Any) -> Value | None | PropertyError:
5959
return value
6060
if isinstance(value, str):
6161
if value.lower() == "true":
62-
return Value("True")
62+
return Value(python_code="True", raw_value=value)
6363
elif value.lower() == "false":
64-
return Value("False")
64+
return Value(python_code="False", raw_value=value)
6565
if isinstance(value, bool):
66-
return Value(str(value))
66+
return Value(python_code=str(value), raw_value=value)
6767
return PropertyError(f"Invalid boolean value: {value}")

openapi_python_client/parser/properties/const.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@ def convert_value(self, value: Any) -> Value | None | PropertyError:
6969
if value is None:
7070
return value
7171
if value != self.value:
72-
return PropertyError(detail=f"Invalid value for const {self.name}; {value} != {self.value}")
72+
return PropertyError(
73+
detail=f"Invalid value for const {self.name}; {value.raw_value} != {self.value.raw_value}"
74+
)
7375
return value
7476

7577
@staticmethod
@@ -89,7 +91,7 @@ def _convert_value(value: Any) -> Value | None:
8991
return value # pragma: no cover
9092
if isinstance(value, str):
9193
return StringProperty.convert_value(value)
92-
return Value(str(value))
94+
return Value(python_code=str(value), raw_value=value)
9395

9496
def get_type_string(
9597
self,
@@ -99,7 +101,7 @@ def get_type_string(
99101
multipart: bool = False,
100102
quoted: bool = False,
101103
) -> str:
102-
lit = f"Literal[{self.value}]"
104+
lit = f"Literal[{self.value.python_code}]"
103105
if not no_optional and not self.required:
104106
return f"Union[{lit}, Unset]"
105107
return lit

openapi_python_client/parser/properties/date.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def convert_value(cls, value: Any) -> Value | None | PropertyError:
5757
isoparse(value).date() # make sure it's a valid value
5858
except ValueError as e:
5959
return PropertyError(f"Invalid date: {e}")
60-
return Value(f"isoparse({value!r}).date()")
60+
return Value(python_code=f"isoparse({value!r}).date()", raw_value=value)
6161
return PropertyError(f"Cannot convert {value} to a date")
6262

6363
def get_imports(self, *, prefix: str) -> set[str]:

openapi_python_client/parser/properties/datetime.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def convert_value(cls, value: Any) -> Value | None | PropertyError:
5959
isoparse(value) # make sure it's a valid value
6060
except ValueError as e:
6161
return PropertyError(f"Invalid datetime: {e}")
62-
return Value(f"isoparse({value!r})")
62+
return Value(python_code=f"isoparse({value!r})", raw_value=value)
6363
return PropertyError(f"Cannot convert {value} to a datetime")
6464

6565
def get_imports(self, *, prefix: str) -> set[str]:

openapi_python_client/parser/properties/enum_property.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -159,17 +159,11 @@ def convert_value(self, value: Any) -> Value | PropertyError | None:
159159
if isinstance(value, self.value_type):
160160
inverse_values = {v: k for k, v in self.values.items()}
161161
try:
162-
return Value(f"{self.class_info.name}.{inverse_values[value]}")
162+
return Value(python_code=f"{self.class_info.name}.{inverse_values[value]}", raw_value=value)
163163
except KeyError:
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-
173167
def get_base_type_string(self, *, quoted: bool = False) -> str:
174168
return self.class_info.name
175169

openapi_python_client/parser/properties/float.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,11 @@ def convert_value(cls, value: Any) -> Value | None | PropertyError:
6161
if isinstance(value, str):
6262
try:
6363
parsed = float(value)
64-
return Value(str(parsed))
64+
return Value(python_code=str(parsed), raw_value=value)
6565
except ValueError:
6666
return PropertyError(f"Invalid float value: {value}")
6767
if isinstance(value, float):
68-
return Value(str(value))
68+
return Value(python_code=str(value), raw_value=value)
6969
if isinstance(value, int) and not isinstance(value, bool):
70-
return Value(str(float(value)))
70+
return Value(python_code=str(float(value)), raw_value=value)
7171
return PropertyError(f"Cannot convert {value} to a float")

openapi_python_client/parser/properties/int.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,16 @@ def build(
5858
def convert_value(cls, value: Any) -> Value | None | PropertyError:
5959
if value is None or isinstance(value, Value):
6060
return value
61-
if isinstance(value, str):
61+
converted = value
62+
if isinstance(converted, str):
6263
try:
63-
value = float(value)
64+
converted = float(converted)
6465
except ValueError:
65-
return PropertyError(f"Invalid int value: {value}")
66-
if isinstance(value, float):
67-
as_int = int(value)
68-
if value == as_int:
69-
value = as_int
70-
if isinstance(value, int) and not isinstance(value, bool):
71-
return Value(str(value))
66+
return PropertyError(f"Invalid int value: {converted}")
67+
if isinstance(converted, float):
68+
as_int = int(converted)
69+
if converted == as_int:
70+
converted = as_int
71+
if isinstance(converted, int) and not isinstance(converted, bool):
72+
return Value(python_code=str(converted), raw_value=value)
7273
return PropertyError(f"Invalid int value: {value}")

openapi_python_client/parser/properties/merge_properties.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,10 @@ def _merge_common_attributes(base: PropertyT, *extend_with: PropertyProtocol) ->
148148
"""
149149
current = base
150150
for override in extend_with:
151-
override_default = current.convert_value(override.default_to_raw())
151+
if override.default is not None:
152+
override_default = current.convert_value(override.default.raw_value)
153+
else:
154+
override_default = None
152155
if isinstance(override_default, PropertyError):
153156
return override_default
154157
current = evolve(

openapi_python_client/parser/properties/none.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,5 @@ def convert_value(cls, value: Any) -> Value | None | PropertyError:
5757
return value
5858
if isinstance(value, str):
5959
if value == "None":
60-
return Value(value)
60+
return Value(python_code=value, raw_value=value)
6161
return PropertyError(f"Value {value} is not valid, only None is allowed")

openapi_python_client/parser/properties/protocol.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
__all__ = ["PropertyProtocol", "Value"]
44

55
from abc import abstractmethod
6+
from dataclasses import dataclass
67
from typing import TYPE_CHECKING, Any, ClassVar, Protocol, TypeVar
78

89
from ... import Config
@@ -16,8 +17,15 @@
1617
ModelProperty = "ModelProperty"
1718

1819

19-
class Value(str):
20-
"""Represents a valid (converted) value for a property"""
20+
@dataclass
21+
class Value:
22+
"""
23+
Some literal values in OpenAPI documents (like defaults) have to be converted into Python code safely
24+
(with string escaping, for example). We still keep the `raw_value` around for merging `allOf`.
25+
"""
26+
27+
python_code: str
28+
raw_value: Any
2129

2230

2331
PropertyType = TypeVar("PropertyType", bound="PropertyProtocol")
@@ -148,7 +156,7 @@ def to_string(self) -> str:
148156
"""How this should be declared in a dataclass"""
149157
default: str | None
150158
if self.default is not None:
151-
default = self.default
159+
default = self.default.python_code
152160
elif not self.required:
153161
default = "UNSET"
154162
else:
@@ -162,7 +170,7 @@ def to_docstring(self) -> str:
162170
"""Returns property docstring"""
163171
doc = f"{self.python_name} ({self.get_type_string()}): {self.description or ''}"
164172
if self.default:
165-
doc += f" Default: {self.default}."
173+
doc += f" Default: {self.default.python_code}."
166174
if self.example:
167175
doc += f" Example: {self.example}."
168176
return doc
@@ -177,9 +185,3 @@ def is_base_type(self) -> bool:
177185
ListProperty.__name__,
178186
UnionProperty.__name__,
179187
}
180-
181-
def default_to_raw(self) -> Any | None:
182-
d = self.default
183-
if not isinstance(d, Value):
184-
return d
185-
return eval(str(d)) # This should be safe because we've already escaped string values

openapi_python_client/parser/properties/string.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,4 @@ def convert_value(cls, value: Any) -> Value | None:
6565
return value
6666
if not isinstance(value, str):
6767
value = str(value)
68-
return Value(repr(utils.remove_string_escapes(value)))
68+
return Value(python_code=repr(utils.remove_string_escapes(value)), raw_value=value)
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{% macro construct(property, source) %}
22
{{ property.python_name }} = cast({{ property.get_type_string() }} , {{ source }})
3-
if {{ property.python_name }} != {{ property.value }}{% if not property.required %}and not isinstance({{ property.python_name }}, Unset){% endif %}:
4-
raise ValueError(f"{{ property.name }} must match const {{ property.value }}, got '{{'{' + property.python_name + '}' }}'")
3+
if {{ property.python_name }} != {{ property.value.python_code }}{% if not property.required %}and not isinstance({{ property.python_name }}, Unset){% endif %}:
4+
raise ValueError(f"{{ property.name }} must match const {{ property.value.python_code }}, got '{{'{' + property.python_name + '}' }}'")
55
{%- endmacro %}

0 commit comments

Comments
 (0)