Coverage for pydantic/_internal/_dataclasses.py: 95.06%
69 statements
« prev ^ index » next coverage.py v7.5.4, created at 2024-07-03 19:29 +0000
« prev ^ index » next coverage.py v7.5.4, created at 2024-07-03 19:29 +0000
1"""Private logic for creating pydantic dataclasses."""
3from __future__ import annotations as _annotations 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
5import dataclasses 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
6import typing 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
7import warnings 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
8from functools import partial, wraps 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
9from typing import Any, Callable, ClassVar 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
11from pydantic_core import ( 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
12 ArgsKwargs,
13 SchemaSerializer,
14 SchemaValidator,
15 core_schema,
16)
17from typing_extensions import TypeGuard 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
19from ..errors import PydanticUndefinedAnnotation 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
20from ..fields import FieldInfo 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
21from ..plugin._schema_validator import create_schema_validator 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
22from ..warnings import PydanticDeprecatedSince20 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
23from . import _config, _decorators, _typing_extra 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
24from ._fields import collect_dataclass_fields 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
25from ._generate_schema import GenerateSchema 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
26from ._generics import get_standard_typevars_map 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
27from ._mock_val_ser import set_dataclass_mocks 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
28from ._schema_generation_shared import CallbackGetCoreSchemaHandler 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
29from ._signature import generate_pydantic_signature 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
31if typing.TYPE_CHECKING: 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
32 from ..config import ConfigDict
34 class StandardDataclass(typing.Protocol):
35 __dataclass_fields__: ClassVar[dict[str, Any]]
36 __dataclass_params__: ClassVar[Any] # in reality `dataclasses._DataclassParams`
37 __post_init__: ClassVar[Callable[..., None]]
39 def __init__(self, *args: object, **kwargs: object) -> None:
40 pass
42 class PydanticDataclass(StandardDataclass, typing.Protocol):
43 """A protocol containing attributes only available once a class has been decorated as a Pydantic dataclass.
45 Attributes:
46 __pydantic_config__: Pydantic-specific configuration settings for the dataclass.
47 __pydantic_complete__: Whether dataclass building is completed, or if there are still undefined fields.
48 __pydantic_core_schema__: The pydantic-core schema used to build the SchemaValidator and SchemaSerializer.
49 __pydantic_decorators__: Metadata containing the decorators defined on the dataclass.
50 __pydantic_fields__: Metadata about the fields defined on the dataclass.
51 __pydantic_serializer__: The pydantic-core SchemaSerializer used to dump instances of the dataclass.
52 __pydantic_validator__: The pydantic-core SchemaValidator used to validate instances of the dataclass.
53 """
55 __pydantic_config__: ClassVar[ConfigDict]
56 __pydantic_complete__: ClassVar[bool]
57 __pydantic_core_schema__: ClassVar[core_schema.CoreSchema]
58 __pydantic_decorators__: ClassVar[_decorators.DecoratorInfos]
59 __pydantic_fields__: ClassVar[dict[str, FieldInfo]]
60 __pydantic_serializer__: ClassVar[SchemaSerializer]
61 __pydantic_validator__: ClassVar[SchemaValidator]
63else:
64 # See PyCharm issues https://youtrack.jetbrains.com/issue/PY-21915
65 # and https://youtrack.jetbrains.com/issue/PY-51428
66 DeprecationWarning = PydanticDeprecatedSince20 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
69def set_dataclass_fields( 1abcdefghijklmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
70 cls: type[StandardDataclass],
71 types_namespace: dict[str, Any] | None = None,
72 config_wrapper: _config.ConfigWrapper | None = None,
73) -> None:
74 """Collect and set `cls.__pydantic_fields__`.
76 Args:
77 cls: The class.
78 types_namespace: The types namespace, defaults to `None`.
79 config_wrapper: The config wrapper instance, defaults to `None`.
80 """
81 typevars_map = get_standard_typevars_map(cls) 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
82 fields = collect_dataclass_fields(cls, types_namespace, typevars_map=typevars_map, config_wrapper=config_wrapper) 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
84 cls.__pydantic_fields__ = fields # type: ignore 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
87def complete_dataclass( 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
88 cls: type[Any],
89 config_wrapper: _config.ConfigWrapper,
90 *,
91 raise_errors: bool = True,
92 types_namespace: dict[str, Any] | None,
93) -> bool:
94 """Finish building a pydantic dataclass.
96 This logic is called on a class which has already been wrapped in `dataclasses.dataclass()`.
98 This is somewhat analogous to `pydantic._internal._model_construction.complete_model_class`.
100 Args:
101 cls: The class.
102 config_wrapper: The config wrapper instance.
103 raise_errors: Whether to raise errors, defaults to `True`.
104 types_namespace: The types namespace.
106 Returns:
107 `True` if building a pydantic dataclass is successfully completed, `False` otherwise.
109 Raises:
110 PydanticUndefinedAnnotation: If `raise_error` is `True` and there is an undefined annotations.
111 """
112 if hasattr(cls, '__post_init_post_parse__'): 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
113 warnings.warn( 1abcdefghijklKLmnopqrstuvwxyzABCDEFGHIJ
114 'Support for `__post_init_post_parse__` has been dropped, the method will not be called', DeprecationWarning
115 )
117 if types_namespace is None: 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
118 types_namespace = _typing_extra.get_cls_types_namespace(cls) 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
120 set_dataclass_fields(cls, types_namespace, config_wrapper=config_wrapper) 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
122 typevars_map = get_standard_typevars_map(cls) 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
123 gen_schema = GenerateSchema( 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
124 config_wrapper,
125 types_namespace,
126 typevars_map,
127 )
129 # This needs to be called before we change the __init__
130 sig = generate_pydantic_signature( 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
131 init=cls.__init__,
132 fields=cls.__pydantic_fields__, # type: ignore
133 config_wrapper=config_wrapper,
134 is_dataclass=True,
135 )
137 # dataclass.__init__ must be defined here so its `__qualname__` can be changed since functions can't be copied.
138 def __init__(__dataclass_self__: PydanticDataclass, *args: Any, **kwargs: Any) -> None: 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
139 __tracebackhide__ = True 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
140 s = __dataclass_self__ 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
141 s.__pydantic_validator__.validate_python(ArgsKwargs(args, kwargs), self_instance=s) 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
143 __init__.__qualname__ = f'{cls.__qualname__}.__init__' 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
145 cls.__init__ = __init__ # type: ignore 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
146 cls.__pydantic_config__ = config_wrapper.config_dict # type: ignore 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
147 cls.__signature__ = sig # type: ignore 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
148 get_core_schema = getattr(cls, '__get_pydantic_core_schema__', None) 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
149 try: 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
150 if get_core_schema: 150 ↛ 151line 150 didn't jump to line 151 because the condition on line 150 was never true1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
151 schema = get_core_schema(
152 cls,
153 CallbackGetCoreSchemaHandler(
154 partial(gen_schema.generate_schema, from_dunder_get_core_schema=False),
155 gen_schema,
156 ref_mode='unpack',
157 ),
158 )
159 else:
160 schema = gen_schema.generate_schema(cls, from_dunder_get_core_schema=False) 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
161 except PydanticUndefinedAnnotation as e: 1abcdefghijklKLmnopqrstuvwxyzABCDEFGHIJ
162 if raise_errors: 1abcdefghijklKLmnopqrstuvwxyzABCDEFGHIJ
163 raise 1abcdefghijklKLmnopqrstuvwxyzABCDEFGHIJ
164 set_dataclass_mocks(cls, cls.__name__, f'`{e.name}`') 1abcdefghijklKLmnopqrstuvwxyzABCDEFGHIJ
165 return False 1abcdefghijklKLmnopqrstuvwxyzABCDEFGHIJ
167 core_config = config_wrapper.core_config(cls) 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
169 try: 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
170 schema = gen_schema.clean_schema(schema) 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
171 except gen_schema.CollectedInvalid: 1abcdefghijklKLmnopqrstuvwxyzABCDEFGHIJ
172 set_dataclass_mocks(cls, cls.__name__, 'all referenced types')
173 return False
175 # We are about to set all the remaining required properties expected for this cast;
176 # __pydantic_decorators__ and __pydantic_fields__ should already be set
177 cls = typing.cast('type[PydanticDataclass]', cls) 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
178 # debug(schema)
180 cls.__pydantic_core_schema__ = schema 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
181 cls.__pydantic_validator__ = validator = create_schema_validator( 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
182 schema, cls, cls.__module__, cls.__qualname__, 'dataclass', core_config, config_wrapper.plugin_settings
183 )
184 cls.__pydantic_serializer__ = SchemaSerializer(schema, core_config) 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
186 if config_wrapper.validate_assignment: 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
188 @wraps(cls.__setattr__) 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
189 def validated_setattr(instance: Any, field: str, value: str, /) -> None: 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
190 validator.validate_assignment(instance, field, value) 1abcdefghijklKLmnopqrstuvwxyzABCDEFGHIJ
192 cls.__setattr__ = validated_setattr.__get__(None, cls) # type: ignore 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
194 return True 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
197def is_builtin_dataclass(_cls: type[Any]) -> TypeGuard[type[StandardDataclass]]: 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
198 """Returns True if a class is a stdlib dataclass and *not* a pydantic dataclass.
200 We check that
201 - `_cls` is a dataclass
202 - `_cls` does not inherit from a processed pydantic dataclass (and thus have a `__pydantic_validator__`)
203 - `_cls` does not have any annotations that are not dataclass fields
204 e.g.
205 ```py
206 import dataclasses
208 import pydantic.dataclasses
210 @dataclasses.dataclass
211 class A:
212 x: int
214 @pydantic.dataclasses.dataclass
215 class B(A):
216 y: int
217 ```
218 In this case, when we first check `B`, we make an extra check and look at the annotations ('y'),
219 which won't be a superset of all the dataclass fields (only the stdlib fields i.e. 'x')
221 Args:
222 cls: The class.
224 Returns:
225 `True` if the class is a stdlib dataclass, `False` otherwise.
226 """
227 return ( 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ
228 dataclasses.is_dataclass(_cls)
229 and not hasattr(_cls, '__pydantic_validator__')
230 and set(_cls.__dataclass_fields__).issuperset(set(getattr(_cls, '__annotations__', {})))
231 )