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

1"""Private logic for creating pydantic dataclasses.""" 

2 

3from __future__ import annotations as _annotations 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ

4 

5import dataclasses 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ

6import typing 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ

7import warnings 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ

8from functools import partial, wraps 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ

9from typing import Any, Callable, ClassVar 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ

10 

11from pydantic_core import ( 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ

12 ArgsKwargs, 

13 SchemaSerializer, 

14 SchemaValidator, 

15 core_schema, 

16) 

17from typing_extensions import TypeGuard 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ

18 

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

30 

31if typing.TYPE_CHECKING: 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ

32 from ..config import ConfigDict 

33 

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]] 

38 

39 def __init__(self, *args: object, **kwargs: object) -> None: 

40 pass 

41 

42 class PydanticDataclass(StandardDataclass, typing.Protocol): 

43 """A protocol containing attributes only available once a class has been decorated as a Pydantic dataclass. 

44 

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 """ 

54 

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] 

62 

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

67 

68 

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__`. 

75 

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

83 

84 cls.__pydantic_fields__ = fields # type: ignore 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ

85 

86 

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. 

95 

96 This logic is called on a class which has already been wrapped in `dataclasses.dataclass()`. 

97 

98 This is somewhat analogous to `pydantic._internal._model_construction.complete_model_class`. 

99 

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. 

105 

106 Returns: 

107 `True` if building a pydantic dataclass is successfully completed, `False` otherwise. 

108 

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 ) 

116 

117 if types_namespace is None: 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ

118 types_namespace = _typing_extra.get_cls_types_namespace(cls) 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ

119 

120 set_dataclass_fields(cls, types_namespace, config_wrapper=config_wrapper) 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ

121 

122 typevars_map = get_standard_typevars_map(cls) 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ

123 gen_schema = GenerateSchema( 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ

124 config_wrapper, 

125 types_namespace, 

126 typevars_map, 

127 ) 

128 

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 ) 

136 

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

142 

143 __init__.__qualname__ = f'{cls.__qualname__}.__init__' 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ

144 

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

166 

167 core_config = config_wrapper.core_config(cls) 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ

168 

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 

174 

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) 

179 

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

185 

186 if config_wrapper.validate_assignment: 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ

187 

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

191 

192 cls.__setattr__ = validated_setattr.__get__(None, cls) # type: ignore 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ

193 

194 return True 1abcdefghijklKLmnopqrstuvwxMNOPQRSTUVyzABCDEFGHIJ

195 

196 

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. 

199 

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 

207 

208 import pydantic.dataclasses 

209 

210 @dataclasses.dataclass 

211 class A: 

212 x: int 

213 

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') 

220 

221 Args: 

222 cls: The class. 

223 

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 )