diff --git a/.changeset/allow_default_values_for_properties_of_any_type.md b/.changeset/allow_default_values_for_properties_of_any_type.md new file mode 100644 index 000000000..9b1a93bf7 --- /dev/null +++ b/.changeset/allow_default_values_for_properties_of_any_type.md @@ -0,0 +1,5 @@ +--- +default: patch +--- + +# Allow default values for properties of `Any` type diff --git a/end_to_end_tests/baseline_openapi_3.0.json b/end_to_end_tests/baseline_openapi_3.0.json index 4fd2711cf..468e3532d 100644 --- a/end_to_end_tests/baseline_openapi_3.0.json +++ b/end_to_end_tests/baseline_openapi_3.0.json @@ -596,6 +596,16 @@ "name": "float_prop", "in": "query" }, + { + "required": true, + "schema": { + "title": "Float with int default", + "type": "number", + "default": 3 + }, + "name": "float_with_int", + "in": "query" + }, { "required": true, "schema": { @@ -1674,15 +1684,20 @@ "an_required_field" ] }, - "Aliased":{ + "Aliased": { "allOf": [ - {"$ref": "#/components/schemas/AModel"} + { + "$ref": "#/components/schemas/AModel" + } ] }, "Extended": { "allOf": [ - {"$ref": "#/components/schemas/Aliased"}, - {"type": "object", + { + "$ref": "#/components/schemas/Aliased" + }, + { + "type": "object", "properties": { "fromExtended": { "type": "string" @@ -1708,7 +1723,9 @@ ], "type": "object", "properties": { - "any_value": {}, + "any_value": { + "default": "default" + }, "an_enum_value": { "$ref": "#/components/schemas/AnEnum" }, @@ -2185,7 +2202,10 @@ }, "stringToEnum": { "type": "string", - "enum": ["a", "b"] + "enum": [ + "a", + "b" + ] }, "stringToDate": { "type": "string", diff --git a/end_to_end_tests/baseline_openapi_3.1.yaml b/end_to_end_tests/baseline_openapi_3.1.yaml index c100ed296..5b01d9e29 100644 --- a/end_to_end_tests/baseline_openapi_3.1.yaml +++ b/end_to_end_tests/baseline_openapi_3.1.yaml @@ -592,6 +592,16 @@ info: "name": "float_prop", "in": "query" }, + { + "required": true, + "schema": { + "title": "Float with int default", + "type": "number", + "default": 3 + }, + "name": "float_with_int", + "in": "query" + }, { "required": true, "schema": { @@ -1698,7 +1708,9 @@ info: ], "type": "object", "properties": { - "any_value": { }, + "any_value": { + "default": "default", + }, "an_enum_value": { "$ref": "#/components/schemas/AnEnum" }, @@ -1972,7 +1984,7 @@ info: "title": "Some Integer Array", "type": "array", "items": { - "type": ["integer", "null"] + "type": [ "integer", "null" ] } }, "some_array": { @@ -2166,7 +2178,7 @@ info: "numberToInt": { "type": "number" }, - "anyToString": {} + "anyToString": { } } }, { @@ -2179,7 +2191,7 @@ info: }, "stringToEnum": { "type": "string", - "enum": ["a", "b"] + "enum": [ "a", "b" ] }, "stringToDate": { "type": "string", @@ -2265,7 +2277,7 @@ info: }, "ModelWithNoProperties": { "type": "object", - "properties": {}, + "properties": { }, "additionalProperties": false }, "AllOfSubModel": { diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/defaults/defaults_tests_defaults_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/defaults/defaults_tests_defaults_post.py index a14cb2670..43d4e1340 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/defaults/defaults_tests_defaults_post.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/defaults/defaults_tests_defaults_post.py @@ -19,6 +19,7 @@ def _get_kwargs( string_with_num: str = "1", date_prop: datetime.date = isoparse("1010-10-10").date(), float_prop: float = 3.14, + float_with_int: float = 3.0, int_prop: int = 7, boolean_prop: bool = False, list_prop: List[AnEnum], @@ -39,6 +40,8 @@ def _get_kwargs( params["float_prop"] = float_prop + params["float_with_int"] = float_with_int + params["int_prop"] = int_prop params["boolean_prop"] = boolean_prop @@ -117,6 +120,7 @@ def sync_detailed( string_with_num: str = "1", date_prop: datetime.date = isoparse("1010-10-10").date(), float_prop: float = 3.14, + float_with_int: float = 3.0, int_prop: int = 7, boolean_prop: bool = False, list_prop: List[AnEnum], @@ -133,6 +137,7 @@ def sync_detailed( string_with_num (str): Default: '1'. date_prop (datetime.date): Default: isoparse('1010-10-10').date(). float_prop (float): Default: 3.14. + float_with_int (float): Default: 3.0. int_prop (int): Default: 7. boolean_prop (bool): Default: False. list_prop (List[AnEnum]): @@ -155,6 +160,7 @@ def sync_detailed( string_with_num=string_with_num, date_prop=date_prop, float_prop=float_prop, + float_with_int=float_with_int, int_prop=int_prop, boolean_prop=boolean_prop, list_prop=list_prop, @@ -179,6 +185,7 @@ def sync( string_with_num: str = "1", date_prop: datetime.date = isoparse("1010-10-10").date(), float_prop: float = 3.14, + float_with_int: float = 3.0, int_prop: int = 7, boolean_prop: bool = False, list_prop: List[AnEnum], @@ -195,6 +202,7 @@ def sync( string_with_num (str): Default: '1'. date_prop (datetime.date): Default: isoparse('1010-10-10').date(). float_prop (float): Default: 3.14. + float_with_int (float): Default: 3.0. int_prop (int): Default: 7. boolean_prop (bool): Default: False. list_prop (List[AnEnum]): @@ -218,6 +226,7 @@ def sync( string_with_num=string_with_num, date_prop=date_prop, float_prop=float_prop, + float_with_int=float_with_int, int_prop=int_prop, boolean_prop=boolean_prop, list_prop=list_prop, @@ -236,6 +245,7 @@ async def asyncio_detailed( string_with_num: str = "1", date_prop: datetime.date = isoparse("1010-10-10").date(), float_prop: float = 3.14, + float_with_int: float = 3.0, int_prop: int = 7, boolean_prop: bool = False, list_prop: List[AnEnum], @@ -252,6 +262,7 @@ async def asyncio_detailed( string_with_num (str): Default: '1'. date_prop (datetime.date): Default: isoparse('1010-10-10').date(). float_prop (float): Default: 3.14. + float_with_int (float): Default: 3.0. int_prop (int): Default: 7. boolean_prop (bool): Default: False. list_prop (List[AnEnum]): @@ -274,6 +285,7 @@ async def asyncio_detailed( string_with_num=string_with_num, date_prop=date_prop, float_prop=float_prop, + float_with_int=float_with_int, int_prop=int_prop, boolean_prop=boolean_prop, list_prop=list_prop, @@ -296,6 +308,7 @@ async def asyncio( string_with_num: str = "1", date_prop: datetime.date = isoparse("1010-10-10").date(), float_prop: float = 3.14, + float_with_int: float = 3.0, int_prop: int = 7, boolean_prop: bool = False, list_prop: List[AnEnum], @@ -312,6 +325,7 @@ async def asyncio( string_with_num (str): Default: '1'. date_prop (datetime.date): Default: isoparse('1010-10-10').date(). float_prop (float): Default: 3.14. + float_with_int (float): Default: 3.0. int_prop (int): Default: 7. boolean_prop (bool): Default: False. list_prop (List[AnEnum]): @@ -336,6 +350,7 @@ async def asyncio( string_with_num=string_with_num, date_prop=date_prop, float_prop=float_prop, + float_with_int=float_with_int, int_prop=int_prop, boolean_prop=boolean_prop, list_prop=list_prop, diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/a_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model.py index d14160bf8..054e6bfa7 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/a_model.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/a_model.py @@ -33,7 +33,7 @@ class AModel: nullable_one_of_models (Union['FreeFormModel', 'ModelWithUnionProperty', None]): model (ModelWithUnionProperty): nullable_model (Union['ModelWithUnionProperty', None]): - any_value (Union[Unset, Any]): + any_value (Union[Unset, Any]): Default: 'default'. an_optional_allof_enum (Union[Unset, AnAllOfEnum]): nested_list_of_enums (Union[Unset, List[List[DifferentEnum]]]): a_not_required_date (Union[Unset, datetime.date]): @@ -58,7 +58,7 @@ class AModel: model: "ModelWithUnionProperty" nullable_model: Union["ModelWithUnionProperty", None] an_allof_enum_with_overridden_default: AnAllOfEnum = AnAllOfEnum.OVERRIDDEN_DEFAULT - any_value: Union[Unset, Any] = UNSET + any_value: Union[Unset, Any] = "default" an_optional_allof_enum: Union[Unset, AnAllOfEnum] = UNSET nested_list_of_enums: Union[Unset, List[List[DifferentEnum]]] = UNSET a_not_required_date: Union[Unset, datetime.date] = UNSET diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/extended.py b/end_to_end_tests/golden-record/my_test_api_client/models/extended.py index 932c98a99..c3659222b 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/extended.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/extended.py @@ -33,7 +33,7 @@ class Extended: nullable_one_of_models (Union['FreeFormModel', 'ModelWithUnionProperty', None]): model (ModelWithUnionProperty): nullable_model (Union['ModelWithUnionProperty', None]): - any_value (Union[Unset, Any]): + any_value (Union[Unset, Any]): Default: 'default'. an_optional_allof_enum (Union[Unset, AnAllOfEnum]): nested_list_of_enums (Union[Unset, List[List[DifferentEnum]]]): a_not_required_date (Union[Unset, datetime.date]): @@ -59,7 +59,7 @@ class Extended: model: "ModelWithUnionProperty" nullable_model: Union["ModelWithUnionProperty", None] an_allof_enum_with_overridden_default: AnAllOfEnum = AnAllOfEnum.OVERRIDDEN_DEFAULT - any_value: Union[Unset, Any] = UNSET + any_value: Union[Unset, Any] = "default" an_optional_allof_enum: Union[Unset, AnAllOfEnum] = UNSET nested_list_of_enums: Union[Unset, List[List[DifferentEnum]]] = UNSET a_not_required_date: Union[Unset, datetime.date] = UNSET diff --git a/openapi_python_client/parser/properties/__init__.py b/openapi_python_client/parser/properties/__init__.py index c8777f509..02a6fdafe 100644 --- a/openapi_python_client/parser/properties/__init__.py +++ b/openapi_python_client/parser/properties/__init__.py @@ -280,7 +280,7 @@ def property_from_data( # noqa: PLR0911, PLR0912 AnyProperty.build( name=name, required=required, - default=None, + default=data.default, python_name=utils.PythonIdentifier(value=name, prefix=config.field_prefix), description=data.description, example=data.example, diff --git a/openapi_python_client/parser/properties/any.py b/openapi_python_client/parser/properties/any.py index fdeef93a1..b760a1568 100644 --- a/openapi_python_client/parser/properties/any.py +++ b/openapi_python_client/parser/properties/any.py @@ -33,9 +33,13 @@ def build( @classmethod def convert_value(cls, value: Any) -> Value | None: - if value is None or isinstance(value, Value): + from .string import StringProperty + + if value is None: return value - return Value(str(value)) + if isinstance(value, str): + return StringProperty.convert_value(value) + return Value(python_code=str(value), raw_value=value) name: str required: bool diff --git a/openapi_python_client/parser/properties/boolean.py b/openapi_python_client/parser/properties/boolean.py index e6bb883a8..5fd4235d7 100644 --- a/openapi_python_client/parser/properties/boolean.py +++ b/openapi_python_client/parser/properties/boolean.py @@ -59,9 +59,9 @@ def convert_value(cls, value: Any) -> Value | None | PropertyError: return value if isinstance(value, str): if value.lower() == "true": - return Value("True") + return Value(python_code="True", raw_value=value) elif value.lower() == "false": - return Value("False") + return Value(python_code="False", raw_value=value) if isinstance(value, bool): - return Value(str(value)) + return Value(python_code=str(value), raw_value=value) return PropertyError(f"Invalid boolean value: {value}") diff --git a/openapi_python_client/parser/properties/const.py b/openapi_python_client/parser/properties/const.py index b4386eaf5..9da3f8f1e 100644 --- a/openapi_python_client/parser/properties/const.py +++ b/openapi_python_client/parser/properties/const.py @@ -63,13 +63,13 @@ def build( return prop def convert_value(self, value: Any) -> Value | None | PropertyError: - if isinstance(value, Value): - return value value = self._convert_value(value) if value is None: return value if value != self.value: - return PropertyError(detail=f"Invalid value for const {self.name}; {value} != {self.value}") + return PropertyError( + detail=f"Invalid value for const {self.name}; {value.raw_value} != {self.value.raw_value}" + ) return value @staticmethod @@ -85,11 +85,9 @@ def _convert_value(value: Any) -> Value: ... # pragma: no cover def _convert_value(value: Any) -> Value | None: if value is None or isinstance(value, Value): return value - if isinstance(value, Value): - return value # pragma: no cover if isinstance(value, str): return StringProperty.convert_value(value) - return Value(str(value)) + return Value(python_code=str(value), raw_value=value) def get_type_string( self, @@ -99,7 +97,7 @@ def get_type_string( multipart: bool = False, quoted: bool = False, ) -> str: - lit = f"Literal[{self.value}]" + lit = f"Literal[{self.value.python_code}]" if not no_optional and not self.required: return f"Union[{lit}, Unset]" return lit diff --git a/openapi_python_client/parser/properties/date.py b/openapi_python_client/parser/properties/date.py index 24e0c34fe..7261698ea 100644 --- a/openapi_python_client/parser/properties/date.py +++ b/openapi_python_client/parser/properties/date.py @@ -57,7 +57,7 @@ def convert_value(cls, value: Any) -> Value | None | PropertyError: isoparse(value).date() # make sure it's a valid value except ValueError as e: return PropertyError(f"Invalid date: {e}") - return Value(f"isoparse({value!r}).date()") + return Value(python_code=f"isoparse({value!r}).date()", raw_value=value) return PropertyError(f"Cannot convert {value} to a date") def get_imports(self, *, prefix: str) -> set[str]: diff --git a/openapi_python_client/parser/properties/datetime.py b/openapi_python_client/parser/properties/datetime.py index abb28ac22..5924d173c 100644 --- a/openapi_python_client/parser/properties/datetime.py +++ b/openapi_python_client/parser/properties/datetime.py @@ -59,7 +59,7 @@ def convert_value(cls, value: Any) -> Value | None | PropertyError: isoparse(value) # make sure it's a valid value except ValueError as e: return PropertyError(f"Invalid datetime: {e}") - return Value(f"isoparse({value!r})") + return Value(python_code=f"isoparse({value!r})", raw_value=value) return PropertyError(f"Cannot convert {value} to a datetime") def get_imports(self, *, prefix: str) -> set[str]: diff --git a/openapi_python_client/parser/properties/enum_property.py b/openapi_python_client/parser/properties/enum_property.py index 0e5c80588..b6a27254f 100644 --- a/openapi_python_client/parser/properties/enum_property.py +++ b/openapi_python_client/parser/properties/enum_property.py @@ -159,17 +159,11 @@ def convert_value(self, value: Any) -> Value | PropertyError | None: if isinstance(value, self.value_type): inverse_values = {v: k for k, v in self.values.items()} try: - return Value(f"{self.class_info.name}.{inverse_values[value]}") + return Value(python_code=f"{self.class_info.name}.{inverse_values[value]}", raw_value=value) except KeyError: return PropertyError(detail=f"Value {value} is not valid for enum {self.name}") return PropertyError(detail=f"Cannot convert {value} to enum {self.name} of type {self.value_type}") - def default_to_raw(self) -> ValueType | None: - if self.default is None: - return None - key = self.default.split(".")[1] - return self.values[key] - def get_base_type_string(self, *, quoted: bool = False) -> str: return self.class_info.name diff --git a/openapi_python_client/parser/properties/float.py b/openapi_python_client/parser/properties/float.py index d8f469c69..a785db6d4 100644 --- a/openapi_python_client/parser/properties/float.py +++ b/openapi_python_client/parser/properties/float.py @@ -61,11 +61,11 @@ def convert_value(cls, value: Any) -> Value | None | PropertyError: if isinstance(value, str): try: parsed = float(value) - return Value(str(parsed)) + return Value(python_code=str(parsed), raw_value=value) except ValueError: return PropertyError(f"Invalid float value: {value}") if isinstance(value, float): - return Value(str(value)) + return Value(python_code=str(value), raw_value=value) if isinstance(value, int) and not isinstance(value, bool): - return Value(str(float(value))) + return Value(python_code=str(float(value)), raw_value=value) return PropertyError(f"Cannot convert {value} to a float") diff --git a/openapi_python_client/parser/properties/int.py b/openapi_python_client/parser/properties/int.py index b34663a34..1cd340fbd 100644 --- a/openapi_python_client/parser/properties/int.py +++ b/openapi_python_client/parser/properties/int.py @@ -58,15 +58,16 @@ def build( def convert_value(cls, value: Any) -> Value | None | PropertyError: if value is None or isinstance(value, Value): return value - if isinstance(value, str): + converted = value + if isinstance(converted, str): try: - value = float(value) + converted = float(converted) except ValueError: - return PropertyError(f"Invalid int value: {value}") - if isinstance(value, float): - as_int = int(value) - if value == as_int: - value = as_int - if isinstance(value, int) and not isinstance(value, bool): - return Value(str(value)) + return PropertyError(f"Invalid int value: {converted}") + if isinstance(converted, float): + as_int = int(converted) + if converted == as_int: + converted = as_int + if isinstance(converted, int) and not isinstance(converted, bool): + return Value(python_code=str(converted), raw_value=value) return PropertyError(f"Invalid int value: {value}") diff --git a/openapi_python_client/parser/properties/merge_properties.py b/openapi_python_client/parser/properties/merge_properties.py index f4e72849b..dc7b3e5eb 100644 --- a/openapi_python_client/parser/properties/merge_properties.py +++ b/openapi_python_client/parser/properties/merge_properties.py @@ -148,7 +148,10 @@ def _merge_common_attributes(base: PropertyT, *extend_with: PropertyProtocol) -> """ current = base for override in extend_with: - override_default = current.convert_value(override.default_to_raw()) + if override.default is not None: + override_default = current.convert_value(override.default.raw_value) + else: + override_default = None if isinstance(override_default, PropertyError): return override_default current = evolve( diff --git a/openapi_python_client/parser/properties/none.py b/openapi_python_client/parser/properties/none.py index c329dac40..9c473693d 100644 --- a/openapi_python_client/parser/properties/none.py +++ b/openapi_python_client/parser/properties/none.py @@ -57,5 +57,5 @@ def convert_value(cls, value: Any) -> Value | None | PropertyError: return value if isinstance(value, str): if value == "None": - return Value(value) + return Value(python_code=value, raw_value=value) return PropertyError(f"Value {value} is not valid, only None is allowed") diff --git a/openapi_python_client/parser/properties/protocol.py b/openapi_python_client/parser/properties/protocol.py index 14af0c976..c9555949d 100644 --- a/openapi_python_client/parser/properties/protocol.py +++ b/openapi_python_client/parser/properties/protocol.py @@ -3,6 +3,7 @@ __all__ = ["PropertyProtocol", "Value"] from abc import abstractmethod +from dataclasses import dataclass from typing import TYPE_CHECKING, Any, ClassVar, Protocol, TypeVar from ... import Config @@ -16,8 +17,15 @@ ModelProperty = "ModelProperty" -class Value(str): - """Represents a valid (converted) value for a property""" +@dataclass +class Value: + """ + Some literal values in OpenAPI documents (like defaults) have to be converted into Python code safely + (with string escaping, for example). We still keep the `raw_value` around for merging `allOf`. + """ + + python_code: str + raw_value: Any PropertyType = TypeVar("PropertyType", bound="PropertyProtocol") @@ -148,7 +156,7 @@ def to_string(self) -> str: """How this should be declared in a dataclass""" default: str | None if self.default is not None: - default = self.default + default = self.default.python_code elif not self.required: default = "UNSET" else: @@ -162,7 +170,7 @@ def to_docstring(self) -> str: """Returns property docstring""" doc = f"{self.python_name} ({self.get_type_string()}): {self.description or ''}" if self.default: - doc += f" Default: {self.default}." + doc += f" Default: {self.default.python_code}." if self.example: doc += f" Example: {self.example}." return doc @@ -177,9 +185,3 @@ def is_base_type(self) -> bool: ListProperty.__name__, UnionProperty.__name__, } - - def default_to_raw(self) -> Any | None: - d = self.default - if not isinstance(d, Value): - return d - return eval(str(d)) # This should be safe because we've already escaped string values diff --git a/openapi_python_client/parser/properties/string.py b/openapi_python_client/parser/properties/string.py index f9adf04ae..e40c1eee6 100644 --- a/openapi_python_client/parser/properties/string.py +++ b/openapi_python_client/parser/properties/string.py @@ -65,4 +65,4 @@ def convert_value(cls, value: Any) -> Value | None: return value if not isinstance(value, str): value = str(value) - return Value(repr(utils.remove_string_escapes(value))) + return Value(python_code=repr(utils.remove_string_escapes(value)), raw_value=value) diff --git a/openapi_python_client/templates/property_templates/const_property.py.jinja b/openapi_python_client/templates/property_templates/const_property.py.jinja index ea48ab73e..fa635b5b9 100644 --- a/openapi_python_client/templates/property_templates/const_property.py.jinja +++ b/openapi_python_client/templates/property_templates/const_property.py.jinja @@ -1,5 +1,5 @@ {% macro construct(property, source) %} {{ property.python_name }} = cast({{ property.get_type_string() }} , {{ source }}) -if {{ property.python_name }} != {{ property.value }}{% if not property.required %}and not isinstance({{ property.python_name }}, Unset){% endif %}: - raise ValueError(f"{{ property.name }} must match const {{ property.value }}, got '{{'{' + property.python_name + '}' }}'") +if {{ property.python_name }} != {{ property.value.python_code }}{% if not property.required %}and not isinstance({{ property.python_name }}, Unset){% endif %}: + raise ValueError(f"{{ property.name }} must match const {{ property.value.python_code }}, got '{{'{' + property.python_name + '}' }}'") {%- endmacro %} diff --git a/pyproject.toml b/pyproject.toml index 0689285df..e2058e058 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,7 +66,7 @@ ignore = ["E501", "PLR0913"] "tests/*" = ["PLR2004"] [tool.coverage.run] -omit = ["openapi_python_client/templates/*"] +omit = ["openapi_python_client/__main__.py", "openapi_python_client/templates/*"] [tool.mypy] plugins = ["pydantic.mypy"] diff --git a/tests/conftest.py b/tests/conftest.py index a15a15741..c01b4ce87 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,8 +1,10 @@ -import inspect +from __future__ import annotations + from pathlib import Path -from typing import Any, Callable, Dict, Union +from typing import Any, Callable import pytest +from mypy.semanal_shared import Protocol from openapi_python_client import Config, MetaType from openapi_python_client import schema as oai @@ -10,6 +12,7 @@ from openapi_python_client.parser.properties import ( AnyProperty, BooleanProperty, + Class, DateProperty, DateTimeProperty, EnumProperty, @@ -22,9 +25,10 @@ UnionProperty, ) from openapi_python_client.parser.properties.float import FloatProperty -from openapi_python_client.parser.properties.protocol import Value +from openapi_python_client.parser.properties.protocol import PropertyType, Value from openapi_python_client.schema.openapi_schema_pydantic import Parameter from openapi_python_client.schema.parameter_location import ParameterLocation +from openapi_python_client.utils import ClassName, PythonIdentifier @pytest.fixture(scope="session") @@ -40,8 +44,12 @@ def config() -> Config: ) +class ModelFactory(Protocol): + def __call__(self, *args, **kwargs): ... + + @pytest.fixture -def model_property_factory() -> Callable[..., ModelProperty]: +def model_property_factory() -> ModelFactory: """ This fixture surfaces in the test as a function which manufactures ModelProperties with defaults. @@ -53,7 +61,7 @@ def _factory(**kwargs): kwargs = _common_kwargs(kwargs) kwargs = { "description": "", - "class_info": Class(name="MyClass", module_name="my_module"), + "class_info": Class(name=ClassName("MyClass", ""), module_name=PythonIdentifier("my_module", "")), "data": oai.Schema.model_construct(), "roots": set(), "required_properties": None, @@ -70,7 +78,9 @@ def _factory(**kwargs): return _factory -def _simple_factory(cls: type, default_kwargs: Union[dict, Callable[[dict], dict], None] = None): +def _simple_factory( + cls: type[PropertyType], default_kwargs: dict | Callable[[dict], dict] | None = None +) -> Callable[..., PropertyType]: def _factory(**kwargs): kwargs = _common_kwargs(kwargs) defaults = default_kwargs @@ -78,25 +88,42 @@ def _factory(**kwargs): if callable(defaults): defaults = defaults(kwargs) kwargs = {**defaults, **kwargs} - # It's very easy to accidentally set "default" to a raw value rather than a Value in our test - # code, which is never valid but mypy can't catch it for us. So we'll transform it here. - default_value = kwargs.get("default") - if default_value is not None and not isinstance(default_value, Value): - # Some of our property classes have convert_value as a class method; others have it as - # an instance method (because the logic requires knowing the state of the property). We - # can only call it here if it's a class method. - if inspect.ismethod(cls.convert_value) and cls.convert_value.__self__ is cls: - kwargs["default"] = cls.convert_value(default_value) - else: - kwargs["default"] = Value(str(default_value)) rv = cls(**kwargs) return rv return _factory +class SimpleFactory(Protocol[PropertyType]): + def __call__( + self, + *, + default: Value | None = None, + name: str | None = None, + required: bool | None = None, + description: str | None = None, + example: str | None = None, + ) -> PropertyType: ... + + +class EnumFactory(Protocol): + def __call__( + self, + *, + default: Value | None = None, + name: str | None = None, + required: bool | None = None, + values: dict[str, str | int] | None = None, + class_info: Class | None = None, + value_type: type | None = None, + python_name: PythonIdentifier | None = None, + description: str | None = None, + example: str | None = None, + ) -> EnumProperty: ... + + @pytest.fixture -def enum_property_factory() -> Callable[..., EnumProperty]: +def enum_property_factory() -> EnumFactory: """ This fixture surfaces in the test as a function which manufactures EnumProperties with defaults. @@ -115,7 +142,7 @@ def enum_property_factory() -> Callable[..., EnumProperty]: @pytest.fixture -def any_property_factory() -> Callable[..., AnyProperty]: +def any_property_factory() -> SimpleFactory[AnyProperty]: """ This fixture surfaces in the test as a function which manufactures AnyProperty with defaults. @@ -126,7 +153,7 @@ def any_property_factory() -> Callable[..., AnyProperty]: @pytest.fixture -def string_property_factory() -> Callable[..., StringProperty]: +def string_property_factory() -> SimpleFactory[StringProperty]: """ This fixture surfaces in the test as a function which manufactures StringProperties with defaults. @@ -137,7 +164,7 @@ def string_property_factory() -> Callable[..., StringProperty]: @pytest.fixture -def int_property_factory() -> Callable[..., IntProperty]: +def int_property_factory() -> SimpleFactory[IntProperty]: """ This fixture surfaces in the test as a function which manufactures IntProperties with defaults. @@ -148,7 +175,7 @@ def int_property_factory() -> Callable[..., IntProperty]: @pytest.fixture -def float_property_factory() -> Callable[..., FloatProperty]: +def float_property_factory() -> SimpleFactory[FloatProperty]: """ This fixture surfaces in the test as a function which manufactures FloatProperties with defaults. @@ -159,7 +186,7 @@ def float_property_factory() -> Callable[..., FloatProperty]: @pytest.fixture -def none_property_factory() -> Callable[..., NoneProperty]: +def none_property_factory() -> SimpleFactory[NoneProperty]: """ This fixture surfaces in the test as a function which manufactures NoneProperties with defaults. @@ -170,7 +197,7 @@ def none_property_factory() -> Callable[..., NoneProperty]: @pytest.fixture -def boolean_property_factory() -> Callable[..., BooleanProperty]: +def boolean_property_factory() -> SimpleFactory[BooleanProperty]: """ This fixture surfaces in the test as a function which manufactures BooleanProperties with defaults. @@ -181,7 +208,7 @@ def boolean_property_factory() -> Callable[..., BooleanProperty]: @pytest.fixture -def date_time_property_factory() -> Callable[..., DateTimeProperty]: +def date_time_property_factory() -> SimpleFactory[DateTimeProperty]: """ This fixture surfaces in the test as a function which manufactures DateTimeProperties with defaults. @@ -192,7 +219,7 @@ def date_time_property_factory() -> Callable[..., DateTimeProperty]: @pytest.fixture -def date_property_factory() -> Callable[..., DateProperty]: +def date_property_factory() -> SimpleFactory[DateProperty]: """ This fixture surfaces in the test as a function which manufactures DateProperties with defaults. @@ -203,7 +230,7 @@ def date_property_factory() -> Callable[..., DateProperty]: @pytest.fixture -def file_property_factory() -> Callable[..., FileProperty]: +def file_property_factory() -> SimpleFactory[FileProperty]: """ This fixture surfaces in the test as a function which manufactures FileProperties with defaults. @@ -214,7 +241,7 @@ def file_property_factory() -> Callable[..., FileProperty]: @pytest.fixture -def list_property_factory(string_property_factory) -> Callable[..., ListProperty]: +def list_property_factory(string_property_factory) -> SimpleFactory[ListProperty]: """ This fixture surfaces in the test as a function which manufactures ListProperties with defaults. @@ -224,8 +251,19 @@ def list_property_factory(string_property_factory) -> Callable[..., ListProperty return _simple_factory(ListProperty, {"inner_property": string_property_factory()}) +class UnionFactory(SimpleFactory): + def __call__( + self, + *, + default: Value | None = None, + name: str | None = None, + required: bool | None = None, + inner_properties: list[PropertyType] | None = None, + ) -> UnionProperty: ... + + @pytest.fixture -def union_property_factory(date_time_property_factory, string_property_factory) -> Callable[..., UnionProperty]: +def union_property_factory(date_time_property_factory, string_property_factory) -> UnionFactory: """ This fixture surfaces in the test as a function which manufactures UnionProperties with defaults. @@ -256,7 +294,7 @@ def _factory(**kwargs): return _factory -def _common_kwargs(kwargs: Dict[str, Any]) -> Dict[str, Any]: +def _common_kwargs(kwargs: dict[str, Any]) -> dict[str, Any]: kwargs = { "name": "test", "required": True, diff --git a/tests/test___main__.py b/tests/test___main__.py deleted file mode 100644 index 0673c2062..000000000 --- a/tests/test___main__.py +++ /dev/null @@ -1,6 +0,0 @@ -def test_main(mocker): - app = mocker.patch("openapi_python_client.cli.app") - - from openapi_python_client import __main__ # noqa: F401 - - app.assert_called_once() diff --git a/tests/test_cli.py b/tests/test_cli.py index f5f3e0ea8..8679584fd 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -3,7 +3,7 @@ runner = CliRunner() -def test_version(): +def test_version() -> None: from openapi_python_client.cli import app result = runner.invoke(app, ["--version", "generate"]) @@ -12,7 +12,7 @@ def test_version(): assert "openapi-python-client version: " in result.stdout -def test_bad_config(): +def test_bad_config() -> None: from openapi_python_client.cli import app config_path = "config/path" @@ -25,14 +25,14 @@ def test_bad_config(): class TestGenerate: - def test_generate_no_params(self): + def test_generate_no_params(self) -> None: from openapi_python_client.cli import app result = runner.invoke(app, ["generate"]) assert result.exit_code == 1, result.output - def test_generate_url_and_path(self): + def test_generate_url_and_path(self) -> None: from openapi_python_client.cli import app result = runner.invoke(app, ["generate", "--path=blah", "--url=otherblah"]) @@ -40,7 +40,7 @@ def test_generate_url_and_path(self): assert result.exit_code == 1 assert result.output == "Provide either --url or --path, not both\n" - def test_generate_encoding_errors(self): + def test_generate_encoding_errors(self) -> None: path = "cool/path" file_encoding = "error-file-encoding" from openapi_python_client.cli import app diff --git a/tests/test_config.py b/tests/test_config.py index 2e39bbf4e..c24766f9b 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -10,7 +10,7 @@ yaml = YAML(typ=["safe", "string"]) -def json_with_tabs(d): +def json_with_tabs(d: dict) -> str: return json.dumps(d, indent=4).replace(" ", "\t") @@ -24,7 +24,7 @@ def json_with_tabs(d): ], ) @pytest.mark.parametrize("relative", (True, False), ids=("relative", "absolute")) -def test_load_from_path(tmp_path: Path, filename, dump, relative): +def test_load_from_path(tmp_path: Path, filename, dump, relative) -> None: yml_file = tmp_path.joinpath(filename) if relative: if not os.getenv("TEST_RELATIVE"): diff --git a/tests/test_parser/test_properties/test_any.py b/tests/test_parser/test_properties/test_any.py index 7738a24f9..d80e93e64 100644 --- a/tests/test_parser/test_properties/test_any.py +++ b/tests/test_parser/test_properties/test_any.py @@ -1,12 +1,13 @@ from openapi_python_client.parser.properties import AnyProperty +from openapi_python_client.utils import PythonIdentifier -def test_default(): +def test_default() -> None: AnyProperty.build( name="test", required=True, default=42, - python_name="test", + python_name=PythonIdentifier("test", ""), description="test", example="test", ) diff --git a/tests/test_parser/test_properties/test_boolean.py b/tests/test_parser/test_properties/test_boolean.py index 0c4abf0f3..0862f1507 100644 --- a/tests/test_parser/test_properties/test_boolean.py +++ b/tests/test_parser/test_properties/test_boolean.py @@ -2,15 +2,16 @@ from openapi_python_client.parser.errors import PropertyError from openapi_python_client.parser.properties import BooleanProperty +from openapi_python_client.utils import PythonIdentifier -def test_invalid_default_value(): +def test_invalid_default_value() -> None: err = BooleanProperty.build( default="not a boolean", description=None, example=None, required=False, - python_name="not_a_boolean", + python_name=PythonIdentifier("not_a_boolean", ""), name="not_a_boolean", ) @@ -26,7 +27,7 @@ def test_invalid_default_value(): ("False", "False"), ), ) -def test_string_default(value, expected): +def test_string_default(value, expected) -> None: prop = BooleanProperty.build( default=value, description=None, @@ -37,10 +38,10 @@ def test_string_default(value, expected): ) assert isinstance(prop, BooleanProperty) - assert prop.default == expected + assert prop.default.python_code == expected -def test_bool_default(): +def test_bool_default() -> None: prop = BooleanProperty.build( default=True, description=None, @@ -51,4 +52,4 @@ def test_bool_default(): ) assert isinstance(prop, BooleanProperty) - assert prop.default == "True" + assert prop.default.python_code == "True" diff --git a/tests/test_parser/test_properties/test_const.py b/tests/test_parser/test_properties/test_const.py index 6d2ad0bfe..ab9e29332 100644 --- a/tests/test_parser/test_properties/test_const.py +++ b/tests/test_parser/test_properties/test_const.py @@ -1,9 +1,8 @@ from openapi_python_client.parser.errors import PropertyError from openapi_python_client.parser.properties import ConstProperty -from openapi_python_client.parser.properties.protocol import Value -def test_default_doesnt_match_const(): +def test_default_doesnt_match_const() -> None: err = ConstProperty.build( name="test", required=True, @@ -16,7 +15,7 @@ def test_default_doesnt_match_const(): assert isinstance(err, PropertyError) -def test_non_string_const(): +def test_non_string_const() -> None: prop = ConstProperty.build( name="test", required=True, @@ -27,29 +26,3 @@ def test_non_string_const(): ) assert isinstance(prop, ConstProperty) - - -def test_const_already_converted(): - prop = ConstProperty.build( - name="test", - required=True, - default=123, - python_name="test", - description=None, - const=Value("123"), - ) - - assert isinstance(prop, ConstProperty) - - -def test_default_already_converted(): - prop = ConstProperty.build( - name="test", - required=True, - default=Value("123"), - python_name="test", - description=None, - const=123, - ) - - assert isinstance(prop, ConstProperty) diff --git a/tests/test_parser/test_properties/test_date.py b/tests/test_parser/test_properties/test_date.py index 0c70b5c30..bcc3292b6 100644 --- a/tests/test_parser/test_properties/test_date.py +++ b/tests/test_parser/test_properties/test_date.py @@ -1,6 +1,5 @@ from openapi_python_client.parser.errors import PropertyError from openapi_python_client.parser.properties import DateProperty -from openapi_python_client.parser.properties.protocol import Value def test_invalid_default_value(): @@ -27,7 +26,3 @@ def test_default_with_bad_type(): ) assert isinstance(err, PropertyError) - - -def test_dont_recheck_value(): - DateProperty.convert_value(Value("not a date but trust me")) diff --git a/tests/test_parser/test_properties/test_datetime.py b/tests/test_parser/test_properties/test_datetime.py index 7853208d7..94ea6f09c 100644 --- a/tests/test_parser/test_properties/test_datetime.py +++ b/tests/test_parser/test_properties/test_datetime.py @@ -1,6 +1,5 @@ from openapi_python_client.parser.errors import PropertyError from openapi_python_client.parser.properties import DateTimeProperty -from openapi_python_client.parser.properties.protocol import Value def test_invalid_default_value(): @@ -27,7 +26,3 @@ def test_default_with_bad_type(): ) assert isinstance(err, PropertyError) - - -def test_dont_recheck_value(): - DateTimeProperty.convert_value(Value("not a date but trust me")) diff --git a/tests/test_parser/test_properties/test_enum_property.py b/tests/test_parser/test_properties/test_enum_property.py index 704f48b3b..dce27ce10 100644 --- a/tests/test_parser/test_properties/test_enum_property.py +++ b/tests/test_parser/test_properties/test_enum_property.py @@ -1,9 +1,10 @@ import openapi_python_client.schema as oai +from openapi_python_client import Config from openapi_python_client.parser.errors import PropertyError from openapi_python_client.parser.properties import EnumProperty, Schemas -def test_conflict(config): +def test_conflict(config: Config) -> None: schemas = Schemas() _, schemas = EnumProperty.build( @@ -22,7 +23,7 @@ def test_conflict(config): assert err.detail == "Found conflicting enums named Existing with incompatible values." -def test_bad_default_value(config): +def test_bad_default_value(config: Config) -> None: data = oai.Schema(default="B", enum=["A"]) schemas = Schemas() @@ -34,7 +35,7 @@ def test_bad_default_value(config): assert err == PropertyError(detail="Value B is not valid for enum Existing", data=data) -def test_bad_default_type(config): +def test_bad_default_type(config: Config) -> None: data = oai.Schema(default=123, enum=["A"]) schemas = Schemas() @@ -46,7 +47,7 @@ def test_bad_default_type(config): assert isinstance(err, PropertyError) -def test_mixed_types(config): +def test_mixed_types(config: Config) -> None: data = oai.Schema(enum=["A", 1]) schemas = Schemas() @@ -57,7 +58,7 @@ def test_mixed_types(config): assert isinstance(err, PropertyError) -def test_unsupported_type(config): +def test_unsupported_type(config: Config) -> None: data = oai.Schema(enum=[1.4, 1.5]) schemas = Schemas() diff --git a/tests/test_parser/test_properties/test_float.py b/tests/test_parser/test_properties/test_float.py index 9d1159409..356a61424 100644 --- a/tests/test_parser/test_properties/test_float.py +++ b/tests/test_parser/test_properties/test_float.py @@ -17,17 +17,12 @@ def test_invalid_default(): def test_convert_from_string(): - val = FloatProperty.convert_value("1.0") - assert isinstance(val, Value) - assert val == "1.0" - assert FloatProperty.convert_value("1") == "1.0" + assert FloatProperty.convert_value("1.0") == Value(python_code="1.0", raw_value="1.0") + assert FloatProperty.convert_value("1") == Value(python_code="1.0", raw_value="1") def test_convert_from_float(): - val = FloatProperty.convert_value(1.0) - assert isinstance(val, Value) - assert val == "1.0" - assert FloatProperty.convert_value(1) == "1.0" + assert FloatProperty.convert_value(1.0) == Value(python_code="1.0", raw_value=1.0) def test_invalid_type_default(): diff --git a/tests/test_parser/test_properties/test_init.py b/tests/test_parser/test_properties/test_init.py index 2efa27744..5bfd8bc41 100644 --- a/tests/test_parser/test_properties/test_init.py +++ b/tests/test_parser/test_properties/test_init.py @@ -7,6 +7,7 @@ from openapi_python_client.parser.errors import ParameterError, PropertyError from openapi_python_client.parser.properties import ( ListProperty, + ReferencePath, Schemas, StringProperty, UnionProperty, @@ -383,7 +384,7 @@ def test_property_from_data_str_enum(self, enum_property_factory, config): name = "my_enum" required = True - schemas = Schemas(classes_by_name={"AnEnum": existing}) + schemas = Schemas(classes_by_name={ClassName("AnEnum", prefix=""): existing}) prop, new_schemas = property_from_data( name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=config @@ -393,9 +394,9 @@ def test_property_from_data_str_enum(self, enum_property_factory, config): name=name, required=required, values={"A": "A", "B": "B", "C": "C"}, - class_info=Class(name="ParentAnEnum", module_name="parent_an_enum"), + class_info=Class(name=ClassName("ParentAnEnum", ""), module_name=PythonIdentifier("parent_an_enum", "")), value_type=str, - default="ParentAnEnum.B", + default=Value(python_code="ParentAnEnum.B", raw_value="B"), ) assert schemas != new_schemas, "Provided Schemas was mutated" assert new_schemas.classes_by_name == { @@ -414,7 +415,7 @@ def test_property_from_data_str_enum_with_null( name = "my_enum" required = True - schemas = Schemas(classes_by_name={"AnEnum": existing}) + schemas = Schemas(classes_by_name={ClassName("AnEnum", ""): existing}) prop, new_schemas = property_from_data( name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=config @@ -426,13 +427,15 @@ def test_property_from_data_str_enum_with_null( name="my_enum_type_1", required=required, values={"A": "A", "B": "B", "C": "C"}, - class_info=Class(name="ParentAnEnum", module_name="parent_an_enum"), + class_info=Class(name=ClassName("ParentAnEnum", ""), module_name=PythonIdentifier("parent_an_enum", "")), value_type=str, - default="ParentAnEnum.B", + default=Value(python_code="ParentAnEnum.B", raw_value="B"), ) none_property = none_property_factory(name="my_enum_type_0", required=required) assert prop == union_property_factory( - name=name, default="ParentAnEnum.B", inner_properties=[none_property, enum_prop] + name=name, + default=Value(python_code="ParentAnEnum.B", raw_value="B"), + inner_properties=[none_property, enum_prop], ) assert schemas != new_schemas, "Provided Schemas was mutated" assert new_schemas.classes_by_name == { @@ -454,7 +457,9 @@ def test_property_from_data_null_enum(self, enum_property_factory, none_property name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=config ) - assert prop == none_property_factory(name="my_enum", required=required, default="None") + assert prop == none_property_factory( + name="my_enum", required=required, default=Value(python_code="None", raw_value="None") + ) def test_property_from_data_int_enum(self, enum_property_factory, config): from openapi_python_client.parser.properties import Class, Schemas, property_from_data @@ -465,7 +470,7 @@ def test_property_from_data_int_enum(self, enum_property_factory, config): data = Schema.model_construct(title="anEnum", enum=[1, 2, 3], default=3) existing = enum_property_factory() - schemas = Schemas(classes_by_name={"AnEnum": existing}) + schemas = Schemas(classes_by_name={ClassName("AnEnum", ""): existing}) prop, new_schemas = property_from_data( name=name, required=required, data=data, schemas=schemas, parent_name="parent", config=config @@ -475,9 +480,9 @@ def test_property_from_data_int_enum(self, enum_property_factory, config): name=name, required=required, values={"VALUE_1": 1, "VALUE_2": 2, "VALUE_3": 3}, - class_info=Class(name="ParentAnEnum", module_name="parent_an_enum"), + class_info=Class(name=ClassName("ParentAnEnum", ""), module_name=PythonIdentifier("parent_an_enum", "")), value_type=int, - default="ParentAnEnum.VALUE_3", + default=Value(python_code="ParentAnEnum.VALUE_3", raw_value=3), ) assert schemas != new_schemas, "Provided Schemas was mutated" assert new_schemas.classes_by_name == { @@ -520,12 +525,12 @@ def test_property_from_data_ref_enum_with_overridden_default(self, enum_property ) existing_enum = enum_property_factory( name="an_enum", - default="MyEnum.A", + default=Value(python_code="MyEnum.A", raw_value="A"), required=required, values={"A": "a", "B": "b"}, - class_info=Class(name="MyEnum", module_name="my_enum"), + class_info=Class(name=ClassName("MyEnum", ""), module_name=PythonIdentifier("my_enum", "")), ) - schemas = Schemas(classes_by_reference={"/components/schemas/MyEnum": existing_enum}) + schemas = Schemas(classes_by_reference={ReferencePath("/components/schemas/MyEnum"): existing_enum}) prop, new_schemas = property_from_data( name=name, required=required, data=data, schemas=schemas, parent_name="", config=config @@ -534,10 +539,10 @@ def test_property_from_data_ref_enum_with_overridden_default(self, enum_property assert prop == enum_property_factory( name="some_enum", - default="MyEnum.B", + default=Value(python_code="MyEnum.B", raw_value="b"), required=required, values={"A": "a", "B": "b"}, - class_info=Class(name="MyEnum", module_name="my_enum"), + class_info=Class(name=ClassName("MyEnum", ""), module_name=PythonIdentifier("my_enum", "")), ) assert schemas == new_schemas @@ -550,12 +555,12 @@ def test_property_from_data_ref_enum_with_invalid_default(self, enum_property_fa ) existing_enum = enum_property_factory( name="an_enum", - default="MyEnum.A", + default=Value(python_code="MyEnum.A", raw_value="A"), values={"A": "a", "B": "b"}, - class_info=Class(name="MyEnum", module_name="my_enum"), - python_name="an_enum", + class_info=Class(name=ClassName("MyEnum", ""), module_name=PythonIdentifier("my_enum", "")), + python_name=PythonIdentifier("an_enum", ""), ) - schemas = Schemas(classes_by_reference={"/components/schemas/MyEnum": existing_enum}) + schemas = Schemas(classes_by_reference={ReferencePath("/components/schemas/MyEnum"): existing_enum}) prop, new_schemas = property_from_data( name=name, required=False, data=data, schemas=schemas, parent_name="", config=config @@ -569,15 +574,15 @@ def test_property_from_data_ref_model(self, model_property_factory, config): name = "new_name" required = False - class_name = "MyModel" + class_name = ClassName("MyModel", "") data = oai.Reference.model_construct(ref=f"#/components/schemas/{class_name}") - class_info = Class(name=class_name, module_name="my_model") + class_info = Class(name=class_name, module_name=PythonIdentifier("my_model", "")) existing_model = model_property_factory( name="old_name", class_info=class_info, ) - schemas = Schemas(classes_by_reference={f"/components/schemas/{class_name}": existing_model}) + schemas = Schemas(classes_by_reference={ReferencePath(f"/components/schemas/{class_name}"): existing_model}) prop, new_schemas = property_from_data( name=name, required=required, data=data, schemas=schemas, parent_name="", config=config @@ -753,7 +758,9 @@ def test_no_format(self, string_property_factory, required, config): name=name, required=required, data=data, parent_name=None, config=config, schemas=Schemas() ) - assert p == string_property_factory(name=name, required=required, default="hello world") + assert p == string_property_factory( + name=name, required=required, default=StringProperty.convert_value("hello world") + ) def test_datetime_format(self, date_time_property_factory, config): from openapi_python_client.parser.properties import property_from_data @@ -767,7 +774,9 @@ def test_datetime_format(self, date_time_property_factory, config): ) assert p == date_time_property_factory( - name=name, required=required, default=Value(f"isoparse('{data.default}')") + name=name, + required=required, + default=Value(python_code=f"isoparse('{data.default}')", raw_value=data.default), ) def test_datetime_bad_default(self, config): @@ -797,7 +806,9 @@ def test_date_format(self, date_property_factory, config): ) assert p == date_property_factory( - name=name, required=required, default=Value(f"isoparse('{data.default}').date()") + name=name, + required=required, + default=Value(python_code=f"isoparse('{data.default}').date()", raw_value=data.default), ) def test_date_format_bad_default(self, config): diff --git a/tests/test_parser/test_properties/test_int.py b/tests/test_parser/test_properties/test_int.py index e50166e4a..7f9953761 100644 --- a/tests/test_parser/test_properties/test_int.py +++ b/tests/test_parser/test_properties/test_int.py @@ -1,6 +1,7 @@ from openapi_python_client.parser.errors import PropertyError from openapi_python_client.parser.properties import IntProperty from openapi_python_client.parser.properties.protocol import Value +from openapi_python_client.utils import PythonIdentifier def test_invalid_default(): @@ -17,9 +18,7 @@ def test_invalid_default(): def test_convert_from_string(): - val = IntProperty.convert_value("1") - assert isinstance(val, Value) - assert val == "1" + assert IntProperty.convert_value("1") == Value(python_code="1", raw_value="1") def test_invalid_type_default(): @@ -28,7 +27,7 @@ def test_invalid_type_default(): description=None, example=None, required=False, - python_name="not_a_float", + python_name=PythonIdentifier("not_a_float", ""), name="not_a_float", ) diff --git a/tests/test_parser/test_properties/test_merge_properties.py b/tests/test_parser/test_properties/test_merge_properties.py index 78f6bf89f..12ddb79fa 100644 --- a/tests/test_parser/test_properties/test_merge_properties.py +++ b/tests/test_parser/test_properties/test_merge_properties.py @@ -22,9 +22,9 @@ def test_merge_basic_attributes_same_type( model_property_factory, ): basic_props = [ - boolean_property_factory(default="True"), - int_property_factory(default="1"), - float_property_factory(default="1.5"), + boolean_property_factory(default=Value(python_code="True", raw_value="True")), + int_property_factory(default=Value("1", 1)), + float_property_factory(default=Value("1.5", 1.5)), string_property_factory(default=StringProperty.convert_value("x")), list_property_factory(), model_property_factory(), @@ -69,14 +69,14 @@ def test_incompatible_types( def test_merge_int_with_float(int_property_factory, float_property_factory): int_prop = int_property_factory(description="desc1") - float_prop = float_property_factory(default=2, description="desc2") + float_prop = float_property_factory(default=Value("2", 2), description="desc2") assert merge_properties(int_prop, float_prop) == ( - evolve(int_prop, default=Value("2"), description=float_prop.description) + evolve(int_prop, default=Value("2", 2), description=float_prop.description) ) - assert merge_properties(float_prop, int_prop) == evolve(int_prop, default=Value("2")) + assert merge_properties(float_prop, int_prop) == evolve(int_prop, default=Value("2", 2)) - float_prop_with_non_int_default = evolve(float_prop, default=Value("2.5")) + float_prop_with_non_int_default = evolve(float_prop, default=Value("2.5", 2.5)) error = merge_properties(int_prop, float_prop_with_non_int_default) assert isinstance(error, PropertyError), "Expected invalid default to error" assert error.detail == "Invalid int value: 2.5" @@ -92,9 +92,9 @@ def test_merge_with_any( ): original_desc = "description" props = [ - boolean_property_factory(default="True", description=original_desc), - int_property_factory(default="1", description=original_desc), - float_property_factory(default="1.5", description=original_desc), + boolean_property_factory(default=Value("True", "True"), description=original_desc), + int_property_factory(default=Value("1", "1"), description=original_desc), + float_property_factory(default=Value("1.5", "1.5"), description=original_desc), string_property_factory(default=StringProperty.convert_value("x"), description=original_desc), model_property_factory(description=original_desc), ] @@ -134,9 +134,9 @@ def test_merge_enums(enum_property_factory, config): def test_merge_string_with_string_enum(string_property_factory, enum_property_factory): values = {"A": "A", "B": "B"} - string_prop = string_property_factory(default="A", description="desc1", example="example1") + string_prop = string_property_factory(default=Value("A", "A"), description="desc1", example="example1") enum_prop = enum_property_factory( - default="test.B", + default=Value("test.B", "B"), description="desc2", example="example2", values=values, @@ -147,7 +147,7 @@ def test_merge_string_with_string_enum(string_property_factory, enum_property_fa assert merge_properties(enum_prop, string_prop) == evolve( enum_prop, required=True, - default="test.A", + default=Value("test.A", "A"), description=string_prop.description, example=string_prop.example, ) @@ -155,9 +155,9 @@ def test_merge_string_with_string_enum(string_property_factory, enum_property_fa def test_merge_int_with_int_enum(int_property_factory, enum_property_factory): values = {"VALUE_1": 1, "VALUE_2": 2} - int_prop = int_property_factory(default=1, description="desc1", example="example1") + int_prop = int_property_factory(default=Value("1", 1), description="desc1", example="example1") enum_prop = enum_property_factory( - default="test.VALUE_1", + default=Value("test.VALUE_1", 1), description="desc2", example="example2", values=values, @@ -203,7 +203,9 @@ def test_merge_string_with_formatted_string( string_property_factory, ): string_prop = string_property_factory(description="a plain string") - string_prop_with_invalid_default = string_property_factory(default="plain string value") + string_prop_with_invalid_default = string_property_factory( + default=StringProperty.convert_value("plain string value") + ) formatted_props = [ date_property_factory(description="a date"), date_time_property_factory(description="a datetime"), diff --git a/tests/test_parser/test_properties/test_none.py b/tests/test_parser/test_properties/test_none.py index 500d078e9..d61ca0136 100644 --- a/tests/test_parser/test_properties/test_none.py +++ b/tests/test_parser/test_properties/test_none.py @@ -1,6 +1,7 @@ from openapi_python_client.parser.errors import PropertyError from openapi_python_client.parser.properties import NoneProperty from openapi_python_client.parser.properties.protocol import Value +from openapi_python_client.utils import PythonIdentifier def test_default(): @@ -18,11 +19,11 @@ def test_default(): def test_dont_retest_values(): prop = NoneProperty.build( - default=Value("not None"), + default=Value("not None", "not None"), description=None, example=None, required=False, - python_name="not_none", + python_name=PythonIdentifier("not_none", ""), name="not_none", ) diff --git a/tests/test_parser/test_properties/test_protocol.py b/tests/test_parser/test_properties/test_protocol.py index a110f4ed9..1d4111750 100644 --- a/tests/test_parser/test_properties/test_protocol.py +++ b/tests/test_parser/test_properties/test_protocol.py @@ -1,5 +1,9 @@ +from __future__ import annotations + import pytest +from openapi_python_client.parser.properties.protocol import Value + def test_is_base_type(any_property_factory): assert any_property_factory().is_base_type is True @@ -30,16 +34,17 @@ def test_get_type_string(any_property_factory, mocker, required, no_optional, js @pytest.mark.parametrize( "default,required,expected", [ - (None, False, "test: Union[Unset, TestType] = UNSET"), - (None, True, "test: TestType"), - ("Test", False, "test: Union[Unset, TestType] = Test"), - ("Test", True, "test: TestType = Test"), + (None, False, "test: Union[Unset, Any] = UNSET"), + (None, True, "test: Any"), + ("Test", False, "test: Union[Unset, Any] = Test"), + ("Test", True, "test: Any = Test"), ], ) -def test_to_string(mocker, default, required, expected, any_property_factory): +def test_to_string(default: str | None, required: bool, expected: str, any_property_factory): name = "test" - mocker.patch("openapi_python_client.parser.properties.AnyProperty._type_string", "TestType") - p = any_property_factory(name=name, required=required, default=default) + p = any_property_factory( + name=name, required=required, default=Value(default, default) if default is not None else None + ) assert p.to_string() == expected diff --git a/tests/test_parser/test_properties/test_union.py b/tests/test_parser/test_properties/test_union.py index fa8d954d0..acbbd06d6 100644 --- a/tests/test_parser/test_properties/test_union.py +++ b/tests/test_parser/test_properties/test_union.py @@ -20,7 +20,7 @@ def test_property_from_data_union(union_property_factory, date_time_property_fac name=name, required=required, inner_properties=[ - string_property_factory(name=f"{name}_type_0", default=Value("'a'")), + string_property_factory(name=f"{name}_type_0", default=Value("'a'", "a")), date_time_property_factory(name=f"{name}_type_1"), ], ) diff --git a/tests/test_templates/test_property_templates/test_date_property/date_property_template.py b/tests/test_templates/test_property_templates/test_date_property/date_property_template.py.jinja similarity index 100% rename from tests/test_templates/test_property_templates/test_date_property/date_property_template.py rename to tests/test_templates/test_property_templates/test_date_property/date_property_template.py.jinja diff --git a/tests/test_templates/test_property_templates/test_date_property/test_date_property.py b/tests/test_templates/test_property_templates/test_date_property/test_date_property.py index 98999b910..89944994c 100644 --- a/tests/test_templates/test_property_templates/test_date_property/test_date_property.py +++ b/tests/test_templates/test_property_templates/test_date_property/test_date_property.py @@ -27,7 +27,7 @@ def test_required(): lstrip_blocks=True ) - template = env.get_template("date_property_template.py") + template = env.get_template("date_property_template.py.jinja") content = template.render(property=prop) expected = here / "required_not_null.py" assert content == expected.read_text() diff --git a/tests/test_templates/test_property_templates/test_datetime_property/datetime_property_template.py b/tests/test_templates/test_property_templates/test_datetime_property/datetime_property_template.py.jinja similarity index 100% rename from tests/test_templates/test_property_templates/test_datetime_property/datetime_property_template.py rename to tests/test_templates/test_property_templates/test_datetime_property/datetime_property_template.py.jinja diff --git a/tests/test_templates/test_property_templates/test_datetime_property/test_datetime_property.py b/tests/test_templates/test_property_templates/test_datetime_property/test_datetime_property.py index 83d91ff3a..bb9a3bd10 100644 --- a/tests/test_templates/test_property_templates/test_datetime_property/test_datetime_property.py +++ b/tests/test_templates/test_property_templates/test_datetime_property/test_datetime_property.py @@ -27,7 +27,7 @@ def test_required(): lstrip_blocks=True ) - template = env.get_template("datetime_property_template.py") + template = env.get_template("datetime_property_template.py.jinja") content = template.render(property=prop) expected = here / "required_not_null.py" assert content == expected.read_text()