Coverage for /pythoncovmergedfiles/medio/medio/src/pydantic/pydantic/_internal/_dataclasses.py: 26%
78 statements
« prev ^ index » next coverage.py v7.2.3, created at 2023-04-27 07:38 +0000
« prev ^ index » next coverage.py v7.2.3, created at 2023-04-27 07:38 +0000
1"""
2Private logic for creating pydantic dataclasses.
3"""
4from __future__ import annotations as _annotations
6import dataclasses
7import typing
8import warnings
9from functools import partial, wraps
10from typing import Any, Callable, ClassVar
12from pydantic_core import ArgsKwargs, SchemaSerializer, SchemaValidator, core_schema
13from typing_extensions import TypeGuard
15from ..errors import PydanticUndefinedAnnotation
16from ..fields import FieldInfo
17from . import _decorators, _typing_extra
18from ._fields import collect_dataclass_fields
19from ._generate_schema import GenerateSchema
20from ._generics import get_standard_typevars_map
21from ._model_construction import MockValidator
23if typing.TYPE_CHECKING:
24 from ..config import ConfigDict
25 from ._config import ConfigWrapper
27 class StandardDataclass(typing.Protocol):
28 __dataclass_fields__: ClassVar[dict[str, Any]]
29 __dataclass_params__: ClassVar[Any] # in reality `dataclasses._DataclassParams`
30 __post_init__: ClassVar[Callable[..., None]]
32 def __init__(self, *args: object, **kwargs: object) -> None:
33 pass
35 class PydanticDataclass(StandardDataclass, typing.Protocol):
36 __pydantic_core_schema__: typing.ClassVar[core_schema.CoreSchema]
37 __pydantic_validator__: typing.ClassVar[SchemaValidator]
38 __pydantic_serializer__: typing.ClassVar[SchemaSerializer]
39 __pydantic_decorators__: typing.ClassVar[_decorators.DecoratorInfos]
40 """metadata for `@validator`, `@root_validator` and `@serializer` decorators"""
41 __pydantic_fields__: typing.ClassVar[dict[str, FieldInfo]]
42 __pydantic_config__: typing.ClassVar[ConfigDict]
45def set_dataclass_fields(cls: type[StandardDataclass], types_namespace: dict[str, Any] | None = None) -> None:
46 """
47 Collect and set `cls.__pydantic_fields__`
48 """
49 typevars_map = get_standard_typevars_map(cls)
50 fields = collect_dataclass_fields(cls, types_namespace, typevars_map=typevars_map)
52 cls.__pydantic_fields__ = fields # type: ignore
55def complete_dataclass(
56 cls: type[Any],
57 config_wrapper: ConfigWrapper,
58 *,
59 raise_errors: bool = True,
60 types_namespace: dict[str, Any] | None = None,
61) -> bool:
62 """
63 Prepare a raw class to become a pydantic dataclass.
65 Returns `True` if the validation construction is successfully completed, else `False`.
67 This logic is called on a class which is yet to be wrapped in `dataclasses.dataclass()`.
68 """
69 cls_name = cls.__name__
71 if hasattr(cls, '__post_init_post_parse__'):
72 warnings.warn(
73 'Support for `__post_init_post_parse__` has been dropped, the method will not be called', DeprecationWarning
74 )
76 types_namespace = _typing_extra.get_cls_types_namespace(cls, types_namespace)
77 typevars_map = get_standard_typevars_map(cls)
78 gen_schema = GenerateSchema(
79 config_wrapper,
80 types_namespace,
81 typevars_map,
82 )
84 try:
85 get_core_schema = getattr(cls, '__get_pydantic_core_schema__', None)
86 if get_core_schema:
87 schema = get_core_schema(cls, partial(gen_schema.generate_schema, from_dunder_get_core_schema=False))
88 else:
89 schema = gen_schema.generate_schema(cls, False)
90 except PydanticUndefinedAnnotation as e:
91 if raise_errors:
92 raise
93 if config_wrapper.undefined_types_warning:
94 config_warning_string = (
95 f'`{cls_name}` has an undefined annotation: `{e.name}`. '
96 f'It may be possible to resolve this by setting '
97 f'undefined_types_warning=False in the config for `{cls_name}`.'
98 )
99 # FIXME UserWarning should not be raised here, but rather warned!
100 raise UserWarning(config_warning_string)
101 usage_warning_string = (
102 f'`{cls_name}` is not fully defined; you should define `{e.name}`, then call'
103 f' TODO! `methods.rebuild({cls_name})` before the first `{cls_name}` instance is created.' # <--- TODO
104 )
105 cls.__pydantic_validator__ = MockValidator( # type: ignore[assignment]
106 usage_warning_string, code='dataclass-not-fully-defined'
107 )
108 return False
110 core_config = config_wrapper.core_config(cls)
112 # We are about to set all the remaining required properties expected for this cast;
113 # __pydantic_decorators__ and __pydantic_fields__ should already be set
114 cls = typing.cast('type[PydanticDataclass]', cls)
115 # debug(schema)
116 cls.__pydantic_core_schema__ = schema
117 cls.__pydantic_validator__ = validator = SchemaValidator(schema, core_config)
118 cls.__pydantic_serializer__ = SchemaSerializer(schema, core_config)
119 # dataclasses only:
120 cls.__pydantic_config__ = config_wrapper.config_dict
122 if config_wrapper.validate_assignment:
124 @wraps(cls.__setattr__)
125 def validated_setattr(instance: Any, __field: str, __value: str) -> None:
126 validator.validate_assignment(instance, __field, __value)
128 cls.__setattr__ = validated_setattr.__get__(None, cls) # type: ignore
130 # dataclass.__init__ must be defined here so its `__qualname__` can be changed since functions can't copied.
132 def __init__(__dataclass_self__: PydanticDataclass, *args: Any, **kwargs: Any) -> None:
133 __tracebackhide__ = True
134 s = __dataclass_self__
135 s.__pydantic_validator__.validate_python(ArgsKwargs(args, kwargs), self_instance=s)
137 __init__.__qualname__ = f'{cls.__qualname__}.__init__'
138 cls.__init__ = __init__ # type: ignore
140 return True
143def is_builtin_dataclass(_cls: type[Any]) -> TypeGuard[type[StandardDataclass]]:
144 """
145 Whether a class is a stdlib dataclass
146 (useful to discriminated a pydantic dataclass that is actually a wrapper around a stdlib dataclass)
148 we check that
149 - `_cls` is a dataclass
150 - `_cls` is not a processed pydantic dataclass (with a basemodel attached)
151 - `_cls` is not a pydantic dataclass inheriting directly from a stdlib dataclass
152 e.g.
153 ```py
154 @dataclasses.dataclass
155 class A:
156 x: int
158 @pydantic.dataclasses.dataclass
159 class B(A):
160 y: int
161 ```
162 In this case, when we first check `B`, we make an extra check and look at the annotations ('y'),
163 which won't be a superset of all the dataclass fields (only the stdlib fields i.e. 'x')
164 """
165 return (
166 dataclasses.is_dataclass(_cls)
167 and not hasattr(_cls, '__pydantic_validator__')
168 and set(_cls.__dataclass_fields__).issuperset(set(getattr(_cls, '__annotations__', {})))
169 )
172def is_pydantic_dataclass(_cls: type[Any]) -> TypeGuard[type[PydanticDataclass]]:
173 return dataclasses.is_dataclass(_cls) and hasattr(_cls, '__pydantic_validator__')