Coverage for pydantic/_internal/_dataclasses.py: 96.05%

68 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-02-13 19:35 +0000

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

2 

3from __future__ import annotations as _annotations 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

4 

5import dataclasses 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

6import typing 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

7import warnings 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

8from functools import partial, wraps 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

9from typing import Any, ClassVar 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

10 

11from pydantic_core import ( 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

12 ArgsKwargs, 

13 SchemaSerializer, 

14 SchemaValidator, 

15 core_schema, 

16) 

17from typing_extensions import TypeGuard 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

18 

19from ..errors import PydanticUndefinedAnnotation 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

20from ..plugin._schema_validator import PluggableSchemaValidator, create_schema_validator 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

21from ..warnings import PydanticDeprecatedSince20 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

22from . import _config, _decorators 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

23from ._fields import collect_dataclass_fields 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

24from ._generate_schema import GenerateSchema, InvalidSchemaError 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

25from ._generics import get_standard_typevars_map 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

26from ._mock_val_ser import set_dataclass_mocks 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

27from ._namespace_utils import NsResolver 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

28from ._signature import generate_pydantic_signature 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

29from ._utils import LazyClassAttribute 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

30 

31if typing.TYPE_CHECKING: 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

32 from _typeshed import DataclassInstance as StandardDataclass 

33 

34 from ..config import ConfigDict 

35 from ..fields import FieldInfo 

36 

37 class PydanticDataclass(StandardDataclass, typing.Protocol): 

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

39 

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

49 

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] 

57 

58else: 

59 # See PyCharm issues https://youtrack.jetbrains.com/issue/PY-21915 

60 # and https://youtrack.jetbrains.com/issue/PY-51428 

61 DeprecationWarning = PydanticDeprecatedSince20 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

62 

63 

64def set_dataclass_fields( 1abcdefghijklmnopqrstGHIJKLMuvwxyzABCD

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

70 

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) 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

77 fields = collect_dataclass_fields( 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

78 cls, ns_resolver=ns_resolver, typevars_map=typevars_map, config_wrapper=config_wrapper 

79 ) 

80 

81 cls.__pydantic_fields__ = fields # type: ignore 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

82 

83 

84def complete_dataclass( 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

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. 

93 

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

95 

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

97 

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. 

106 

107 Returns: 

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

109 

110 Raises: 

111 PydanticUndefinedAnnotation: If `raise_error` is `True` and there is an undefined annotations. 

112 """ 

113 original_init = cls.__init__ 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

114 

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: 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

118 __tracebackhide__ = True 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

119 s = __dataclass_self__ 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

120 s.__pydantic_validator__.validate_python(ArgsKwargs(args, kwargs), self_instance=s) 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

121 

122 __init__.__qualname__ = f'{cls.__qualname__}.__init__' 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

123 

124 cls.__init__ = __init__ # type: ignore 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

125 cls.__pydantic_config__ = config_wrapper.config_dict # type: ignore 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

126 

127 set_dataclass_fields(cls, ns_resolver, config_wrapper=config_wrapper) 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

128 

129 if not _force_build and config_wrapper.defer_build: 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

130 set_dataclass_mocks(cls) 1abcdefghijEFklmnopqrstuvwxyzABCD

131 return False 1abcdefghijEFklmnopqrstuvwxyzABCD

132 

133 if hasattr(cls, '__post_init_post_parse__'): 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

134 warnings.warn( 1abcdefghijEFklmnopqrstuvwxyzABCD

135 'Support for `__post_init_post_parse__` has been dropped, the method will not be called', DeprecationWarning 

136 ) 

137 

138 typevars_map = get_standard_typevars_map(cls) 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

139 gen_schema = GenerateSchema( 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

140 config_wrapper, 

141 ns_resolver=ns_resolver, 

142 typevars_map=typevars_map, 

143 ) 

144 

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( 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

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 populate_by_name=config_wrapper.populate_by_name, 

157 extra=config_wrapper.extra, 

158 is_dataclass=True, 

159 ), 

160 ) 

161 

162 try: 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

163 schema = gen_schema.generate_schema(cls) 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

164 except PydanticUndefinedAnnotation as e: 1abcdefghijEFklmnopqrstuvwxyzABCD

165 if raise_errors: 1abcdefghijEFklmnopqrstuvwxyzABCD

166 raise 1abcdefghijEFklmnopqrstuvwxyzABCD

167 set_dataclass_mocks(cls, f'`{e.name}`') 1abcdefghijEFklmnopqrstuvwxyzABCD

168 return False 1abcdefghijEFklmnopqrstuvwxyzABCD

169 

170 core_config = config_wrapper.core_config(title=cls.__name__) 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

171 

172 try: 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

173 schema = gen_schema.clean_schema(schema) 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

174 except InvalidSchemaError: 

175 set_dataclass_mocks(cls) 

176 return False 

177 

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) 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

181 # debug(schema) 

182 

183 cls.__pydantic_core_schema__ = schema 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

184 cls.__pydantic_validator__ = validator = create_schema_validator( 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

185 schema, cls, cls.__module__, cls.__qualname__, 'dataclass', core_config, config_wrapper.plugin_settings 

186 ) 

187 cls.__pydantic_serializer__ = SchemaSerializer(schema, core_config) 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

188 

189 if config_wrapper.validate_assignment: 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

190 

191 @wraps(cls.__setattr__) 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

192 def validated_setattr(instance: Any, field: str, value: str, /) -> None: 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

193 validator.validate_assignment(instance, field, value) 1abcdefghijEFklmnopqrstuvwxyzABCD

194 

195 cls.__setattr__ = validated_setattr.__get__(None, cls) # type: ignore 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

196 

197 cls.__pydantic_complete__ = True 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

198 return True 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

199 

200 

201def is_builtin_dataclass(_cls: type[Any]) -> TypeGuard[type[StandardDataclass]]: 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

202 """Returns True if a class is a stdlib dataclass and *not* a pydantic dataclass. 

203 

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 

211 

212 import pydantic.dataclasses 

213 

214 @dataclasses.dataclass 

215 class A: 

216 x: int 

217 

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

224 

225 Args: 

226 cls: The class. 

227 

228 Returns: 

229 `True` if the class is a stdlib dataclass, `False` otherwise. 

230 """ 

231 return ( 1abcdefghijEFklmnopqrstGHIJKLMuvwxyzABCD

232 dataclasses.is_dataclass(_cls) 

233 and not hasattr(_cls, '__pydantic_validator__') 

234 and set(_cls.__dataclass_fields__).issuperset(set(getattr(_cls, '__annotations__', {}))) 

235 )