From 85ef4d7898f4761d40750f943e989fcd7150f583 Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 13 Aug 2024 14:16:04 -0700 Subject: [PATCH 1/2] fix: avoid conflict between uppercase/lowercase enum values --- .../parser/properties/enum_property.py | 24 +++++++++++++++++-- .../test_parser/test_properties/test_init.py | 13 ++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/openapi_python_client/parser/properties/enum_property.py b/openapi_python_client/parser/properties/enum_property.py index 0f0db0d61..8b2e5d377 100644 --- a/openapi_python_client/parser/properties/enum_property.py +++ b/openapi_python_client/parser/properties/enum_property.py @@ -187,6 +187,23 @@ def values_from_list(values: list[str] | list[int]) -> dict[str, ValueType]: """Convert a list of values into dict of {name: value}, where value can sometimes be None""" output: dict[str, ValueType] = {} + # We normally would like to make nice-looking Python constant names for enum values, so that "myValue" + # becomes MY_VALUE, etc. However, that won't work if an enum has two values that differ only by case + # (which is allowed in OpenAPI). + use_case_sensitive_names = False + for i, value1 in enumerate(values): + if use_case_sensitive_names: + break + for j, value2 in enumerate(values): + if ( + i != j + and isinstance(value1, str) + and isinstance(value2, str) + and value1.upper() == value2.upper() + ): + use_case_sensitive_names = True + break + for i, value in enumerate(values): value = cast(Union[str, int], value) if isinstance(value, int): @@ -196,11 +213,14 @@ def values_from_list(values: list[str] | list[int]) -> dict[str, ValueType]: output[f"VALUE_{value}"] = value continue if value and value[0].isalpha(): - key = value.upper() + key = value else: key = f"VALUE_{i}" if key in output: raise ValueError(f"Duplicate key {key} in Enum") - sanitized_key = utils.snake_case(key).upper() + if use_case_sensitive_names: + sanitized_key = utils.sanitize(key.replace(" ", "_")) + else: + sanitized_key = utils.snake_case(key.upper()).upper() output[sanitized_key] = utils.remove_string_escapes(value) return output diff --git a/tests/test_parser/test_properties/test_init.py b/tests/test_parser/test_properties/test_init.py index 3290dcd39..466db8c06 100644 --- a/tests/test_parser/test_properties/test_init.py +++ b/tests/test_parser/test_properties/test_init.py @@ -372,6 +372,19 @@ def test_values_from_list_duplicate(self): with pytest.raises(ValueError): EnumProperty.values_from_list(data) + def test_values_from_list_with_case_sensitive_names(self): + from openapi_python_client.parser.properties import EnumProperty + + data = ["abc", "123", "ABC", "thing with spaces"] + + result = EnumProperty.values_from_list(data) + + assert result == { + "abc": "abc", + "VALUE_1": "123", + "ABC": "ABC", + "thing_with_spaces": "thing with spaces", + } class TestPropertyFromData: def test_property_from_data_str_enum(self, enum_property_factory, config): From 6ab33014c303b75c0c9f9573451815164627756d Mon Sep 17 00:00:00 2001 From: Eli Bishop Date: Tue, 13 Aug 2024 14:53:07 -0700 Subject: [PATCH 2/2] also don't fail if two values are exactly identical --- .../parser/properties/enum_property.py | 23 ++++++++++++++----- .../test_parser/test_properties/test_init.py | 11 ++++++--- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/openapi_python_client/parser/properties/enum_property.py b/openapi_python_client/parser/properties/enum_property.py index 8b2e5d377..49994e3da 100644 --- a/openapi_python_client/parser/properties/enum_property.py +++ b/openapi_python_client/parser/properties/enum_property.py @@ -187,14 +187,25 @@ def values_from_list(values: list[str] | list[int]) -> dict[str, ValueType]: """Convert a list of values into dict of {name: value}, where value can sometimes be None""" output: dict[str, ValueType] = {} - # We normally would like to make nice-looking Python constant names for enum values, so that "myValue" - # becomes MY_VALUE, etc. However, that won't work if an enum has two values that differ only by case - # (which is allowed in OpenAPI). + # Strip out any duplicate values, while preserving the original order of the list. + # OpenAPI doesn't specifically disallow listing the exact same enum value twice; that + # would have no effect on validation behavior. The problem with it is just that we + # can't define two identically-named constants in the generated code. But there's no + # reason for us to do so, anyway; a single constant will suffice. So, just drop any + # duplicate value. + unique_values = [] + for value in values: + if value not in unique_values: + unique_values.append(value) + + # We normally would like to make nice-looking Python constant names for enum values, + # so that "myValue" becomes MY_VALUE, etc. However, that won't work if an enum has two + # values that differ only by case (which is allowed in OpenAPI). use_case_sensitive_names = False - for i, value1 in enumerate(values): + for i, value1 in enumerate(unique_values): if use_case_sensitive_names: break - for j, value2 in enumerate(values): + for j, value2 in enumerate(unique_values): if ( i != j and isinstance(value1, str) @@ -204,7 +215,7 @@ def values_from_list(values: list[str] | list[int]) -> dict[str, ValueType]: use_case_sensitive_names = True break - for i, value in enumerate(values): + for i, value in enumerate(unique_values): value = cast(Union[str, int], value) if isinstance(value, int): if value < 0: diff --git a/tests/test_parser/test_properties/test_init.py b/tests/test_parser/test_properties/test_init.py index 466db8c06..3d168eca3 100644 --- a/tests/test_parser/test_properties/test_init.py +++ b/tests/test_parser/test_properties/test_init.py @@ -364,13 +364,18 @@ def test_values_from_list(self): "VALUE_7": "", } - def test_values_from_list_duplicate(self): + def test_values_from_list_duplicate_is_skipped(self): from openapi_python_client.parser.properties import EnumProperty data = ["abc", "123", "a23", "abc"] - with pytest.raises(ValueError): - EnumProperty.values_from_list(data) + result = EnumProperty.values_from_list(data) + + assert result == { + "ABC": "abc", + "VALUE_1": "123", + "A23": "a23", + } def test_values_from_list_with_case_sensitive_names(self): from openapi_python_client.parser.properties import EnumProperty