Skip to content

fix: avoid conflict between uppercase/lowercase enum values, and ignore duplicate values #1095

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 34 additions & 3 deletions openapi_python_client/parser/properties/enum_property.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,35 @@ 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] = {}

for i, value in enumerate(values):
# 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(unique_values):
if use_case_sensitive_names:
break
for j, value2 in enumerate(unique_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(unique_values):
value = cast(Union[str, int], value)
if isinstance(value, int):
if value < 0:
Expand All @@ -196,11 +224,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
24 changes: 21 additions & 3 deletions tests/test_parser/test_properties/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,14 +364,32 @@ 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

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):
Expand Down