3636
3737from datamodel_code_generator import Error , NamingStrategy
3838from datamodel_code_generator .enums import ClassNameAffixScope
39+ from datamodel_code_generator .format import PythonVersion
3940from datamodel_code_generator .util import ConfigDict , camel_to_snake , is_pydantic_v2 , model_validator
4041
4142if TYPE_CHECKING :
@@ -221,6 +222,27 @@ def context_variable(setter: Callable[[T], None], current_value: T, new_value: T
221222 setter (previous_value )
222223
223224
225+ _BUILTIN_TYPE_ATTRIBUTES : frozenset [str ] = frozenset (
226+ name for cls in (str , int , float , bytes ) for name in dir (cls ) if not name .startswith ("_" )
227+ )
228+
229+ _BUILTIN_TYPE_ATTRIBUTES_INTRODUCED_IN : dict [PythonVersion , frozenset [str ]] = {
230+ PythonVersion .PY_314 : frozenset ({"from_number" }),
231+ }
232+
233+
234+ def _get_builtin_type_attributes_for_target (target : PythonVersion ) -> frozenset [str ]:
235+ """Get builtin type attributes adjusted for the target Python version."""
236+ attrs = set (_BUILTIN_TYPE_ATTRIBUTES )
237+ target_key = target .version_key
238+ for ver , names in _BUILTIN_TYPE_ATTRIBUTES_INTRODUCED_IN .items ():
239+ if target_key >= ver .version_key :
240+ attrs .update (names )
241+ else :
242+ attrs .difference_update (names )
243+ return frozenset (attrs )
244+
245+
224246class FieldNameResolver :
225247 """Converts schema field names to valid Python identifiers."""
226248
@@ -234,6 +256,8 @@ def __init__( # noqa: PLR0913, PLR0917
234256 remove_special_field_name_prefix : bool = False , # noqa: FBT001, FBT002
235257 capitalise_enum_members : bool = False , # noqa: FBT001, FBT002
236258 no_alias : bool = False , # noqa: FBT001, FBT002
259+ use_subclass_enum : bool = False , # noqa: FBT001, FBT002
260+ target_python_version : PythonVersion | None = None ,
237261 ) -> None :
238262 """Initialize field name resolver with transformation options."""
239263 self .aliases : Mapping [str , str | list [str ]] = {} if aliases is None else {** aliases }
@@ -246,9 +270,10 @@ def __init__( # noqa: PLR0913, PLR0917
246270 self .remove_special_field_name_prefix : bool = remove_special_field_name_prefix
247271 self .capitalise_enum_members : bool = capitalise_enum_members
248272 self .no_alias = no_alias
273+ self .use_subclass_enum : bool = use_subclass_enum
274+ self .target_python_version = target_python_version
249275
250- @classmethod
251- def _validate_field_name (cls , field_name : str ) -> bool : # noqa: ARG003
276+ def _validate_field_name (self , field_name : str ) -> bool : # noqa: ARG002, PLR6301
252277 """Check if a field name is valid. Subclasses may override."""
253278 return True
254279
@@ -283,7 +308,8 @@ def get_valid_name( # noqa: PLR0912
283308 if self .capitalise_enum_members or (self .snake_case_field and not ignore_snake_case_field ):
284309 name = camel_to_snake (name )
285310 count = 1
286- if iskeyword (name ) or not self ._validate_field_name (name ):
311+ validated_name = name .upper () if self .capitalise_enum_members else name
312+ if iskeyword (validated_name ) or not self ._validate_field_name (validated_name ):
287313 name += "_"
288314 if upper_camel :
289315 new_name = snake_to_upper_camel (name )
@@ -357,100 +383,28 @@ def get_valid_field_name_and_alias(
357383class PydanticFieldNameResolver (FieldNameResolver ):
358384 """Field name resolver that avoids Pydantic reserved names."""
359385
360- @classmethod
361- def _validate_field_name (cls , field_name : str ) -> bool :
386+ def _validate_field_name (self , field_name : str ) -> bool : # noqa: PLR6301
362387 """Check field name doesn't conflict with BaseModel attributes."""
363388 # TODO: Support Pydantic V2
364389 return not hasattr (BaseModel , field_name )
365390
366391
367392class EnumFieldNameResolver (FieldNameResolver ):
368- """Field name resolver for enum members with special handling for reserved names.
369-
370- When using --use-subclass-enum, enums inherit from types like str or int.
371- Member names that conflict with methods of these types cause type checker errors.
372- This class detects and handles such conflicts by adding underscore suffixes.
373-
374- The _BUILTIN_TYPE_ATTRIBUTES set is intentionally static (not using hasattr)
375- to avoid runtime Python version differences affecting code generation.
376- Based on Python 3.8-3.14 method names (union of all versions for safety).
377- Note: 'mro' is handled explicitly in get_valid_name for backward compatibility.
378- """
379-
380- _BUILTIN_TYPE_ATTRIBUTES : ClassVar [frozenset [str ]] = frozenset ({
381- "as_integer_ratio" ,
382- "bit_count" ,
383- "bit_length" ,
384- "capitalize" ,
385- "casefold" ,
386- "center" ,
387- "conjugate" ,
388- "count" ,
389- "decode" ,
390- "denominator" ,
391- "encode" ,
392- "endswith" ,
393- "expandtabs" ,
394- "find" ,
395- "format" ,
396- "format_map" ,
397- "from_bytes" ,
398- "from_number" ,
399- "fromhex" ,
400- "hex" ,
401- "imag" ,
402- "index" ,
403- "isalnum" ,
404- "isalpha" ,
405- "isascii" ,
406- "isdecimal" ,
407- "isdigit" ,
408- "isidentifier" ,
409- "islower" ,
410- "isnumeric" ,
411- "isprintable" ,
412- "isspace" ,
413- "istitle" ,
414- "isupper" ,
415- "is_integer" ,
416- "join" ,
417- "ljust" ,
418- "lower" ,
419- "lstrip" ,
420- "maketrans" ,
421- "numerator" ,
422- "partition" ,
423- "real" ,
424- "removeprefix" ,
425- "removesuffix" ,
426- "replace" ,
427- "rfind" ,
428- "rindex" ,
429- "rjust" ,
430- "rpartition" ,
431- "rsplit" ,
432- "rstrip" ,
433- "split" ,
434- "splitlines" ,
435- "startswith" ,
436- "strip" ,
437- "swapcase" ,
438- "title" ,
439- "to_bytes" ,
440- "translate" ,
441- "upper" ,
442- "zfill" ,
443- })
444-
445- @classmethod
446- def _validate_field_name (cls , field_name : str ) -> bool :
447- """Check field name doesn't conflict with subclass enum base type attributes.
393+ """Field name resolver for enum members with special handling for reserved names."""
394+
395+ def __init__ (self , ** kwargs : Any ) -> None :
396+ """Initialize with version-aware builtin type attributes."""
397+ super ().__init__ (** kwargs )
398+ target = self .target_python_version
399+ self ._builtin_type_attributes = (
400+ _get_builtin_type_attributes_for_target (target ) if target is not None else _BUILTIN_TYPE_ATTRIBUTES
401+ )
448402
449- When using --use-subclass-enum, enums inherit from types like str or int.
450- Member names that conflict with methods of these types (e.g., 'count' for str)
451- cause type checker errors. This method detects such conflicts.
452- """
453- return field_name not in cls . _BUILTIN_TYPE_ATTRIBUTES
403+ def _validate_field_name ( self , field_name : str ) -> bool :
404+ """Check field name doesn't conflict with subclass enum base type attributes."""
405+ if not self . use_subclass_enum :
406+ return True
407+ return field_name not in self . _builtin_type_attributes
454408
455409 def get_valid_name (
456410 self ,
@@ -538,6 +492,8 @@ def __init__( # noqa: PLR0913, PLR0917
538492 remove_special_field_name_prefix : bool = False , # noqa: FBT001, FBT002
539493 capitalise_enum_members : bool = False , # noqa: FBT001, FBT002
540494 no_alias : bool = False , # noqa: FBT001, FBT002
495+ use_subclass_enum : bool = False , # noqa: FBT001, FBT002
496+ target_python_version : PythonVersion | None = None ,
541497 remove_suffix_number : bool = False , # noqa: FBT001, FBT002
542498 parent_scoped_naming : bool = False , # noqa: FBT001, FBT002
543499 treat_dot_as_module : bool | None = None , # noqa: FBT001
@@ -575,6 +531,8 @@ def __init__( # noqa: PLR0913, PLR0917
575531 remove_special_field_name_prefix = remove_special_field_name_prefix ,
576532 capitalise_enum_members = capitalise_enum_members if k == ModelType .ENUM else False ,
577533 no_alias = no_alias ,
534+ use_subclass_enum = use_subclass_enum if k == ModelType .ENUM else False ,
535+ target_python_version = target_python_version if k == ModelType .ENUM else None ,
578536 )
579537 for k , v in merged_field_name_resolver_classes .items ()
580538 }
0 commit comments