88
99import re
1010from collections import defaultdict
11- from typing import TYPE_CHECKING , Any , ClassVar , Literal , NamedTuple , Optional
11+ from typing import TYPE_CHECKING , Any , ClassVar , Optional
1212
13- from pydantic import Field , ValidationError , field_validator , model_validator
13+ from pydantic import ValidationError , field_validator , model_validator
1414
1515from datamodel_code_generator import Error
16- from datamodel_code_generator .enums import TargetPydanticVersion
1716from datamodel_code_generator .imports import IMPORT_ANY , IMPORT_DICT , Import
1817from datamodel_code_generator .model import _rebuild_model_with_datamodel_namespace
1918from datamodel_code_generator .model .base import (
2322 DataModelFieldBase ,
2423)
2524from datamodel_code_generator .model .imports import IMPORT_CLASSVAR
25+ from datamodel_code_generator .model .pydantic_base import BaseModelBase
2626from datamodel_code_generator .model .pydantic_base import (
27- BaseModelBase ,
27+ DataModelField as _PydanticBaseDataModelField ,
2828)
2929from datamodel_code_generator .model .pydantic_base import (
30- Constraints as _Constraints ,
30+ PatternConstraints as _Constraints ,
3131)
32- from datamodel_code_generator .model .pydantic_base import (
33- DataModelField as _PydanticBaseDataModelField ,
32+ from datamodel_code_generator .model .pydantic_v2 ._config import (
33+ ConfigAttribute ,
34+ ConfigDict ,
35+ build_base_config_parameters ,
3436)
3537from datamodel_code_generator .model .pydantic_v2 .imports import (
3638 IMPORT_ALIAS_CHOICES ,
@@ -68,10 +70,6 @@ def __repr__(self) -> str:
6870class Constraints (_Constraints ):
6971 """Pydantic v2 field constraints with pattern support."""
7072
71- # To override existing pattern alias
72- regex : Optional [str ] = Field (None , alias = "regex" ) # noqa: UP045
73- pattern : Optional [str ] = Field (None , alias = "pattern" ) # noqa: UP045
74-
7573 @model_validator (mode = "before" ) # ty: ignore
7674 def validate_min_max_items (cls , values : Any ) -> dict [str , Any ]: # noqa: N805
7775 """Validate and convert minItems/maxItems to minLength/maxLength."""
@@ -273,14 +271,6 @@ def imports(self) -> tuple[Import, ...]:
273271 return base_imports
274272
275273
276- class ConfigAttribute (NamedTuple ):
277- """Configuration attribute mapping for ConfigDict conversion."""
278-
279- from_ : str
280- to : str
281- invert : bool
282-
283-
284274class BaseModel (BaseModelBase ):
285275 """Pydantic v2 BaseModel with ConfigDict and pattern-based regex_engine support."""
286276
@@ -290,6 +280,7 @@ class BaseModel(BaseModelBase):
290280 BASE_CLASS_ALIAS : ClassVar [str ] = "_BaseModel"
291281 SUPPORTS_DISCRIMINATOR : ClassVar [bool ] = True
292282 SUPPORTS_FIELD_RENAMING : ClassVar [bool ] = True
283+ SUPPORTS_CONFIG_EXTRA : ClassVar [bool ] = True
293284 TYPED_EXTRA_FIELD_NAME : ClassVar [str ] = "__pydantic_extra__"
294285 # In Pydantic 2.11+, populate_by_name is deprecated in favor of validate_by_name + validate_by_alias
295286 # Default to V2 compatible (populate_by_name) unless target_pydantic_version is specified
@@ -356,30 +347,20 @@ def __init__( # noqa: PLR0913
356347 keyword_only = keyword_only ,
357348 treat_dot_as_module = treat_dot_as_module ,
358349 )
359- config_parameters : dict [str , Any ] = {}
360-
361- extra = self ._get_config_extra ()
362- if extra :
363- config_parameters ["extra" ] = extra
364-
365- # Select CONFIG_ATTRIBUTES based on target_pydantic_version
366- config_attributes = self ._get_config_attributes ()
367- for from_ , to , invert in config_attributes :
368- if from_ in self .extra_template_data :
369- config_parameters [to ] = (
370- not self .extra_template_data [from_ ] if invert else self .extra_template_data [from_ ]
371- )
372- for data_type in self .all_data_types :
373- if data_type .is_custom_type : # pragma: no cover
374- config_parameters ["arbitrary_types_allowed" ] = True
375- break
350+ config_parameters = build_base_config_parameters (
351+ extra_template_data = self .extra_template_data ,
352+ all_data_types = self .all_data_types ,
353+ config_attributes_v2 = self ._CONFIG_ATTRIBUTES_V2 ,
354+ config_attributes_v2_11 = self ._CONFIG_ATTRIBUTES_V2_11 ,
355+ include_extra = self .SUPPORTS_CONFIG_EXTRA ,
356+ )
376357
377358 if self ._has_lookaround_pattern ():
378359 config_parameters ["regex_engine" ] = '"python-re"'
379360
380361 if isinstance (self .extra_template_data .get ("config" ), dict ):
381362 for key , value in self .extra_template_data ["config" ].items ():
382- config_parameters [key ] = value # noqa: PERF403
363+ config_parameters [key ] = value
383364
384365 # Handle json_schema_extra from schema extensions (x-* fields)
385366 model_extras = self .extra_template_data .get ("model_extras" )
@@ -388,47 +369,11 @@ def __init__( # noqa: PLR0913
388369 config_parameters ["json_schema_extra" ] = {** existing , ** model_extras }
389370
390371 if config_parameters :
391- from datamodel_code_generator .model .pydantic_v2 import ConfigDict # noqa: PLC0415
392-
393372 self .extra_template_data ["config" ] = ConfigDict .model_validate (config_parameters ) # ty: ignore
394373 self ._additional_imports .append (IMPORT_CONFIG_DICT )
395374
396375 self ._process_validators ()
397376
398- def _get_config_extra (self ) -> Literal ["'allow'" , "'forbid'" , "'ignore'" ] | None :
399- additional_properties = self .extra_template_data .get ("additionalProperties" )
400- unevaluated_properties = self .extra_template_data .get ("unevaluatedProperties" )
401- allow_extra_fields = self .extra_template_data .get ("allow_extra_fields" )
402- extra_fields = self .extra_template_data .get ("extra_fields" )
403-
404- config_extra = None
405- if allow_extra_fields or extra_fields == "allow" :
406- config_extra = "'allow'"
407- elif extra_fields == "forbid" :
408- config_extra = "'forbid'"
409- elif extra_fields == "ignore" :
410- config_extra = "'ignore'"
411- elif additional_properties is True :
412- config_extra = "'allow'"
413- elif additional_properties is False :
414- config_extra = "'forbid'"
415- elif unevaluated_properties is True :
416- config_extra = "'allow'"
417- elif unevaluated_properties is False :
418- config_extra = "'forbid'"
419- return config_extra
420-
421- def _get_config_attributes (self ) -> list [ConfigAttribute ]:
422- """Get config attributes based on target Pydantic version.
423-
424- If target_pydantic_version is V2_11, use validate_by_name.
425- Otherwise (V2 or not specified), use populate_by_name for compatibility.
426- """
427- target_version = self .extra_template_data .get ("target_pydantic_version" )
428- if target_version == TargetPydanticVersion .V2_11 :
429- return self ._CONFIG_ATTRIBUTES_V2_11
430- return self ._CONFIG_ATTRIBUTES_V2
431-
432377 def _has_lookaround_pattern (self ) -> bool :
433378 """Check if any field has a regex pattern with lookaround assertions."""
434379 lookaround_regex = re .compile (r"\(\?<?[=!]" )
0 commit comments