1- """Utility functions and Pydantic version compatibility helpers.
2-
3- Provides Pydantic version detection (PYDANTIC_V2), YAML/TOML loading,
4- and version-compatible decorators (model_validator, field_validator).
5- """
1+ """Utility functions for YAML/TOML loading and lazy BaseModel access."""
62
73from __future__ import annotations
84
95import re
106import warnings
117from functools import lru_cache
12- from typing import TYPE_CHECKING , Any , Literal , TypeVar , overload
8+ from typing import TYPE_CHECKING , Any
139
1410if TYPE_CHECKING :
1511 from collections .abc import Callable
1612 from pathlib import Path
1713
18- from pydantic import BaseModel as _BaseModel
19-
2014try :
2115 from tomllib import load as load_tomllib # type: ignore[ignoreMissingImports]
2216except ImportError : # pragma: no cover
@@ -29,34 +23,6 @@ def load_toml(path: Path) -> dict[str, Any]:
2923 return load_tomllib (f )
3024
3125
32- @lru_cache (maxsize = 1 )
33- def get_pydantic_version () -> tuple [Any , bool , bool ]:
34- """Get pydantic version info lazily. Returns (version, is_v2, is_v2_11)."""
35- # Apply pydantic patch before importing pydantic
36- from datamodel_code_generator .pydantic_patch import apply_patch # noqa: PLC0415
37-
38- apply_patch ()
39-
40- import pydantic # noqa: PLC0415
41- from packaging import version # noqa: PLC0415
42-
43- pydantic_version = version .parse (pydantic .VERSION if isinstance (pydantic .VERSION , str ) else str (pydantic .VERSION ))
44- is_v2 = version .parse ("2.0b3" ) <= pydantic_version
45- is_v2_11 = version .parse ("2.11" ) <= pydantic_version
46- return pydantic_version , is_v2 , is_v2_11
47-
48-
49- _is_v2 : bool | None = None
50-
51-
52- def is_pydantic_v2 () -> bool :
53- """Check if pydantic v2 is installed."""
54- global _is_v2 # noqa: PLW0603
55- if _is_v2 is None :
56- _is_v2 = get_pydantic_version ()[1 ]
57- return _is_v2
58-
59-
6026_YAML_1_2_BOOL_PATTERN = re .compile (r"^(?:true|false|True|False|TRUE|FALSE)$" )
6127_YAML_DEPRECATED_BOOL_VALUES = {"True" , "False" , "TRUE" , "FALSE" }
6228# Pattern for scientific notation without decimal point (e.g., 1e-5, 1E+10)
@@ -117,121 +83,16 @@ class CustomSafeLoader(_SafeLoader): # type: ignore[valid-type,misc]
11783 return CustomSafeLoader
11884
11985
120- Model = TypeVar ("Model" , bound = "_BaseModel" ) # ty: ignore
121- T = TypeVar ("T" )
122-
123-
124- @overload
125- def model_validator (
126- mode : Literal ["before" ],
127- ) -> (
128- Callable [[Callable [[type [Model ], T ], T ]], Callable [[type [Model ], T ], T ]]
129- | Callable [[Callable [[Model , T ], T ]], Callable [[Model , T ], T ]]
130- ): ...
131-
132-
133- @overload
134- def model_validator (
135- mode : Literal ["after" ],
136- ) -> (
137- Callable [[Callable [[type [Model ], T ], T ]], Callable [[type [Model ], T ], T ]]
138- | Callable [[Callable [[Model , T ], T ]], Callable [[Model , T ], T ]]
139- | Callable [[Callable [[Model ], Model ]], Callable [[Model ], Model ]]
140- ): ...
141-
142-
143- @overload
144- def model_validator () -> (
145- Callable [[Callable [[type [Model ], T ], T ]], Callable [[type [Model ], T ], T ]]
146- | Callable [[Callable [[Model , T ], T ]], Callable [[Model , T ], T ]]
147- | Callable [[Callable [[Model ], Model ]], Callable [[Model ], Model ]]
148- ): ...
149-
150-
151- def model_validator ( # ty: ignore
152- mode : Literal ["before" , "after" ] = "after" ,
153- ) -> (
154- Callable [[Callable [[type [Model ], T ], T ]], Callable [[type [Model ], T ], T ]]
155- | Callable [[Callable [[Model , T ], T ]], Callable [[Model , T ], T ]]
156- | Callable [[Callable [[Model ], Model ]], Callable [[Model ], Model ]]
157- ):
158- """Decorate model validators for both Pydantic v1 and v2."""
159-
160- @overload
161- def inner (method : Callable [[type [Model ], T ], T ]) -> Callable [[type [Model ], T ], T ]: ...
162-
163- @overload
164- def inner (method : Callable [[Model , T ], T ]) -> Callable [[Model , T ], T ]: ...
165-
166- @overload
167- def inner (method : Callable [[Model ], Model ]) -> Callable [[Model ], Model ]: ...
168-
169- def inner (
170- method : Callable [[type [Model ], T ], T ] | Callable [[Model , T ], T ] | Callable [[Model ], Model ],
171- ) -> Callable [[type [Model ], T ], T ] | Callable [[Model , T ], T ] | Callable [[Model ], Model ]:
172- if is_pydantic_v2 ():
173- from pydantic import model_validator as model_validator_v2 # noqa: PLC0415
174-
175- if mode == "before" :
176- return model_validator_v2 (mode = mode )(classmethod (method )) # type: ignore[reportReturnType]
177- return model_validator_v2 (mode = mode )(method ) # type: ignore[reportReturnType]
178- from pydantic import root_validator # noqa: PLC0415 # pragma: no cover
179-
180- return root_validator (method , pre = mode == "before" ) # ty: ignore # pragma: no cover
181-
182- return inner
183-
184-
185- def field_validator (
186- field_name : str ,
187- * fields : str ,
188- mode : Literal ["before" , "after" ] = "after" ,
189- ) -> Callable [[Any ], Callable [[Any , Any ], Any ]]:
190- """Decorate field validators for both Pydantic v1 and v2."""
191-
192- def inner (method : Callable [[Model , Any ], Any ]) -> Callable [[Model , Any ], Any ]:
193- if is_pydantic_v2 ():
194- from pydantic import field_validator as field_validator_v2 # noqa: PLC0415
195-
196- return field_validator_v2 (field_name , * fields , mode = mode )(method ) # ty: ignore
197- from pydantic import validator # noqa: PLC0415 # pragma: no cover
198-
199- return validator (field_name , * fields , pre = mode == "before" )(method ) # ty: ignore # pragma: no cover
200-
201- return inner
202-
203-
204- @lru_cache (maxsize = 1 )
205- def _get_config_dict () -> type :
206- """Get ConfigDict type lazily. Only used with pydantic v2."""
207- from pydantic import ConfigDict # noqa: PLC0415
208-
209- return ConfigDict
210-
211-
212- class _ConfigDictProxy :
213- """Proxy for lazy ConfigDict access."""
214-
215- def __call__ (self , ** kwargs : Any ) -> Any :
216- return _get_config_dict ()(** kwargs )
217-
218-
219- ConfigDict : type = _ConfigDictProxy () # type: ignore[assignment]
220-
221-
22286@lru_cache (maxsize = 1 )
22387def _get_base_model_class () -> type :
224- """Get version-compatible BaseModel class lazily."""
88+ """Get BaseModel class with strict=False config lazily."""
22589 from pydantic import BaseModel as _PydanticBaseModel # noqa: PLC0415
90+ from pydantic import ConfigDict as _ConfigDict # noqa: PLC0415
22691
227- if is_pydantic_v2 ( ):
228- from pydantic import ConfigDict as _ConfigDict # noqa: PLC0415
92+ class _BaseModelV2 ( _PydanticBaseModel ):
93+ model_config = _ConfigDict ( strict = False )
22994
230- class _BaseModelV2 (_PydanticBaseModel ):
231- model_config = _ConfigDict (strict = False )
232-
233- return _BaseModelV2
234- return _PydanticBaseModel # pragma: no cover
95+ return _BaseModelV2
23596
23697
23798_BaseModel : type | None = None
@@ -290,31 +151,3 @@ def camel_to_snake(string: str) -> str:
290151 """Convert camelCase or PascalCase to snake_case."""
291152 subbed = _UNDER_SCORE_1 .sub (r"\1_\2" , string )
292153 return _UNDER_SCORE_2 .sub (r"\1_\2" , subbed ).lower ()
293-
294-
295- def model_dump (obj : _BaseModel , ** kwargs : Any ) -> dict [str , Any ]: # ty: ignore
296- """Version-compatible model serialization (dict/model_dump)."""
297- if is_pydantic_v2 ():
298- return obj .model_dump (** kwargs ) # ty: ignore
299- return obj .dict (** kwargs ) # type: ignore[reportDeprecated] # pragma: no cover
300-
301-
302- def model_validate (cls : type [Model ], obj : Any ) -> Model :
303- """Version-compatible model validation (parse_obj/model_validate)."""
304- if is_pydantic_v2 ():
305- return cls .model_validate (obj ) # ty: ignore
306- return cls .parse_obj (obj ) # type: ignore[reportDeprecated] # pragma: no cover
307-
308-
309- def get_fields_set (obj : _BaseModel ) -> set [str ]: # ty: ignore
310- """Version-compatible access to fields set (__fields_set__/model_fields_set)."""
311- if is_pydantic_v2 ():
312- return obj .model_fields_set # ty: ignore
313- return obj .__fields_set__ # type: ignore[reportDeprecated] # pragma: no cover
314-
315-
316- def model_copy (obj : Model , ** kwargs : Any ) -> Model :
317- """Version-compatible model copy (copy/model_copy)."""
318- if is_pydantic_v2 ():
319- return obj .model_copy (** kwargs ) # ty: ignore
320- return obj .copy (** kwargs ) # type: ignore[reportDeprecated] # pragma: no cover
0 commit comments