Coverage for pydantic/_internal/_dataclasses.py: 96.05%
68 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-28 10:05 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-28 10:05 +0000
1"""Private logic for creating pydantic dataclasses."""
3from __future__ import annotations as _annotations 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
5import dataclasses 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
6import typing 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
7import warnings 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
8from functools import partial, wraps 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
9from typing import Any, ClassVar 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
11from pydantic_core import ( 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
12 ArgsKwargs,
13 SchemaSerializer,
14 SchemaValidator,
15 core_schema,
16)
17from typing_extensions import TypeGuard 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
19from ..errors import PydanticUndefinedAnnotation 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
20from ..plugin._schema_validator import PluggableSchemaValidator, create_schema_validator 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
21from ..warnings import PydanticDeprecatedSince20 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
22from . import _config, _decorators 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
23from ._fields import collect_dataclass_fields 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
24from ._generate_schema import GenerateSchema, InvalidSchemaError 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
25from ._generics import get_standard_typevars_map 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
26from ._mock_val_ser import set_dataclass_mocks 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
27from ._namespace_utils import NsResolver 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
28from ._signature import generate_pydantic_signature 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
29from ._utils import LazyClassAttribute 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
31if typing.TYPE_CHECKING: 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
32 from _typeshed import DataclassInstance as StandardDataclass
34 from ..config import ConfigDict
35 from ..fields import FieldInfo
37 class PydanticDataclass(StandardDataclass, typing.Protocol):
38 """A protocol containing attributes only available once a class has been decorated as a Pydantic dataclass.
40 Attributes:
41 __pydantic_config__: Pydantic-specific configuration settings for the dataclass.
42 __pydantic_complete__: Whether dataclass building is completed, or if there are still undefined fields.
43 __pydantic_core_schema__: The pydantic-core schema used to build the SchemaValidator and SchemaSerializer.
44 __pydantic_decorators__: Metadata containing the decorators defined on the dataclass.
45 __pydantic_fields__: Metadata about the fields defined on the dataclass.
46 __pydantic_serializer__: The pydantic-core SchemaSerializer used to dump instances of the dataclass.
47 __pydantic_validator__: The pydantic-core SchemaValidator used to validate instances of the dataclass.
48 """
50 __pydantic_config__: ClassVar[ConfigDict]
51 __pydantic_complete__: ClassVar[bool]
52 __pydantic_core_schema__: ClassVar[core_schema.CoreSchema]
53 __pydantic_decorators__: ClassVar[_decorators.DecoratorInfos]
54 __pydantic_fields__: ClassVar[dict[str, FieldInfo]]
55 __pydantic_serializer__: ClassVar[SchemaSerializer]
56 __pydantic_validator__: ClassVar[SchemaValidator | PluggableSchemaValidator]
58else:
59 # See PyCharm issues https://youtrack.jetbrains.com/issue/PY-21915
60 # and https://youtrack.jetbrains.com/issue/PY-51428
61 DeprecationWarning = PydanticDeprecatedSince20 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
64def set_dataclass_fields( 1abcdefghijklmnopqrstuvJKLMNOPwxyzABCDEFG
65 cls: type[StandardDataclass],
66 ns_resolver: NsResolver | None = None,
67 config_wrapper: _config.ConfigWrapper | None = None,
68) -> None:
69 """Collect and set `cls.__pydantic_fields__`.
71 Args:
72 cls: The class.
73 ns_resolver: Namespace resolver to use when getting dataclass annotations.
74 config_wrapper: The config wrapper instance, defaults to `None`.
75 """
76 typevars_map = get_standard_typevars_map(cls) 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
77 fields = collect_dataclass_fields( 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
78 cls, ns_resolver=ns_resolver, typevars_map=typevars_map, config_wrapper=config_wrapper
79 )
81 cls.__pydantic_fields__ = fields # type: ignore 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
84def complete_dataclass( 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
85 cls: type[Any],
86 config_wrapper: _config.ConfigWrapper,
87 *,
88 raise_errors: bool = True,
89 ns_resolver: NsResolver | None = None,
90 _force_build: bool = False,
91) -> bool:
92 """Finish building a pydantic dataclass.
94 This logic is called on a class which has already been wrapped in `dataclasses.dataclass()`.
96 This is somewhat analogous to `pydantic._internal._model_construction.complete_model_class`.
98 Args:
99 cls: The class.
100 config_wrapper: The config wrapper instance.
101 raise_errors: Whether to raise errors, defaults to `True`.
102 ns_resolver: The namespace resolver instance to use when collecting dataclass fields
103 and during schema building.
104 _force_build: Whether to force building the dataclass, no matter if
105 [`defer_build`][pydantic.config.ConfigDict.defer_build] is set.
107 Returns:
108 `True` if building a pydantic dataclass is successfully completed, `False` otherwise.
110 Raises:
111 PydanticUndefinedAnnotation: If `raise_error` is `True` and there is an undefined annotations.
112 """
113 original_init = cls.__init__ 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
115 # dataclass.__init__ must be defined here so its `__qualname__` can be changed since functions can't be copied,
116 # and so that the mock validator is used if building was deferred:
117 def __init__(__dataclass_self__: PydanticDataclass, *args: Any, **kwargs: Any) -> None: 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
118 __tracebackhide__ = True 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
119 s = __dataclass_self__ 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
120 s.__pydantic_validator__.validate_python(ArgsKwargs(args, kwargs), self_instance=s) 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
122 __init__.__qualname__ = f'{cls.__qualname__}.__init__' 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
124 cls.__init__ = __init__ # type: ignore 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
125 cls.__pydantic_config__ = config_wrapper.config_dict # type: ignore 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
127 set_dataclass_fields(cls, ns_resolver, config_wrapper=config_wrapper) 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
129 if not _force_build and config_wrapper.defer_build: 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
130 set_dataclass_mocks(cls) 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG
131 return False 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG
133 if hasattr(cls, '__post_init_post_parse__'): 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
134 warnings.warn( 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG
135 'Support for `__post_init_post_parse__` has been dropped, the method will not be called', DeprecationWarning
136 )
138 typevars_map = get_standard_typevars_map(cls) 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
139 gen_schema = GenerateSchema( 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
140 config_wrapper,
141 ns_resolver=ns_resolver,
142 typevars_map=typevars_map,
143 )
145 # set __signature__ attr only for the class, but not for its instances
146 # (because instances can define `__call__`, and `inspect.signature` shouldn't
147 # use the `__signature__` attribute and instead generate from `__call__`).
148 cls.__signature__ = LazyClassAttribute( 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
149 '__signature__',
150 partial(
151 generate_pydantic_signature,
152 # It's important that we reference the `original_init` here,
153 # as it is the one synthesized by the stdlib `dataclass` module:
154 init=original_init,
155 fields=cls.__pydantic_fields__, # type: ignore
156 validate_by_name=config_wrapper.validate_by_name,
157 extra=config_wrapper.extra,
158 is_dataclass=True,
159 ),
160 )
162 try: 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
163 schema = gen_schema.generate_schema(cls) 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
164 except PydanticUndefinedAnnotation as e: 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG
165 if raise_errors: 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG
166 raise 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG
167 set_dataclass_mocks(cls, f'`{e.name}`') 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG
168 return False 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG
170 core_config = config_wrapper.core_config(title=cls.__name__) 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
172 try: 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
173 schema = gen_schema.clean_schema(schema) 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
174 except InvalidSchemaError:
175 set_dataclass_mocks(cls)
176 return False
178 # We are about to set all the remaining required properties expected for this cast;
179 # __pydantic_decorators__ and __pydantic_fields__ should already be set
180 cls = typing.cast('type[PydanticDataclass]', cls) 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
181 # debug(schema)
183 cls.__pydantic_core_schema__ = schema 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
184 cls.__pydantic_validator__ = validator = create_schema_validator( 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
185 schema, cls, cls.__module__, cls.__qualname__, 'dataclass', core_config, config_wrapper.plugin_settings
186 )
187 cls.__pydantic_serializer__ = SchemaSerializer(schema, core_config) 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
189 if config_wrapper.validate_assignment: 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
191 @wraps(cls.__setattr__) 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
192 def validated_setattr(instance: Any, field: str, value: str, /) -> None: 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
193 validator.validate_assignment(instance, field, value) 1abcdefghijkHIlmnopqrstuvwxyzABCDEFG
195 cls.__setattr__ = validated_setattr.__get__(None, cls) # type: ignore 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
197 cls.__pydantic_complete__ = True 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
198 return True 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
201def is_builtin_dataclass(_cls: type[Any]) -> TypeGuard[type[StandardDataclass]]: 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
202 """Returns True if a class is a stdlib dataclass and *not* a pydantic dataclass.
204 We check that
205 - `_cls` is a dataclass
206 - `_cls` does not inherit from a processed pydantic dataclass (and thus have a `__pydantic_validator__`)
207 - `_cls` does not have any annotations that are not dataclass fields
208 e.g.
209 ```python
210 import dataclasses
212 import pydantic.dataclasses
214 @dataclasses.dataclass
215 class A:
216 x: int
218 @pydantic.dataclasses.dataclass
219 class B(A):
220 y: int
221 ```
222 In this case, when we first check `B`, we make an extra check and look at the annotations ('y'),
223 which won't be a superset of all the dataclass fields (only the stdlib fields i.e. 'x')
225 Args:
226 cls: The class.
228 Returns:
229 `True` if the class is a stdlib dataclass, `False` otherwise.
230 """
231 return ( 1abcdefghijkHIlmnopqrstuvJKLMNOPwxyzABCDEFG
232 dataclasses.is_dataclass(_cls)
233 and not hasattr(_cls, '__pydantic_validator__')
234 and set(_cls.__dataclass_fields__).issuperset(set(getattr(_cls, '__annotations__', {})))
235 )