1515from datamodel_code_generator import cached_path_exists
1616from datamodel_code_generator .imports import Import
1717from datamodel_code_generator .model import (
18+ UNDEFINED ,
1819 ConstraintsBase ,
1920 DataModel ,
2021 DataModelFieldBase ,
2122 _rebuild_model_with_datamodel_namespace ,
2223)
23- from datamodel_code_generator .model .base import UNDEFINED , repr_set_sorted
24- from datamodel_code_generator .types import UnionIntFloat , chain_as_tuple
24+ from datamodel_code_generator .python_literal import represent_python_value
25+ from datamodel_code_generator .types import (
26+ UnionIntFloat ,
27+ chain_as_tuple ,
28+ merge_normalized_constraint ,
29+ normalize_integer_constraint ,
30+ )
2531
2632# Defined here instead of importing from pydantic_v2.imports to avoid circular import
2733# (pydantic_base -> pydantic_v2.imports -> pydantic_v2/__init__ -> pydantic_v2.base_model -> pydantic_base)
@@ -68,6 +74,7 @@ class DataModelField(DataModelFieldBase):
6874 "regex" ,
6975 }
7076 _COMPARE_EXPRESSIONS : ClassVar [set [str ]] = {"gt" , "ge" , "lt" , "le" }
77+ _INTEGER_CONSTRAINTS : ClassVar [set [str ]] = _COMPARE_EXPRESSIONS | {"multiple_of" }
7178 constraints : Optional [Constraints ] = None # noqa: UP045
7279 _PARSE_METHOD : ClassVar [str ] = "model_validate"
7380
@@ -138,26 +145,43 @@ def _has_field_statement(self) -> bool:
138145 return True
139146 return bool (self .nullable and self .required and not self .use_default_with_required )
140147
141- def _has_float_data_type (self ) -> bool :
142- """Check whether any nested data type is float-like ."""
148+ def _has_numeric_data_type (self , type_name : str , strict_import_part : str ) -> bool :
149+ """Return whether any field data type is the given builtin or strict numeric type ."""
143150 return any (
144- data_type .type == "float"
145- or (data_type .strict and data_type .import_ and "Float" in data_type .import_ .import_ )
151+ data_type .type == type_name
152+ or (data_type .strict and data_type .import_ and strict_import_part in data_type .import_ .import_ )
146153 for data_type in self .data_type .all_data_types
147154 )
148155
149- def _get_strict_field_constraint_value (self , constraint : str , value : Any , * , is_float_type : bool ) -> Any :
150- if value is None or constraint not in self ._COMPARE_EXPRESSIONS :
151- return value
156+ def _get_strict_field_constraint (
157+ self , constraint : str , value : Any , * , is_float_type : bool , is_int_type : bool
158+ ) -> tuple [str , Any ] | None :
159+ if value is None or constraint not in self ._INTEGER_CONSTRAINTS :
160+ return constraint , value
152161 if is_float_type :
153- return float (value )
154- str_value = str (value )
155- if "e" in str_value .lower (): # pragma: no cover
156- # Scientific notation like 1e-08 - keep as float
157- return float (value )
158- if isinstance (value , int ) and not isinstance (value , bool ):
159- return value
160- return int (value )
162+ return constraint , float (value )
163+ if is_int_type :
164+ return normalize_integer_constraint (constraint , value )
165+ if isinstance (value , float ) and value .is_integer ():
166+ return constraint , int (value )
167+ return constraint , value
168+
169+ def _get_normalized_constraint_data (self ) -> dict [str , Any ]:
170+ """Build constraint data with integer-safe values, merging colliding bounds."""
171+ assert self .constraints is not None
172+ dumped = self .constraints ._exclude_unset_dump # noqa: SLF001
173+ has_integer_constraints = bool (self ._INTEGER_CONSTRAINTS & dumped .keys ())
174+ is_float_type = has_integer_constraints and self ._has_numeric_data_type ("float" , "Float" )
175+ is_int_type = has_integer_constraints and not is_float_type and self ._has_numeric_data_type ("int" , "Int" )
176+ constraint_data : dict [str , Any ] = {}
177+ for k , v in dumped .items ():
178+ if (
179+ normalized := self ._get_strict_field_constraint (
180+ k , v , is_float_type = is_float_type , is_int_type = is_int_type
181+ )
182+ ) is not None :
183+ merge_normalized_constraint (constraint_data , normalized [0 ], normalized [1 ])
184+ return constraint_data
161185
162186 def _get_default_factory_for_optional_nested_model (self ) -> str | None :
163187 """Get default_factory for optional nested Pydantic model fields.
@@ -196,12 +220,7 @@ def _get_field_data_and_default_factory(self) -> tuple[dict[str, Any], Any]:
196220 if any (d .import_ == IMPORT_ANYURL for d in self .data_type .all_data_types ):
197221 constraint_data : dict [str , Any ] = {}
198222 else :
199- dumped = self .constraints ._exclude_unset_dump # noqa: SLF001
200- is_float_type = bool (self ._COMPARE_EXPRESSIONS & dumped .keys ()) and self ._has_float_data_type ()
201- constraint_data = {
202- k : self ._get_strict_field_constraint_value (k , v , is_float_type = is_float_type )
203- for k , v in dumped .items ()
204- }
223+ constraint_data = self ._get_normalized_constraint_data ()
205224 data = {** data , ** constraint_data }
206225
207226 if self .use_field_description :
@@ -239,7 +258,7 @@ def __str__(self) -> str:
239258 """Return Field() call with all constraints and metadata."""
240259 data , default_factory = self ._get_field_data_and_default_factory ()
241260
242- field_arguments = sorted (f"{ k } ={ v !r } " for k , v in data .items () if v is not None )
261+ field_arguments = sorted (f"{ k } ={ represent_python_value ( v ) } " for k , v in data .items () if v is not None )
243262
244263 if not field_arguments and not default_factory :
245264 if self .nullable and self .required and not self .use_default_with_required :
@@ -259,13 +278,13 @@ def __str__(self) -> str:
259278 ):
260279 field_arguments = ["..." , * field_arguments ]
261280 elif not default_factory :
262- default_repr = repr_set_sorted ( self . default ) if isinstance ( self . default , set ) else repr (self .default )
281+ default_repr = represent_python_value (self .default )
263282 field_arguments = [default_repr , * field_arguments ]
264283
265284 if self .is_class_var :
266285 if self .default is UNDEFINED : # pragma: no cover
267286 return ""
268- return repr_set_sorted ( self . default ) if isinstance ( self . default , set ) else repr (self .default )
287+ return represent_python_value (self .default )
269288
270289 return f"Field({ ', ' .join (field_arguments )} )"
271290
0 commit comments