Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pydantic/dataclasses.py: 74%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

119 statements  

1"""Provide an enhanced dataclass that performs validation.""" 

2 

3from __future__ import annotations as _annotations 

4 

5import dataclasses 

6import sys 

7import types 

8from typing import TYPE_CHECKING, Any, Callable, Generic, Literal, NoReturn, TypeVar, overload 

9from warnings import warn 

10 

11from typing_extensions import TypeGuard, dataclass_transform 

12 

13from ._internal import _config, _decorators, _namespace_utils, _typing_extra 

14from ._internal import _dataclasses as _pydantic_dataclasses 

15from ._migration import getattr_migration 

16from .config import ConfigDict 

17from .errors import PydanticUserError 

18from .fields import Field, FieldInfo, PrivateAttr 

19 

20if TYPE_CHECKING: 

21 from ._internal._dataclasses import PydanticDataclass 

22 from ._internal._namespace_utils import MappingNamespace 

23 

24__all__ = 'dataclass', 'rebuild_dataclass' 

25 

26_T = TypeVar('_T') 

27 

28if sys.version_info >= (3, 10): 

29 

30 @dataclass_transform(field_specifiers=(dataclasses.field, Field, PrivateAttr)) 

31 @overload 

32 def dataclass( 

33 *, 

34 init: Literal[False] = False, 

35 repr: bool = True, 

36 eq: bool = True, 

37 order: bool = False, 

38 unsafe_hash: bool = False, 

39 frozen: bool = False, 

40 config: ConfigDict | type[object] | None = None, 

41 validate_on_init: bool | None = None, 

42 kw_only: bool = ..., 

43 slots: bool = ..., 

44 ) -> Callable[[type[_T]], type[PydanticDataclass]]: # type: ignore 

45 ... 

46 

47 @dataclass_transform(field_specifiers=(dataclasses.field, Field, PrivateAttr)) 

48 @overload 

49 def dataclass( 

50 _cls: type[_T], # type: ignore 

51 *, 

52 init: Literal[False] = False, 

53 repr: bool = True, 

54 eq: bool = True, 

55 order: bool = False, 

56 unsafe_hash: bool = False, 

57 frozen: bool | None = None, 

58 config: ConfigDict | type[object] | None = None, 

59 validate_on_init: bool | None = None, 

60 kw_only: bool = ..., 

61 slots: bool = ..., 

62 ) -> type[PydanticDataclass]: ... 

63 

64else: 

65 

66 @dataclass_transform(field_specifiers=(dataclasses.field, Field, PrivateAttr)) 

67 @overload 

68 def dataclass( 

69 *, 

70 init: Literal[False] = False, 

71 repr: bool = True, 

72 eq: bool = True, 

73 order: bool = False, 

74 unsafe_hash: bool = False, 

75 frozen: bool | None = None, 

76 config: ConfigDict | type[object] | None = None, 

77 validate_on_init: bool | None = None, 

78 ) -> Callable[[type[_T]], type[PydanticDataclass]]: # type: ignore 

79 ... 

80 

81 @dataclass_transform(field_specifiers=(dataclasses.field, Field, PrivateAttr)) 

82 @overload 

83 def dataclass( 

84 _cls: type[_T], # type: ignore 

85 *, 

86 init: Literal[False] = False, 

87 repr: bool = True, 

88 eq: bool = True, 

89 order: bool = False, 

90 unsafe_hash: bool = False, 

91 frozen: bool | None = None, 

92 config: ConfigDict | type[object] | None = None, 

93 validate_on_init: bool | None = None, 

94 ) -> type[PydanticDataclass]: ... 

95 

96 

97@dataclass_transform(field_specifiers=(dataclasses.field, Field, PrivateAttr)) 

98def dataclass( 

99 _cls: type[_T] | None = None, 

100 *, 

101 init: Literal[False] = False, 

102 repr: bool = True, 

103 eq: bool = True, 

104 order: bool = False, 

105 unsafe_hash: bool = False, 

106 frozen: bool | None = None, 

107 config: ConfigDict | type[object] | None = None, 

108 validate_on_init: bool | None = None, 

109 kw_only: bool = False, 

110 slots: bool = False, 

111) -> Callable[[type[_T]], type[PydanticDataclass]] | type[PydanticDataclass]: 

112 """!!! abstract "Usage Documentation" 

113 [`dataclasses`](../concepts/dataclasses.md) 

114 

115 A decorator used to create a Pydantic-enhanced dataclass, similar to the standard Python `dataclass`, 

116 but with added validation. 

117 

118 This function should be used similarly to `dataclasses.dataclass`. 

119 

120 Args: 

121 _cls: The target `dataclass`. 

122 init: Included for signature compatibility with `dataclasses.dataclass`, and is passed through to 

123 `dataclasses.dataclass` when appropriate. If specified, must be set to `False`, as pydantic inserts its 

124 own `__init__` function. 

125 repr: A boolean indicating whether to include the field in the `__repr__` output. 

126 eq: Determines if a `__eq__` method should be generated for the class. 

127 order: Determines if comparison magic methods should be generated, such as `__lt__`, but not `__eq__`. 

128 unsafe_hash: Determines if a `__hash__` method should be included in the class, as in `dataclasses.dataclass`. 

129 frozen: Determines if the generated class should be a 'frozen' `dataclass`, which does not allow its 

130 attributes to be modified after it has been initialized. If not set, the value from the provided `config` argument will be used (and will default to `False` otherwise). 

131 config: The Pydantic config to use for the `dataclass`. 

132 validate_on_init: A deprecated parameter included for backwards compatibility; in V2, all Pydantic dataclasses 

133 are validated on init. 

134 kw_only: Determines if `__init__` method parameters must be specified by keyword only. Defaults to `False`. 

135 slots: Determines if the generated class should be a 'slots' `dataclass`, which does not allow the addition of 

136 new attributes after instantiation. 

137 

138 Returns: 

139 A decorator that accepts a class as its argument and returns a Pydantic `dataclass`. 

140 

141 Raises: 

142 AssertionError: Raised if `init` is not `False` or `validate_on_init` is `False`. 

143 """ 

144 assert init is False, 'pydantic.dataclasses.dataclass only supports init=False' 

145 assert validate_on_init is not False, 'validate_on_init=False is no longer supported' 

146 

147 if sys.version_info >= (3, 10): 

148 kwargs = {'kw_only': kw_only, 'slots': slots} 

149 else: 

150 kwargs = {} 

151 

152 def make_pydantic_fields_compatible(cls: type[Any]) -> None: 

153 """Make sure that stdlib `dataclasses` understands `Field` kwargs like `kw_only` 

154 To do that, we simply change 

155 `x: int = pydantic.Field(..., kw_only=True)` 

156 into 

157 `x: int = dataclasses.field(default=pydantic.Field(..., kw_only=True), kw_only=True)` 

158 """ 

159 for annotation_cls in cls.__mro__: 

160 annotations: dict[str, Any] = getattr(annotation_cls, '__annotations__', {}) 

161 for field_name in annotations: 

162 field_value = getattr(cls, field_name, None) 

163 # Process only if this is an instance of `FieldInfo`. 

164 if not isinstance(field_value, FieldInfo): 

165 continue 

166 

167 # Initialize arguments for the standard `dataclasses.field`. 

168 field_args: dict = {'default': field_value} 

169 

170 # Handle `kw_only` for Python 3.10+ 

171 if sys.version_info >= (3, 10) and field_value.kw_only: 

172 field_args['kw_only'] = True 

173 

174 # Set `repr` attribute if it's explicitly specified to be not `True`. 

175 if field_value.repr is not True: 

176 field_args['repr'] = field_value.repr 

177 

178 setattr(cls, field_name, dataclasses.field(**field_args)) 

179 # In Python 3.9, when subclassing, information is pulled from cls.__dict__['__annotations__'] 

180 # for annotations, so we must make sure it's initialized before we add to it. 

181 if cls.__dict__.get('__annotations__') is None: 

182 cls.__annotations__ = {} 

183 cls.__annotations__[field_name] = annotations[field_name] 

184 

185 def create_dataclass(cls: type[Any]) -> type[PydanticDataclass]: 

186 """Create a Pydantic dataclass from a regular dataclass. 

187 

188 Args: 

189 cls: The class to create the Pydantic dataclass from. 

190 

191 Returns: 

192 A Pydantic dataclass. 

193 """ 

194 from ._internal._utils import is_model_class 

195 

196 if is_model_class(cls): 

197 raise PydanticUserError( 

198 f'Cannot create a Pydantic dataclass from {cls.__name__} as it is already a Pydantic model', 

199 code='dataclass-on-model', 

200 ) 

201 

202 original_cls = cls 

203 

204 # we warn on conflicting config specifications, but only if the class doesn't have a dataclass base 

205 # because a dataclass base might provide a __pydantic_config__ attribute that we don't want to warn about 

206 has_dataclass_base = any(dataclasses.is_dataclass(base) for base in cls.__bases__) 

207 if not has_dataclass_base and config is not None and hasattr(cls, '__pydantic_config__'): 

208 warn( 

209 f'`config` is set via both the `dataclass` decorator and `__pydantic_config__` for dataclass {cls.__name__}. ' 

210 f'The `config` specification from `dataclass` decorator will take priority.', 

211 category=UserWarning, 

212 stacklevel=2, 

213 ) 

214 

215 # if config is not explicitly provided, try to read it from the type 

216 config_dict = config if config is not None else getattr(cls, '__pydantic_config__', None) 

217 config_wrapper = _config.ConfigWrapper(config_dict) 

218 decorators = _decorators.DecoratorInfos.build(cls) 

219 

220 # Keep track of the original __doc__ so that we can restore it after applying the dataclasses decorator 

221 # Otherwise, classes with no __doc__ will have their signature added into the JSON schema description, 

222 # since dataclasses.dataclass will set this as the __doc__ 

223 original_doc = cls.__doc__ 

224 

225 if _pydantic_dataclasses.is_builtin_dataclass(cls): 

226 # Don't preserve the docstring for vanilla dataclasses, as it may include the signature 

227 # This matches v1 behavior, and there was an explicit test for it 

228 original_doc = None 

229 

230 # We don't want to add validation to the existing std lib dataclass, so we will subclass it 

231 # If the class is generic, we need to make sure the subclass also inherits from Generic 

232 # with all the same parameters. 

233 bases = (cls,) 

234 if issubclass(cls, Generic): 

235 generic_base = Generic[cls.__parameters__] # type: ignore 

236 bases = bases + (generic_base,) 

237 cls = types.new_class(cls.__name__, bases) 

238 

239 make_pydantic_fields_compatible(cls) 

240 

241 # Respect frozen setting from dataclass constructor and fallback to config setting if not provided 

242 if frozen is not None: 

243 frozen_ = frozen 

244 if config_wrapper.frozen: 

245 # It's not recommended to define both, as the setting from the dataclass decorator will take priority. 

246 warn( 

247 f'`frozen` is set via both the `dataclass` decorator and `config` for dataclass {cls.__name__!r}.' 

248 'This is not recommended. The `frozen` specification on `dataclass` will take priority.', 

249 category=UserWarning, 

250 stacklevel=2, 

251 ) 

252 else: 

253 frozen_ = config_wrapper.frozen or False 

254 

255 cls = dataclasses.dataclass( # type: ignore[call-overload] 

256 cls, 

257 # the value of init here doesn't affect anything except that it makes it easier to generate a signature 

258 init=True, 

259 repr=repr, 

260 eq=eq, 

261 order=order, 

262 unsafe_hash=unsafe_hash, 

263 frozen=frozen_, 

264 **kwargs, 

265 ) 

266 

267 # This is an undocumented attribute to distinguish stdlib/Pydantic dataclasses. 

268 # It should be set as early as possible: 

269 cls.__is_pydantic_dataclass__ = True 

270 

271 cls.__pydantic_decorators__ = decorators # type: ignore 

272 cls.__doc__ = original_doc 

273 cls.__module__ = original_cls.__module__ 

274 cls.__qualname__ = original_cls.__qualname__ 

275 cls.__pydantic_fields_complete__ = classmethod(_pydantic_fields_complete) 

276 cls.__pydantic_complete__ = False # `complete_dataclass` will set it to `True` if successful. 

277 # TODO `parent_namespace` is currently None, but we could do the same thing as Pydantic models: 

278 # fetch the parent ns using `parent_frame_namespace` (if the dataclass was defined in a function), 

279 # and possibly cache it (see the `__pydantic_parent_namespace__` logic for models). 

280 _pydantic_dataclasses.complete_dataclass(cls, config_wrapper, raise_errors=False) 

281 return cls 

282 

283 return create_dataclass if _cls is None else create_dataclass(_cls) 

284 

285 

286def _pydantic_fields_complete(cls: type[PydanticDataclass]) -> bool: 

287 """Return whether the fields where successfully collected (i.e. type hints were successfully resolves). 

288 

289 This is a private property, not meant to be used outside Pydantic. 

290 """ 

291 return all(field_info._complete for field_info in cls.__pydantic_fields__.values()) 

292 

293 

294__getattr__ = getattr_migration(__name__) 

295 

296if sys.version_info < (3, 11): 

297 # Monkeypatch dataclasses.InitVar so that typing doesn't error if it occurs as a type when evaluating type hints 

298 # Starting in 3.11, typing.get_type_hints will not raise an error if the retrieved type hints are not callable. 

299 

300 def _call_initvar(*args: Any, **kwargs: Any) -> NoReturn: 

301 """This function does nothing but raise an error that is as similar as possible to what you'd get 

302 if you were to try calling `InitVar[int]()` without this monkeypatch. The whole purpose is just 

303 to ensure typing._type_check does not error if the type hint evaluates to `InitVar[<parameter>]`. 

304 """ 

305 raise TypeError("'InitVar' object is not callable") 

306 

307 dataclasses.InitVar.__call__ = _call_initvar 

308 

309 

310def rebuild_dataclass( 

311 cls: type[PydanticDataclass], 

312 *, 

313 force: bool = False, 

314 raise_errors: bool = True, 

315 _parent_namespace_depth: int = 2, 

316 _types_namespace: MappingNamespace | None = None, 

317) -> bool | None: 

318 """Try to rebuild the pydantic-core schema for the dataclass. 

319 

320 This may be necessary when one of the annotations is a ForwardRef which could not be resolved during 

321 the initial attempt to build the schema, and automatic rebuilding fails. 

322 

323 This is analogous to `BaseModel.model_rebuild`. 

324 

325 Args: 

326 cls: The class to rebuild the pydantic-core schema for. 

327 force: Whether to force the rebuilding of the schema, defaults to `False`. 

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

329 _parent_namespace_depth: The depth level of the parent namespace, defaults to 2. 

330 _types_namespace: The types namespace, defaults to `None`. 

331 

332 Returns: 

333 Returns `None` if the schema is already "complete" and rebuilding was not required. 

334 If rebuilding _was_ required, returns `True` if rebuilding was successful, otherwise `False`. 

335 """ 

336 if not force and cls.__pydantic_complete__: 

337 return None 

338 

339 for attr in ('__pydantic_core_schema__', '__pydantic_validator__', '__pydantic_serializer__'): 

340 if attr in cls.__dict__: 

341 # Deleting the validator/serializer is necessary as otherwise they can get reused in 

342 # pydantic-core. Same applies for the core schema that can be reused in schema generation. 

343 delattr(cls, attr) 

344 

345 cls.__pydantic_complete__ = False 

346 

347 if _types_namespace is not None: 

348 rebuild_ns = _types_namespace 

349 elif _parent_namespace_depth > 0: 

350 rebuild_ns = _typing_extra.parent_frame_namespace(parent_depth=_parent_namespace_depth, force=True) or {} 

351 else: 

352 rebuild_ns = {} 

353 

354 ns_resolver = _namespace_utils.NsResolver( 

355 parent_namespace=rebuild_ns, 

356 ) 

357 

358 return _pydantic_dataclasses.complete_dataclass( 

359 cls, 

360 _config.ConfigWrapper(cls.__pydantic_config__, check=False), 

361 raise_errors=raise_errors, 

362 ns_resolver=ns_resolver, 

363 # We could provide a different config instead (with `'defer_build'` set to `True`) 

364 # of this explicit `_force_build` argument, but because config can come from the 

365 # decorator parameter or the `__pydantic_config__` attribute, `complete_dataclass` 

366 # will overwrite `__pydantic_config__` with the provided config above: 

367 _force_build=True, 

368 ) 

369 

370 

371def is_pydantic_dataclass(class_: type[Any], /) -> TypeGuard[type[PydanticDataclass]]: 

372 """Whether a class is a pydantic dataclass. 

373 

374 Args: 

375 class_: The class. 

376 

377 Returns: 

378 `True` if the class is a pydantic dataclass, `False` otherwise. 

379 """ 

380 try: 

381 return '__is_pydantic_dataclass__' in class_.__dict__ and dataclasses.is_dataclass(class_) 

382 except AttributeError: 

383 return False