Coverage for pydantic/_internal/_dataclasses.py: 97.60%
99 statements
« prev ^ index » next coverage.py v7.10.0, created at 2025-07-26 11:49 +0000
« prev ^ index » next coverage.py v7.10.0, created at 2025-07-26 11:49 +0000
1"""Private logic for creating pydantic dataclasses."""
3from __future__ import annotations as _annotations 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
5import copy 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
6import dataclasses 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
7import sys 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
8import typing 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
9import warnings 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
10from collections.abc import Generator 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
11from contextlib import contextmanager 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
12from functools import partial 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
13from typing import Any, ClassVar, cast 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
15from pydantic_core import ( 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
16 ArgsKwargs,
17 SchemaSerializer,
18 SchemaValidator,
19 core_schema,
20)
21from typing_extensions import TypeAlias, TypeIs 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
23from ..errors import PydanticUndefinedAnnotation 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
24from ..fields import FieldInfo 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
25from ..plugin._schema_validator import PluggableSchemaValidator, create_schema_validator 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
26from ..warnings import PydanticDeprecatedSince20 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
27from . import _config, _decorators 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
28from ._fields import collect_dataclass_fields 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
29from ._generate_schema import GenerateSchema, InvalidSchemaError 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
30from ._generics import get_standard_typevars_map 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
31from ._mock_val_ser import set_dataclass_mocks 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
32from ._namespace_utils import NsResolver 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
33from ._signature import generate_pydantic_signature 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
34from ._utils import LazyClassAttribute 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
36if typing.TYPE_CHECKING: 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
37 from _typeshed import DataclassInstance as StandardDataclass
39 from ..config import ConfigDict
41 class PydanticDataclass(StandardDataclass, typing.Protocol): 1r
42 """A protocol containing attributes only available once a class has been decorated as a Pydantic dataclass.
44 Attributes:
45 __pydantic_config__: Pydantic-specific configuration settings for the dataclass.
46 __pydantic_complete__: Whether dataclass building is completed, or if there are still undefined fields.
47 __pydantic_core_schema__: The pydantic-core schema used to build the SchemaValidator and SchemaSerializer.
48 __pydantic_decorators__: Metadata containing the decorators defined on the dataclass.
49 __pydantic_fields__: Metadata about the fields defined on the dataclass.
50 __pydantic_serializer__: The pydantic-core SchemaSerializer used to dump instances of the dataclass.
51 __pydantic_validator__: The pydantic-core SchemaValidator used to validate instances of the dataclass.
52 """
54 __pydantic_config__: ClassVar[ConfigDict]
55 __pydantic_complete__: ClassVar[bool]
56 __pydantic_core_schema__: ClassVar[core_schema.CoreSchema]
57 __pydantic_decorators__: ClassVar[_decorators.DecoratorInfos]
58 __pydantic_fields__: ClassVar[dict[str, FieldInfo]]
59 __pydantic_serializer__: ClassVar[SchemaSerializer]
60 __pydantic_validator__: ClassVar[SchemaValidator | PluggableSchemaValidator]
62 @classmethod
63 def __pydantic_fields_complete__(cls) -> bool: ...
65else:
66 # See PyCharm issues https://youtrack.jetbrains.com/issue/PY-21915
67 # and https://youtrack.jetbrains.com/issue/PY-51428
68 DeprecationWarning = PydanticDeprecatedSince20 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
71def set_dataclass_fields( 1IJjklmnopqabcKLstuvwxyzdefPMNABCDEFGHghi
72 cls: type[StandardDataclass],
73 config_wrapper: _config.ConfigWrapper,
74 ns_resolver: NsResolver | None = None,
75) -> None:
76 """Collect and set `cls.__pydantic_fields__`.
78 Args:
79 cls: The class.
80 config_wrapper: The config wrapper instance.
81 ns_resolver: Namespace resolver to use when getting dataclass annotations.
82 """
83 typevars_map = get_standard_typevars_map(cls) 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
84 fields = collect_dataclass_fields( 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
85 cls, ns_resolver=ns_resolver, typevars_map=typevars_map, config_wrapper=config_wrapper
86 )
88 cls.__pydantic_fields__ = fields # type: ignore 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
91def complete_dataclass( 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
92 cls: type[Any],
93 config_wrapper: _config.ConfigWrapper,
94 *,
95 raise_errors: bool = True,
96 ns_resolver: NsResolver | None = None,
97 _force_build: bool = False,
98) -> bool:
99 """Finish building a pydantic dataclass.
101 This logic is called on a class which has already been wrapped in `dataclasses.dataclass()`.
103 This is somewhat analogous to `pydantic._internal._model_construction.complete_model_class`.
105 Args:
106 cls: The class.
107 config_wrapper: The config wrapper instance.
108 raise_errors: Whether to raise errors, defaults to `True`.
109 ns_resolver: The namespace resolver instance to use when collecting dataclass fields
110 and during schema building.
111 _force_build: Whether to force building the dataclass, no matter if
112 [`defer_build`][pydantic.config.ConfigDict.defer_build] is set.
114 Returns:
115 `True` if building a pydantic dataclass is successfully completed, `False` otherwise.
117 Raises:
118 PydanticUndefinedAnnotation: If `raise_error` is `True` and there is an undefined annotations.
119 """
120 original_init = cls.__init__ 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
122 # dataclass.__init__ must be defined here so its `__qualname__` can be changed since functions can't be copied,
123 # and so that the mock validator is used if building was deferred:
124 def __init__(__dataclass_self__: PydanticDataclass, *args: Any, **kwargs: Any) -> None: 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
125 __tracebackhide__ = True 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
126 s = __dataclass_self__ 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
127 s.__pydantic_validator__.validate_python(ArgsKwargs(args, kwargs), self_instance=s) 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
129 __init__.__qualname__ = f'{cls.__qualname__}.__init__' 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
131 cls.__init__ = __init__ # type: ignore 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
132 cls.__pydantic_config__ = config_wrapper.config_dict # type: ignore 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
134 set_dataclass_fields(cls, config_wrapper=config_wrapper, ns_resolver=ns_resolver) 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
136 if not _force_build and config_wrapper.defer_build: 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
137 set_dataclass_mocks(cls) 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi
138 return False 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi
140 if hasattr(cls, '__post_init_post_parse__'): 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
141 warnings.warn( 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi
142 'Support for `__post_init_post_parse__` has been dropped, the method will not be called', DeprecationWarning
143 )
145 typevars_map = get_standard_typevars_map(cls) 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
146 gen_schema = GenerateSchema( 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
147 config_wrapper,
148 ns_resolver=ns_resolver,
149 typevars_map=typevars_map,
150 )
152 # set __signature__ attr only for the class, but not for its instances
153 # (because instances can define `__call__`, and `inspect.signature` shouldn't
154 # use the `__signature__` attribute and instead generate from `__call__`).
155 cls.__signature__ = LazyClassAttribute( 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
156 '__signature__',
157 partial(
158 generate_pydantic_signature,
159 # It's important that we reference the `original_init` here,
160 # as it is the one synthesized by the stdlib `dataclass` module:
161 init=original_init,
162 fields=cls.__pydantic_fields__, # type: ignore
163 validate_by_name=config_wrapper.validate_by_name,
164 extra=config_wrapper.extra,
165 is_dataclass=True,
166 ),
167 )
169 try: 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
170 schema = gen_schema.generate_schema(cls) 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
171 except PydanticUndefinedAnnotation as e: 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi
172 if raise_errors: 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi
173 raise 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi
174 set_dataclass_mocks(cls, f'`{e.name}`') 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi
175 return False 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi
177 core_config = config_wrapper.core_config(title=cls.__name__) 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
179 try: 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
180 schema = gen_schema.clean_schema(schema) 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
181 except InvalidSchemaError:
182 set_dataclass_mocks(cls)
183 return False
185 # We are about to set all the remaining required properties expected for this cast;
186 # __pydantic_decorators__ and __pydantic_fields__ should already be set
187 cls = typing.cast('type[PydanticDataclass]', cls) 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
189 cls.__pydantic_core_schema__ = schema 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
190 cls.__pydantic_validator__ = create_schema_validator( 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
191 schema, cls, cls.__module__, cls.__qualname__, 'dataclass', core_config, config_wrapper.plugin_settings
192 )
193 cls.__pydantic_serializer__ = SchemaSerializer(schema, core_config) 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
194 cls.__pydantic_complete__ = True 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
195 return True 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
198def is_stdlib_dataclass(cls: type[Any], /) -> TypeIs[type[StandardDataclass]]: 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
199 """Returns `True` if the class is a stdlib dataclass and *not* a Pydantic dataclass.
201 Unlike the stdlib `dataclasses.is_dataclass()` function, this does *not* include subclasses
202 of a dataclass that are themselves not dataclasses.
204 Args:
205 cls: The class.
207 Returns:
208 `True` if the class is a stdlib dataclass, `False` otherwise.
209 """
210 return '__dataclass_fields__' in cls.__dict__ and not hasattr(cls, '__pydantic_validator__') 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
213def as_dataclass_field(pydantic_field: FieldInfo) -> dataclasses.Field[Any]: 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
214 field_args: dict[str, Any] = {'default': pydantic_field} 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi
216 # Needed because if `doc` is set, the dataclass slots will be a dict (field name -> doc) instead of a tuple:
217 if sys.version_info >= (3, 14) and pydantic_field.description is not None: 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi
218 field_args['doc'] = pydantic_field.description 1abcdefghi
220 # Needed as the stdlib dataclass module processes kw_only in a specific way during class construction:
221 if sys.version_info >= (3, 10) and pydantic_field.kw_only: 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi
222 field_args['kw_only'] = True 1jklmnopqabcrstuvwxyzdefABCDEFGHghi
224 # Needed as the stdlib dataclass modules generates `__repr__()` during class construction:
225 if pydantic_field.repr is not True: 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi
226 field_args['repr'] = pydantic_field.repr 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi
228 return dataclasses.field(**field_args) 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi
231DcFields: TypeAlias = dict[str, dataclasses.Field[Any]] 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
234@contextmanager 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
235def patch_base_fields(cls: type[Any]) -> Generator[None]: 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
236 """Temporarily patch the stdlib dataclasses bases of `cls` if the Pydantic `Field()` function is used.
238 When creating a Pydantic dataclass, it is possible to inherit from stdlib dataclasses, where
239 the Pydantic `Field()` function is used. To create this Pydantic dataclass, we first apply
240 the stdlib `@dataclass` decorator on it. During the construction of the stdlib dataclass,
241 the `kw_only` and `repr` field arguments need to be understood by the stdlib *during* the
242 dataclass construction. To do so, we temporarily patch the fields dictionary of the affected
243 bases.
245 For instance, with the following example:
247 ```python {test="skip" lint="skip"}
248 import dataclasses as stdlib_dc
250 import pydantic
251 import pydantic.dataclasses as pydantic_dc
253 @stdlib_dc.dataclass
254 class A:
255 a: int = pydantic.Field(repr=False)
257 # Notice that the `repr` attribute of the dataclass field is `True`:
258 A.__dataclass_fields__['a']
259 #> dataclass.Field(default=FieldInfo(repr=False), repr=True, ...)
261 @pydantic_dc.dataclass
262 class B(A):
263 b: int = pydantic.Field(repr=False)
264 ```
266 When passing `B` to the stdlib `@dataclass` decorator, it will look for fields in the parent classes
267 and reuse them directly. When this context manager is active, `A` will be temporarily patched to be
268 equivalent to:
270 ```python {test="skip" lint="skip"}
271 @stdlib_dc.dataclass
272 class A:
273 a: int = stdlib_dc.field(default=Field(repr=False), repr=False)
274 ```
276 !!! note
277 This is only applied to the bases of `cls`, and not `cls` itself. The reason is that the Pydantic
278 dataclass decorator "owns" `cls` (in the previous example, `B`). As such, we instead modify the fields
279 directly (in the previous example, we simply do `setattr(B, 'b', as_dataclass_field(pydantic_field))`).
281 !!! note
282 This approach is far from ideal, and can probably be the source of unwanted side effects/race conditions.
283 The previous implemented approach was mutating the `__annotations__` dict of `cls`, which is no longer a
284 safe operation in Python 3.14+, and resulted in unexpected behavior with field ordering anyway.
285 """
286 # A list of two-tuples, the first element being a reference to the
287 # dataclass fields dictionary, the second element being a mapping between
288 # the field names that were modified, and their original `Field`:
289 original_fields_list: list[tuple[DcFields, DcFields]] = [] 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
291 for base in cls.__mro__[1:]: 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
292 dc_fields: dict[str, dataclasses.Field[Any]] = base.__dict__.get('__dataclass_fields__', {}) 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
293 dc_fields_with_pydantic_field_defaults = { 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
294 field_name: field
295 for field_name, field in dc_fields.items()
296 if isinstance(field.default, FieldInfo)
297 # Only do the patching if one of the affected attributes is set:
298 and (field.default.description is not None or field.default.kw_only or field.default.repr is not True)
299 }
300 if dc_fields_with_pydantic_field_defaults: 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
301 original_fields_list.append((dc_fields, dc_fields_with_pydantic_field_defaults)) 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi
302 for field_name, field in dc_fields_with_pydantic_field_defaults.items(): 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi
303 default = cast(FieldInfo, field.default) 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi
304 # `dataclasses.Field` isn't documented as working with `copy.copy()`.
305 # It is a class with `__slots__`, so should work (and we hope for the best):
306 new_dc_field = copy.copy(field) 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi
307 # For base fields, no need to set `doc` from `FieldInfo.description`, this is only relevant
308 # for the class under construction and handled in `as_dataclass_field()`.
309 if sys.version_info >= (3, 10) and default.kw_only: 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi
310 new_dc_field.kw_only = True 1jklmnopqabcrstuvwxyzdefABCDEFGHghi
311 if default.repr is not True: 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi
312 new_dc_field.repr = default.repr 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi
313 dc_fields[field_name] = new_dc_field 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi
315 try: 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
316 yield 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
317 finally:
318 for fields, original_fields in original_fields_list: 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi
319 for field_name, original_field in original_fields.items(): 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi
320 fields[field_name] = original_field 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi