Skip to content

Commit 5aa57d2

Browse files
Fix enum member names conflicting with builtin type methods (#2660)
* Fix enum member name conflicts with built-in str methods by adding underscore suffixes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 33b9347 commit 5aa57d2

4 files changed

Lines changed: 127 additions & 1 deletion

File tree

src/datamodel_code_generator/reference.py

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,92 @@ def _validate_field_name(cls, field_name: str) -> bool:
358358

359359

360360
class EnumFieldNameResolver(FieldNameResolver):
361-
"""Field name resolver for enum members with special handling for 'mro'."""
361+
"""Field name resolver for enum members with special handling for reserved names.
362+
363+
When using --use-subclass-enum, enums inherit from types like str or int.
364+
Member names that conflict with methods of these types cause type checker errors.
365+
This class detects and handles such conflicts by adding underscore suffixes.
366+
367+
The _BUILTIN_TYPE_ATTRIBUTES set is intentionally static (not using hasattr)
368+
to avoid runtime Python version differences affecting code generation.
369+
Based on Python 3.8-3.14 method names (union of all versions for safety).
370+
Note: 'mro' is handled explicitly in get_valid_name for backward compatibility.
371+
"""
372+
373+
_BUILTIN_TYPE_ATTRIBUTES: ClassVar[frozenset[str]] = frozenset({
374+
"as_integer_ratio",
375+
"bit_count",
376+
"bit_length",
377+
"capitalize",
378+
"casefold",
379+
"center",
380+
"conjugate",
381+
"count",
382+
"decode",
383+
"denominator",
384+
"encode",
385+
"endswith",
386+
"expandtabs",
387+
"find",
388+
"format",
389+
"format_map",
390+
"from_bytes",
391+
"from_number",
392+
"fromhex",
393+
"hex",
394+
"imag",
395+
"index",
396+
"isalnum",
397+
"isalpha",
398+
"isascii",
399+
"isdecimal",
400+
"isdigit",
401+
"isidentifier",
402+
"islower",
403+
"isnumeric",
404+
"isprintable",
405+
"isspace",
406+
"istitle",
407+
"isupper",
408+
"is_integer",
409+
"join",
410+
"ljust",
411+
"lower",
412+
"lstrip",
413+
"maketrans",
414+
"numerator",
415+
"partition",
416+
"real",
417+
"removeprefix",
418+
"removesuffix",
419+
"replace",
420+
"rfind",
421+
"rindex",
422+
"rjust",
423+
"rpartition",
424+
"rsplit",
425+
"rstrip",
426+
"split",
427+
"splitlines",
428+
"startswith",
429+
"strip",
430+
"swapcase",
431+
"title",
432+
"to_bytes",
433+
"translate",
434+
"upper",
435+
"zfill",
436+
})
437+
438+
@classmethod
439+
def _validate_field_name(cls, field_name: str) -> bool:
440+
"""Check field name doesn't conflict with subclass enum base type attributes.
441+
442+
When using --use-subclass-enum, enums inherit from types like str or int.
443+
Member names that conflict with methods of these types (e.g., 'count' for str)
444+
cause type checker errors. This method detects such conflicts.
445+
"""
446+
return field_name not in cls._BUILTIN_TYPE_ATTRIBUTES
362447

363448
def get_valid_name(
364449
self,
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# generated by datamodel-codegen:
2+
# filename: enum_builtin_conflict.yaml
3+
# timestamp: 1985-10-26T08:21:00+00:00
4+
5+
from __future__ import annotations
6+
7+
from enum import Enum
8+
9+
10+
class StringMethodEnum(str, Enum):
11+
count_ = 'count'
12+
index_ = 'index'
13+
format_ = 'format'
14+
normal_value = 'normal_value'
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
openapi: "3.0.0"
2+
info:
3+
title: Test API
4+
version: "1.0.0"
5+
paths: {}
6+
components:
7+
schemas:
8+
StringMethodEnum:
9+
type: string
10+
enum:
11+
- count
12+
- index
13+
- format
14+
- normal_value

tests/main/openapi/test_main_openapi.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3569,3 +3569,16 @@ def test_main_openapi_x_enum_names(output_file: Path) -> None:
35693569
assert_func=assert_file_content,
35703570
expected_file="x_enum_names.py",
35713571
)
3572+
3573+
3574+
def test_main_enum_builtin_conflict(output_file: Path) -> None:
3575+
"""Test enum member names that conflict with str methods get underscore suffix."""
3576+
with freeze_time(TIMESTAMP):
3577+
run_main_and_assert(
3578+
input_path=OPEN_API_DATA_PATH / "enum_builtin_conflict.yaml",
3579+
output_path=output_file,
3580+
input_file_type="openapi",
3581+
assert_func=assert_file_content,
3582+
expected_file="enum_builtin_conflict.py",
3583+
extra_args=["--use-subclass-enum"],
3584+
)

0 commit comments

Comments
 (0)