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

99 statements  

« prev     ^ index     » next       coverage.py v7.10.0, created at 2025-07-26 11:49 +0000

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

2 

3from __future__ import annotations as _annotations 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

4 

5import copy 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

6import dataclasses 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

7import sys 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

8import typing 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

9import warnings 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

10from collections.abc import Generator 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

11from contextlib import contextmanager 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

12from functools import partial 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

13from typing import Any, ClassVar, cast 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

14 

15from pydantic_core import ( 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

16 ArgsKwargs, 

17 SchemaSerializer, 

18 SchemaValidator, 

19 core_schema, 

20) 

21from typing_extensions import TypeAlias, TypeIs 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

22 

23from ..errors import PydanticUndefinedAnnotation 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

24from ..fields import FieldInfo 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

25from ..plugin._schema_validator import PluggableSchemaValidator, create_schema_validator 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

26from ..warnings import PydanticDeprecatedSince20 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

27from . import _config, _decorators 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

28from ._fields import collect_dataclass_fields 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

29from ._generate_schema import GenerateSchema, InvalidSchemaError 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

30from ._generics import get_standard_typevars_map 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

31from ._mock_val_ser import set_dataclass_mocks 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

32from ._namespace_utils import NsResolver 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

33from ._signature import generate_pydantic_signature 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

34from ._utils import LazyClassAttribute 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

35 

36if typing.TYPE_CHECKING: 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

37 from _typeshed import DataclassInstance as StandardDataclass 

38 

39 from ..config import ConfigDict 

40 

41 class PydanticDataclass(StandardDataclass, typing.Protocol): 1r

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

43 

44 Attributes: 

45 __pydantic_config__: Pydantic-specific configuration settings for the dataclass. 

46 __pydantic_complete__: Whether dataclass building is completed, or if there are still undefined fields. 

47 __pydantic_core_schema__: The pydantic-core schema used to build the SchemaValidator and SchemaSerializer. 

48 __pydantic_decorators__: Metadata containing the decorators defined on the dataclass. 

49 __pydantic_fields__: Metadata about the fields defined on the dataclass. 

50 __pydantic_serializer__: The pydantic-core SchemaSerializer used to dump instances of the dataclass. 

51 __pydantic_validator__: The pydantic-core SchemaValidator used to validate instances of the dataclass. 

52 """ 

53 

54 __pydantic_config__: ClassVar[ConfigDict] 

55 __pydantic_complete__: ClassVar[bool] 

56 __pydantic_core_schema__: ClassVar[core_schema.CoreSchema] 

57 __pydantic_decorators__: ClassVar[_decorators.DecoratorInfos] 

58 __pydantic_fields__: ClassVar[dict[str, FieldInfo]] 

59 __pydantic_serializer__: ClassVar[SchemaSerializer] 

60 __pydantic_validator__: ClassVar[SchemaValidator | PluggableSchemaValidator] 

61 

62 @classmethod 

63 def __pydantic_fields_complete__(cls) -> bool: ... 

64 

65else: 

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

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

68 DeprecationWarning = PydanticDeprecatedSince20 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

69 

70 

71def set_dataclass_fields( 1IJjklmnopqabcKLstuvwxyzdefPMNABCDEFGHghi

72 cls: type[StandardDataclass], 

73 config_wrapper: _config.ConfigWrapper, 

74 ns_resolver: NsResolver | None = None, 

75) -> None: 

76 """Collect and set `cls.__pydantic_fields__`. 

77 

78 Args: 

79 cls: The class. 

80 config_wrapper: The config wrapper instance. 

81 ns_resolver: Namespace resolver to use when getting dataclass annotations. 

82 """ 

83 typevars_map = get_standard_typevars_map(cls) 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

84 fields = collect_dataclass_fields( 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

85 cls, ns_resolver=ns_resolver, typevars_map=typevars_map, config_wrapper=config_wrapper 

86 ) 

87 

88 cls.__pydantic_fields__ = fields # type: ignore 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

89 

90 

91def complete_dataclass( 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

92 cls: type[Any], 

93 config_wrapper: _config.ConfigWrapper, 

94 *, 

95 raise_errors: bool = True, 

96 ns_resolver: NsResolver | None = None, 

97 _force_build: bool = False, 

98) -> bool: 

99 """Finish building a pydantic dataclass. 

100 

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

102 

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

104 

105 Args: 

106 cls: The class. 

107 config_wrapper: The config wrapper instance. 

108 raise_errors: Whether to raise errors, defaults to `True`. 

109 ns_resolver: The namespace resolver instance to use when collecting dataclass fields 

110 and during schema building. 

111 _force_build: Whether to force building the dataclass, no matter if 

112 [`defer_build`][pydantic.config.ConfigDict.defer_build] is set. 

113 

114 Returns: 

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

116 

117 Raises: 

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

119 """ 

120 original_init = cls.__init__ 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

121 

122 # dataclass.__init__ must be defined here so its `__qualname__` can be changed since functions can't be copied, 

123 # and so that the mock validator is used if building was deferred: 

124 def __init__(__dataclass_self__: PydanticDataclass, *args: Any, **kwargs: Any) -> None: 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

125 __tracebackhide__ = True 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

126 s = __dataclass_self__ 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

127 s.__pydantic_validator__.validate_python(ArgsKwargs(args, kwargs), self_instance=s) 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

128 

129 __init__.__qualname__ = f'{cls.__qualname__}.__init__' 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

130 

131 cls.__init__ = __init__ # type: ignore 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

132 cls.__pydantic_config__ = config_wrapper.config_dict # type: ignore 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

133 

134 set_dataclass_fields(cls, config_wrapper=config_wrapper, ns_resolver=ns_resolver) 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

135 

136 if not _force_build and config_wrapper.defer_build: 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

137 set_dataclass_mocks(cls) 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi

138 return False 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi

139 

140 if hasattr(cls, '__post_init_post_parse__'): 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

141 warnings.warn( 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi

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

143 ) 

144 

145 typevars_map = get_standard_typevars_map(cls) 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

146 gen_schema = GenerateSchema( 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

147 config_wrapper, 

148 ns_resolver=ns_resolver, 

149 typevars_map=typevars_map, 

150 ) 

151 

152 # set __signature__ attr only for the class, but not for its instances 

153 # (because instances can define `__call__`, and `inspect.signature` shouldn't 

154 # use the `__signature__` attribute and instead generate from `__call__`). 

155 cls.__signature__ = LazyClassAttribute( 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

156 '__signature__', 

157 partial( 

158 generate_pydantic_signature, 

159 # It's important that we reference the `original_init` here, 

160 # as it is the one synthesized by the stdlib `dataclass` module: 

161 init=original_init, 

162 fields=cls.__pydantic_fields__, # type: ignore 

163 validate_by_name=config_wrapper.validate_by_name, 

164 extra=config_wrapper.extra, 

165 is_dataclass=True, 

166 ), 

167 ) 

168 

169 try: 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

170 schema = gen_schema.generate_schema(cls) 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

171 except PydanticUndefinedAnnotation as e: 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi

172 if raise_errors: 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi

173 raise 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi

174 set_dataclass_mocks(cls, f'`{e.name}`') 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi

175 return False 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi

176 

177 core_config = config_wrapper.core_config(title=cls.__name__) 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

178 

179 try: 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

180 schema = gen_schema.clean_schema(schema) 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

181 except InvalidSchemaError: 

182 set_dataclass_mocks(cls) 

183 return False 

184 

185 # We are about to set all the remaining required properties expected for this cast; 

186 # __pydantic_decorators__ and __pydantic_fields__ should already be set 

187 cls = typing.cast('type[PydanticDataclass]', cls) 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

188 

189 cls.__pydantic_core_schema__ = schema 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

190 cls.__pydantic_validator__ = create_schema_validator( 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

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

192 ) 

193 cls.__pydantic_serializer__ = SchemaSerializer(schema, core_config) 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

194 cls.__pydantic_complete__ = True 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

195 return True 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

196 

197 

198def is_stdlib_dataclass(cls: type[Any], /) -> TypeIs[type[StandardDataclass]]: 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

199 """Returns `True` if the class is a stdlib dataclass and *not* a Pydantic dataclass. 

200 

201 Unlike the stdlib `dataclasses.is_dataclass()` function, this does *not* include subclasses 

202 of a dataclass that are themselves not dataclasses. 

203 

204 Args: 

205 cls: The class. 

206 

207 Returns: 

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

209 """ 

210 return '__dataclass_fields__' in cls.__dict__ and not hasattr(cls, '__pydantic_validator__') 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

211 

212 

213def as_dataclass_field(pydantic_field: FieldInfo) -> dataclasses.Field[Any]: 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

214 field_args: dict[str, Any] = {'default': pydantic_field} 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi

215 

216 # Needed because if `doc` is set, the dataclass slots will be a dict (field name -> doc) instead of a tuple: 

217 if sys.version_info >= (3, 14) and pydantic_field.description is not None: 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi

218 field_args['doc'] = pydantic_field.description 1abcdefghi

219 

220 # Needed as the stdlib dataclass module processes kw_only in a specific way during class construction: 

221 if sys.version_info >= (3, 10) and pydantic_field.kw_only: 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi

222 field_args['kw_only'] = True 1jklmnopqabcrstuvwxyzdefABCDEFGHghi

223 

224 # Needed as the stdlib dataclass modules generates `__repr__()` during class construction: 

225 if pydantic_field.repr is not True: 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi

226 field_args['repr'] = pydantic_field.repr 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi

227 

228 return dataclasses.field(**field_args) 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi

229 

230 

231DcFields: TypeAlias = dict[str, dataclasses.Field[Any]] 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

232 

233 

234@contextmanager 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

235def patch_base_fields(cls: type[Any]) -> Generator[None]: 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

236 """Temporarily patch the stdlib dataclasses bases of `cls` if the Pydantic `Field()` function is used. 

237 

238 When creating a Pydantic dataclass, it is possible to inherit from stdlib dataclasses, where 

239 the Pydantic `Field()` function is used. To create this Pydantic dataclass, we first apply 

240 the stdlib `@dataclass` decorator on it. During the construction of the stdlib dataclass, 

241 the `kw_only` and `repr` field arguments need to be understood by the stdlib *during* the 

242 dataclass construction. To do so, we temporarily patch the fields dictionary of the affected 

243 bases. 

244 

245 For instance, with the following example: 

246 

247 ```python {test="skip" lint="skip"} 

248 import dataclasses as stdlib_dc 

249 

250 import pydantic 

251 import pydantic.dataclasses as pydantic_dc 

252 

253 @stdlib_dc.dataclass 

254 class A: 

255 a: int = pydantic.Field(repr=False) 

256 

257 # Notice that the `repr` attribute of the dataclass field is `True`: 

258 A.__dataclass_fields__['a'] 

259 #> dataclass.Field(default=FieldInfo(repr=False), repr=True, ...) 

260 

261 @pydantic_dc.dataclass 

262 class B(A): 

263 b: int = pydantic.Field(repr=False) 

264 ``` 

265 

266 When passing `B` to the stdlib `@dataclass` decorator, it will look for fields in the parent classes 

267 and reuse them directly. When this context manager is active, `A` will be temporarily patched to be 

268 equivalent to: 

269 

270 ```python {test="skip" lint="skip"} 

271 @stdlib_dc.dataclass 

272 class A: 

273 a: int = stdlib_dc.field(default=Field(repr=False), repr=False) 

274 ``` 

275 

276 !!! note 

277 This is only applied to the bases of `cls`, and not `cls` itself. The reason is that the Pydantic 

278 dataclass decorator "owns" `cls` (in the previous example, `B`). As such, we instead modify the fields 

279 directly (in the previous example, we simply do `setattr(B, 'b', as_dataclass_field(pydantic_field))`). 

280 

281 !!! note 

282 This approach is far from ideal, and can probably be the source of unwanted side effects/race conditions. 

283 The previous implemented approach was mutating the `__annotations__` dict of `cls`, which is no longer a 

284 safe operation in Python 3.14+, and resulted in unexpected behavior with field ordering anyway. 

285 """ 

286 # A list of two-tuples, the first element being a reference to the 

287 # dataclass fields dictionary, the second element being a mapping between 

288 # the field names that were modified, and their original `Field`: 

289 original_fields_list: list[tuple[DcFields, DcFields]] = [] 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

290 

291 for base in cls.__mro__[1:]: 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

292 dc_fields: dict[str, dataclasses.Field[Any]] = base.__dict__.get('__dataclass_fields__', {}) 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

293 dc_fields_with_pydantic_field_defaults = { 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

294 field_name: field 

295 for field_name, field in dc_fields.items() 

296 if isinstance(field.default, FieldInfo) 

297 # Only do the patching if one of the affected attributes is set: 

298 and (field.default.description is not None or field.default.kw_only or field.default.repr is not True) 

299 } 

300 if dc_fields_with_pydantic_field_defaults: 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

301 original_fields_list.append((dc_fields, dc_fields_with_pydantic_field_defaults)) 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi

302 for field_name, field in dc_fields_with_pydantic_field_defaults.items(): 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi

303 default = cast(FieldInfo, field.default) 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi

304 # `dataclasses.Field` isn't documented as working with `copy.copy()`. 

305 # It is a class with `__slots__`, so should work (and we hope for the best): 

306 new_dc_field = copy.copy(field) 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi

307 # For base fields, no need to set `doc` from `FieldInfo.description`, this is only relevant 

308 # for the class under construction and handled in `as_dataclass_field()`. 

309 if sys.version_info >= (3, 10) and default.kw_only: 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi

310 new_dc_field.kw_only = True 1jklmnopqabcrstuvwxyzdefABCDEFGHghi

311 if default.repr is not True: 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi

312 new_dc_field.repr = default.repr 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi

313 dc_fields[field_name] = new_dc_field 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi

314 

315 try: 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

316 yield 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

317 finally: 

318 for fields, original_fields in original_fields_list: 1IJjklmnopqabcOrKLstuvwxyzdefPMNABCDEFGHghi

319 for field_name, original_field in original_fields.items(): 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi

320 fields[field_name] = original_field 1IJjklmnopqabcOrKLstuvwxyzdefMNABCDEFGHghi