Coverage for pydantic/_internal/_dataclasses.py: 97.52%
97 statements
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-22 09:30 +0000
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-22 09:30 +0000
1"""Private logic for creating pydantic dataclasses."""
3from __future__ import annotations as _annotations 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
5import copy 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
6import dataclasses 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
7import sys 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
8import typing 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
9import warnings 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
10from collections.abc import Generator 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
11from contextlib import contextmanager 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
12from functools import partial 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
13from typing import Any, ClassVar, cast 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
15from pydantic_core import ( 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
16 ArgsKwargs,
17 SchemaSerializer,
18 SchemaValidator,
19 core_schema,
20)
21from typing_extensions import TypeAlias, TypeIs 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
23from ..errors import PydanticUndefinedAnnotation 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
24from ..fields import FieldInfo 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
25from ..plugin._schema_validator import PluggableSchemaValidator, create_schema_validator 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
26from ..warnings import PydanticDeprecatedSince20 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
27from . import _config, _decorators 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
28from ._fields import collect_dataclass_fields 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
29from ._generate_schema import GenerateSchema, InvalidSchemaError 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
30from ._generics import get_standard_typevars_map 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
31from ._mock_val_ser import set_dataclass_mocks 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
32from ._namespace_utils import NsResolver 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
33from ._signature import generate_pydantic_signature 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
34from ._utils import LazyClassAttribute 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
36if typing.TYPE_CHECKING: 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
37 from _typeshed import DataclassInstance as StandardDataclass
39 from ..config import ConfigDict
41 class PydanticDataclass(StandardDataclass, typing.Protocol): 1k
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 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
71def set_dataclass_fields( 1FGabcdefghijMHIlmnopqrstuNPJKvwxyzABCDEO
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) 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
84 fields = collect_dataclass_fields( 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
85 cls, ns_resolver=ns_resolver, typevars_map=typevars_map, config_wrapper=config_wrapper
86 )
88 cls.__pydantic_fields__ = fields # type: ignore 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
91def complete_dataclass( 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
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__ 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
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: 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
125 __tracebackhide__ = True 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
126 s = __dataclass_self__ 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
127 s.__pydantic_validator__.validate_python(ArgsKwargs(args, kwargs), self_instance=s) 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
129 __init__.__qualname__ = f'{cls.__qualname__}.__init__' 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
131 cls.__init__ = __init__ # type: ignore 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
132 cls.__pydantic_config__ = config_wrapper.config_dict # type: ignore 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
134 set_dataclass_fields(cls, config_wrapper=config_wrapper, ns_resolver=ns_resolver) 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
136 if not _force_build and config_wrapper.defer_build: 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
137 set_dataclass_mocks(cls) 1FGabcdefghijMLkHIlmnopqrstuNJKvwxyzABCDEO
138 return False 1FGabcdefghijMLkHIlmnopqrstuNJKvwxyzABCDEO
140 if hasattr(cls, '__post_init_post_parse__'): 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
141 warnings.warn( 1FGabcdefghijMLkHIlmnopqrstuNJKvwxyzABCDEO
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) 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
146 gen_schema = GenerateSchema( 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
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( 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
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: 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
170 schema = gen_schema.generate_schema(cls) 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
171 except PydanticUndefinedAnnotation as e: 1FGabcdefghijMLkHIlmnopqrstuNJKvwxyzABCDEO
172 if raise_errors: 1FGabcdefghijMLkHIlmnopqrstuNJKvwxyzABCDEO
173 raise 1FGabcdefghijLkHIlmnopqrstuJKvwxyzABCDE
174 set_dataclass_mocks(cls, f'`{e.name}`') 1FGabcdefghijMLkHIlmnopqrstuNJKvwxyzABCDEO
175 return False 1FGabcdefghijMLkHIlmnopqrstuNJKvwxyzABCDEO
177 core_config = config_wrapper.core_config(title=cls.__name__) 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
179 try: 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
180 schema = gen_schema.clean_schema(schema) 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
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) 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
189 cls.__pydantic_core_schema__ = schema 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
190 cls.__pydantic_validator__ = create_schema_validator( 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
191 schema, cls, cls.__module__, cls.__qualname__, 'dataclass', core_config, config_wrapper.plugin_settings
192 )
193 cls.__pydantic_serializer__ = SchemaSerializer(schema, core_config) 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
194 cls.__pydantic_complete__ = True 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
195 return True 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
198def is_stdlib_dataclass(cls: type[Any], /) -> TypeIs[type[StandardDataclass]]: 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
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__') 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
213def as_dataclass_field(pydantic_field: FieldInfo) -> dataclasses.Field[Any]: 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
214 field_args: dict[str, Any] = {'default': pydantic_field} 1FGabcdefghijMLkHIlmnopqrstuNJKvwxyzABCDEO
216 if sys.version_info >= (3, 10) and pydantic_field.kw_only: 1FGabcdefghijMLkHIlmnopqrstuNJKvwxyzABCDEO
217 field_args['kw_only'] = True 1abcdefghijMklmnopqrstuNvwxyzABCDEO
219 if pydantic_field.repr is not True: 1FGabcdefghijMLkHIlmnopqrstuNJKvwxyzABCDEO
220 field_args['repr'] = pydantic_field.repr 1FGabcdefghijMLkHIlmnopqrstuNJKvwxyzABCDEO
222 return dataclasses.field(**field_args) 1FGabcdefghijMLkHIlmnopqrstuNJKvwxyzABCDEO
225DcFields: TypeAlias = dict[str, dataclasses.Field[Any]] 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
228@contextmanager 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
229def patch_base_fields(cls: type[Any]) -> Generator[None]: 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
230 """Temporarily patch the stdlib dataclasses bases of `cls` if the Pydantic `Field()` function is used.
232 When creating a Pydantic dataclass, it is possible to inherit from stdlib dataclasses, where
233 the Pydantic `Field()` function is used. To create this Pydantic dataclass, we first apply
234 the stdlib `@dataclass` decorator on it. During the construction of the stdlib dataclass,
235 the `kw_only` and `repr` field arguments need to be understood by the stdlib *during* the
236 dataclass construction. To do so, we temporarily patch the fields dictionary of the affected
237 bases.
239 For instance, with the following example:
241 ```python {test="skip" lint="skip"}
242 import dataclasses as stdlib_dc
244 import pydantic
245 import pydantic.dataclasses as pydantic_dc
247 @stdlib_dc.dataclass
248 class A:
249 a: int = pydantic.Field(repr=False)
251 # Notice that the `repr` attribute of the dataclass field is `True`:
252 A.__dataclass_fields__['a']
253 #> dataclass.Field(default=FieldInfo(repr=False), repr=True, ...)
255 @pydantic_dc.dataclass
256 class B(A):
257 b: int = pydantic.Field(repr=False)
258 ```
260 When passing `B` to the stdlib `@dataclass` decorator, it will look for fields in the parent classes
261 and reuse them directly. When this context manager is active, `A` will be temporarily patched to be
262 equivalent to:
264 ```python {test="skip" lint="skip"}
265 @stdlib_dc.dataclass
266 class A:
267 a: int = stdlib_dc.field(default=Field(repr=False), repr=False)
268 ```
270 !!! note
271 This is only applied to the bases of `cls`, and not `cls` itself. The reason is that the Pydantic
272 dataclass decorator "owns" `cls` (in the previous example, `B`). As such, we instead modify the fields
273 directly (in the previous example, we simply do `setattr(B, 'b', as_dataclass_field(pydantic_field))`).
275 !!! note
276 This approach is far from ideal, and can probably be the source of unwanted side effects/race conditions.
277 The previous implemented approach was mutating the `__annotations__` dict of `cls`, which is no longer a
278 safe operation in Python 3.14+, and resulted in unexpected behavior with field ordering anyway.
279 """
280 # A list of two-tuples, the first element being a reference to the
281 # dataclass fields dictionary, the second element being a mapping between
282 # the field names that were modified, and their original `Field`:
283 original_fields_list: list[tuple[DcFields, DcFields]] = [] 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
285 for base in cls.__mro__[1:]: 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
286 dc_fields: dict[str, dataclasses.Field[Any]] = base.__dict__.get('__dataclass_fields__', {}) 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
287 dc_fields_with_pydantic_field_defaults = { 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
288 field_name: field
289 for field_name, field in dc_fields.items()
290 if isinstance(field.default, FieldInfo)
291 # Only do the patching if one of the affected attributes is set:
292 and (field.default.kw_only or field.default.repr is not True)
293 }
294 if dc_fields_with_pydantic_field_defaults: 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
295 original_fields_list.append((dc_fields, dc_fields_with_pydantic_field_defaults)) 1FGabcdefghijLkHIlmnopqrstuJKvwxyzABCDE
296 for field_name, field in dc_fields_with_pydantic_field_defaults.items(): 1FGabcdefghijLkHIlmnopqrstuJKvwxyzABCDE
297 default = cast(FieldInfo, field.default) 1FGabcdefghijLkHIlmnopqrstuJKvwxyzABCDE
298 # `dataclasses.Field` isn't documented as working with `copy.copy()`.
299 # It is a class with `__slots__`, so should work (and we hope for the best):
300 new_dc_field = copy.copy(field) 1FGabcdefghijLkHIlmnopqrstuJKvwxyzABCDE
301 if default.kw_only: 1FGabcdefghijLkHIlmnopqrstuJKvwxyzABCDE
302 new_dc_field.kw_only = True 1abcdefghijklmnopqrstuvwxyzABCDE
303 if default.repr is not True: 1FGabcdefghijLkHIlmnopqrstuJKvwxyzABCDE
304 new_dc_field.repr = default.repr 1FGabcdefghijLkHIlmnopqrstuJKvwxyzABCDE
305 dc_fields[field_name] = new_dc_field 1FGabcdefghijLkHIlmnopqrstuJKvwxyzABCDE
307 try: 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
308 yield 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
309 finally:
310 for fields, original_fields in original_fields_list: 1FGabcdefghijMLkHIlmnopqrstuNPJKvwxyzABCDEO
311 for field_name, original_field in original_fields.items(): 1FGabcdefghijLkHIlmnopqrstuJKvwxyzABCDE
312 fields[field_name] = original_field 1FGabcdefghijLkHIlmnopqrstuJKvwxyzABCDE