dataclasses.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. """Provide an enhanced dataclass that performs validation."""
  2. from __future__ import annotations as _annotations
  3. import dataclasses
  4. import functools
  5. import sys
  6. import types
  7. from typing import TYPE_CHECKING, Any, Callable, Generic, Literal, NoReturn, TypeVar, overload
  8. from warnings import warn
  9. from typing_extensions import TypeGuard, dataclass_transform
  10. from ._internal import _config, _decorators, _mock_val_ser, _namespace_utils, _typing_extra
  11. from ._internal import _dataclasses as _pydantic_dataclasses
  12. from ._migration import getattr_migration
  13. from .config import ConfigDict
  14. from .errors import PydanticUserError
  15. from .fields import Field, FieldInfo, PrivateAttr
  16. if TYPE_CHECKING:
  17. from ._internal._dataclasses import PydanticDataclass
  18. from ._internal._namespace_utils import MappingNamespace
  19. __all__ = 'dataclass', 'rebuild_dataclass'
  20. _T = TypeVar('_T')
  21. if sys.version_info >= (3, 10):
  22. @dataclass_transform(field_specifiers=(dataclasses.field, Field, PrivateAttr))
  23. @overload
  24. def dataclass(
  25. *,
  26. init: Literal[False] = False,
  27. repr: bool = True,
  28. eq: bool = True,
  29. order: bool = False,
  30. unsafe_hash: bool = False,
  31. frozen: bool = False,
  32. config: ConfigDict | type[object] | None = None,
  33. validate_on_init: bool | None = None,
  34. kw_only: bool = ...,
  35. slots: bool = ...,
  36. ) -> Callable[[type[_T]], type[PydanticDataclass]]: # type: ignore
  37. ...
  38. @dataclass_transform(field_specifiers=(dataclasses.field, Field, PrivateAttr))
  39. @overload
  40. def dataclass(
  41. _cls: type[_T], # type: ignore
  42. *,
  43. init: Literal[False] = False,
  44. repr: bool = True,
  45. eq: bool = True,
  46. order: bool = False,
  47. unsafe_hash: bool = False,
  48. frozen: bool | None = None,
  49. config: ConfigDict | type[object] | None = None,
  50. validate_on_init: bool | None = None,
  51. kw_only: bool = ...,
  52. slots: bool = ...,
  53. ) -> type[PydanticDataclass]: ...
  54. else:
  55. @dataclass_transform(field_specifiers=(dataclasses.field, Field, PrivateAttr))
  56. @overload
  57. def dataclass(
  58. *,
  59. init: Literal[False] = False,
  60. repr: bool = True,
  61. eq: bool = True,
  62. order: bool = False,
  63. unsafe_hash: bool = False,
  64. frozen: bool | None = None,
  65. config: ConfigDict | type[object] | None = None,
  66. validate_on_init: bool | None = None,
  67. ) -> Callable[[type[_T]], type[PydanticDataclass]]: # type: ignore
  68. ...
  69. @dataclass_transform(field_specifiers=(dataclasses.field, Field, PrivateAttr))
  70. @overload
  71. def dataclass(
  72. _cls: type[_T], # type: ignore
  73. *,
  74. init: Literal[False] = False,
  75. repr: bool = True,
  76. eq: bool = True,
  77. order: bool = False,
  78. unsafe_hash: bool = False,
  79. frozen: bool | None = None,
  80. config: ConfigDict | type[object] | None = None,
  81. validate_on_init: bool | None = None,
  82. ) -> type[PydanticDataclass]: ...
  83. @dataclass_transform(field_specifiers=(dataclasses.field, Field, PrivateAttr))
  84. def dataclass(
  85. _cls: type[_T] | None = None,
  86. *,
  87. init: Literal[False] = False,
  88. repr: bool = True,
  89. eq: bool = True,
  90. order: bool = False,
  91. unsafe_hash: bool = False,
  92. frozen: bool | None = None,
  93. config: ConfigDict | type[object] | None = None,
  94. validate_on_init: bool | None = None,
  95. kw_only: bool = False,
  96. slots: bool = False,
  97. ) -> Callable[[type[_T]], type[PydanticDataclass]] | type[PydanticDataclass]:
  98. """!!! abstract "Usage Documentation"
  99. [`dataclasses`](../concepts/dataclasses.md)
  100. A decorator used to create a Pydantic-enhanced dataclass, similar to the standard Python `dataclass`,
  101. but with added validation.
  102. This function should be used similarly to `dataclasses.dataclass`.
  103. Args:
  104. _cls: The target `dataclass`.
  105. init: Included for signature compatibility with `dataclasses.dataclass`, and is passed through to
  106. `dataclasses.dataclass` when appropriate. If specified, must be set to `False`, as pydantic inserts its
  107. own `__init__` function.
  108. repr: A boolean indicating whether to include the field in the `__repr__` output.
  109. eq: Determines if a `__eq__` method should be generated for the class.
  110. order: Determines if comparison magic methods should be generated, such as `__lt__`, but not `__eq__`.
  111. unsafe_hash: Determines if a `__hash__` method should be included in the class, as in `dataclasses.dataclass`.
  112. frozen: Determines if the generated class should be a 'frozen' `dataclass`, which does not allow its
  113. 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).
  114. config: The Pydantic config to use for the `dataclass`.
  115. validate_on_init: A deprecated parameter included for backwards compatibility; in V2, all Pydantic dataclasses
  116. are validated on init.
  117. kw_only: Determines if `__init__` method parameters must be specified by keyword only. Defaults to `False`.
  118. slots: Determines if the generated class should be a 'slots' `dataclass`, which does not allow the addition of
  119. new attributes after instantiation.
  120. Returns:
  121. A decorator that accepts a class as its argument and returns a Pydantic `dataclass`.
  122. Raises:
  123. AssertionError: Raised if `init` is not `False` or `validate_on_init` is `False`.
  124. """
  125. assert init is False, 'pydantic.dataclasses.dataclass only supports init=False'
  126. assert validate_on_init is not False, 'validate_on_init=False is no longer supported'
  127. if sys.version_info >= (3, 10):
  128. kwargs = {'kw_only': kw_only, 'slots': slots}
  129. else:
  130. kwargs = {}
  131. def create_dataclass(cls: type[Any]) -> type[PydanticDataclass]:
  132. """Create a Pydantic dataclass from a regular dataclass.
  133. Args:
  134. cls: The class to create the Pydantic dataclass from.
  135. Returns:
  136. A Pydantic dataclass.
  137. """
  138. from ._internal._utils import is_model_class
  139. if is_model_class(cls):
  140. raise PydanticUserError(
  141. f'Cannot create a Pydantic dataclass from {cls.__name__} as it is already a Pydantic model',
  142. code='dataclass-on-model',
  143. )
  144. original_cls = cls
  145. # we warn on conflicting config specifications, but only if the class doesn't have a dataclass base
  146. # because a dataclass base might provide a __pydantic_config__ attribute that we don't want to warn about
  147. has_dataclass_base = any(dataclasses.is_dataclass(base) for base in cls.__bases__)
  148. if not has_dataclass_base and config is not None and hasattr(cls, '__pydantic_config__'):
  149. warn(
  150. f'`config` is set via both the `dataclass` decorator and `__pydantic_config__` for dataclass {cls.__name__}. '
  151. f'The `config` specification from `dataclass` decorator will take priority.',
  152. category=UserWarning,
  153. stacklevel=2,
  154. )
  155. # if config is not explicitly provided, try to read it from the type
  156. config_dict = config if config is not None else getattr(cls, '__pydantic_config__', None)
  157. config_wrapper = _config.ConfigWrapper(config_dict)
  158. decorators = _decorators.DecoratorInfos.build(cls)
  159. decorators.update_from_config(config_wrapper)
  160. # Keep track of the original __doc__ so that we can restore it after applying the dataclasses decorator
  161. # Otherwise, classes with no __doc__ will have their signature added into the JSON schema description,
  162. # since dataclasses.dataclass will set this as the __doc__
  163. original_doc = cls.__doc__
  164. if _pydantic_dataclasses.is_stdlib_dataclass(cls):
  165. # Vanilla dataclasses include a default docstring (representing the class signature),
  166. # which we don't want to preserve.
  167. original_doc = None
  168. # We don't want to add validation to the existing std lib dataclass, so we will subclass it
  169. # If the class is generic, we need to make sure the subclass also inherits from Generic
  170. # with all the same parameters.
  171. bases = (cls,)
  172. if issubclass(cls, Generic):
  173. generic_base = Generic[cls.__parameters__] # type: ignore
  174. bases = bases + (generic_base,)
  175. cls = types.new_class(cls.__name__, bases)
  176. # Respect frozen setting from dataclass constructor and fallback to config setting if not provided
  177. if frozen is not None:
  178. frozen_ = frozen
  179. if config_wrapper.frozen:
  180. # It's not recommended to define both, as the setting from the dataclass decorator will take priority.
  181. warn(
  182. f'`frozen` is set via both the `dataclass` decorator and `config` for dataclass {cls.__name__!r}.'
  183. 'This is not recommended. The `frozen` specification on `dataclass` will take priority.',
  184. category=UserWarning,
  185. stacklevel=2,
  186. )
  187. else:
  188. frozen_ = config_wrapper.frozen or False
  189. # Make Pydantic's `Field()` function compatible with stdlib dataclasses. As we'll decorate
  190. # `cls` with the stdlib `@dataclass` decorator first, there are two attributes, `kw_only` and
  191. # `repr` that need to be understood *during* the stdlib creation. We do so in two steps:
  192. # 1. On the decorated class, wrap `Field()` assignment with `dataclass.field()`, with the
  193. # two attributes set (done in `as_dataclass_field()`)
  194. cls_anns = _typing_extra.safe_get_annotations(cls)
  195. for field_name in cls_anns:
  196. # We should look for assignments in `__dict__` instead, but for now we follow
  197. # the same behavior as stdlib dataclasses (see https://github.com/python/cpython/issues/88609)
  198. field_value = getattr(cls, field_name, None)
  199. if isinstance(field_value, FieldInfo):
  200. setattr(cls, field_name, _pydantic_dataclasses.as_dataclass_field(field_value))
  201. # 2. For bases of `cls` that are stdlib dataclasses, we temporarily patch their fields
  202. # (see the docstring of the context manager):
  203. with _pydantic_dataclasses.patch_base_fields(cls):
  204. cls = dataclasses.dataclass( # pyright: ignore[reportCallIssue]
  205. cls,
  206. # the value of init here doesn't affect anything except that it makes it easier to generate a signature
  207. init=True,
  208. repr=repr,
  209. eq=eq,
  210. order=order,
  211. unsafe_hash=unsafe_hash,
  212. frozen=frozen_,
  213. **kwargs,
  214. )
  215. if config_wrapper.validate_assignment:
  216. original_setattr = cls.__setattr__
  217. @functools.wraps(cls.__setattr__)
  218. def validated_setattr(instance: PydanticDataclass, name: str, value: Any, /) -> None:
  219. if frozen_:
  220. return original_setattr(instance, name, value) # pyright: ignore[reportCallIssue]
  221. inst_cls = type(instance)
  222. attr = getattr(inst_cls, name, None)
  223. if isinstance(attr, property):
  224. attr.__set__(instance, value)
  225. elif isinstance(attr, functools.cached_property):
  226. instance.__dict__.__setitem__(name, value)
  227. else:
  228. inst_cls.__pydantic_validator__.validate_assignment(instance, name, value)
  229. cls.__setattr__ = validated_setattr.__get__(None, cls) # type: ignore
  230. if slots and not hasattr(cls, '__setstate__'):
  231. # If slots is set, `pickle` (relied on by `copy.copy()`) will use
  232. # `__setattr__()` to reconstruct the dataclass. However, the custom
  233. # `__setattr__()` set above relies on `validate_assignment()`, which
  234. # in turn expects all the field values to be already present on the
  235. # instance, resulting in attribute errors.
  236. # As such, we make use of `object.__setattr__()` instead.
  237. # Note that we do so only if `__setstate__()` isn't already set (this is the
  238. # case if on top of `slots`, `frozen` is used).
  239. # Taken from `dataclasses._dataclass_get/setstate()`:
  240. def _dataclass_getstate(self: Any) -> list[Any]:
  241. return [getattr(self, f.name) for f in dataclasses.fields(self)]
  242. def _dataclass_setstate(self: Any, state: list[Any]) -> None:
  243. for field, value in zip(dataclasses.fields(self), state):
  244. object.__setattr__(self, field.name, value)
  245. cls.__getstate__ = _dataclass_getstate # pyright: ignore[reportAttributeAccessIssue]
  246. cls.__setstate__ = _dataclass_setstate # pyright: ignore[reportAttributeAccessIssue]
  247. # This is an undocumented attribute to distinguish stdlib/Pydantic dataclasses.
  248. # It should be set as early as possible:
  249. cls.__is_pydantic_dataclass__ = True
  250. cls.__pydantic_decorators__ = decorators # type: ignore
  251. cls.__doc__ = original_doc
  252. # Can be non-existent for dynamically created classes:
  253. firstlineno = getattr(original_cls, '__firstlineno__', None)
  254. cls.__module__ = original_cls.__module__
  255. if sys.version_info >= (3, 13) and firstlineno is not None:
  256. # As per https://docs.python.org/3/reference/datamodel.html#type.__firstlineno__:
  257. # Setting the `__module__` attribute removes the `__firstlineno__` item from the type’s dictionary.
  258. original_cls.__firstlineno__ = firstlineno
  259. cls.__firstlineno__ = firstlineno
  260. cls.__qualname__ = original_cls.__qualname__
  261. cls.__pydantic_fields_complete__ = classmethod(_pydantic_fields_complete)
  262. cls.__pydantic_complete__ = False # `complete_dataclass` will set it to `True` if successful.
  263. # TODO `parent_namespace` is currently None, but we could do the same thing as Pydantic models:
  264. # fetch the parent ns using `parent_frame_namespace` (if the dataclass was defined in a function),
  265. # and possibly cache it (see the `__pydantic_parent_namespace__` logic for models).
  266. _pydantic_dataclasses.complete_dataclass(cls, config_wrapper, raise_errors=False)
  267. return cls
  268. return create_dataclass if _cls is None else create_dataclass(_cls)
  269. def _pydantic_fields_complete(cls: type[PydanticDataclass]) -> bool:
  270. """Return whether the fields where successfully collected (i.e. type hints were successfully resolves).
  271. This is a private property, not meant to be used outside Pydantic.
  272. """
  273. return all(field_info._complete for field_info in cls.__pydantic_fields__.values())
  274. __getattr__ = getattr_migration(__name__)
  275. if sys.version_info < (3, 11):
  276. # Monkeypatch dataclasses.InitVar so that typing doesn't error if it occurs as a type when evaluating type hints
  277. # Starting in 3.11, typing.get_type_hints will not raise an error if the retrieved type hints are not callable.
  278. def _call_initvar(*args: Any, **kwargs: Any) -> NoReturn:
  279. """This function does nothing but raise an error that is as similar as possible to what you'd get
  280. if you were to try calling `InitVar[int]()` without this monkeypatch. The whole purpose is just
  281. to ensure typing._type_check does not error if the type hint evaluates to `InitVar[<parameter>]`.
  282. """
  283. raise TypeError("'InitVar' object is not callable")
  284. dataclasses.InitVar.__call__ = _call_initvar
  285. def rebuild_dataclass(
  286. cls: type[PydanticDataclass],
  287. *,
  288. force: bool = False,
  289. raise_errors: bool = True,
  290. _parent_namespace_depth: int = 2,
  291. _types_namespace: MappingNamespace | None = None,
  292. ) -> bool | None:
  293. """Try to rebuild the pydantic-core schema for the dataclass.
  294. This may be necessary when one of the annotations is a ForwardRef which could not be resolved during
  295. the initial attempt to build the schema, and automatic rebuilding fails.
  296. This is analogous to `BaseModel.model_rebuild`.
  297. Args:
  298. cls: The class to rebuild the pydantic-core schema for.
  299. force: Whether to force the rebuilding of the schema, defaults to `False`.
  300. raise_errors: Whether to raise errors, defaults to `True`.
  301. _parent_namespace_depth: The depth level of the parent namespace, defaults to 2.
  302. _types_namespace: The types namespace, defaults to `None`.
  303. Returns:
  304. Returns `None` if the schema is already "complete" and rebuilding was not required.
  305. If rebuilding _was_ required, returns `True` if rebuilding was successful, otherwise `False`.
  306. """
  307. if not force and cls.__pydantic_complete__:
  308. return None
  309. for attr in ('__pydantic_core_schema__', '__pydantic_validator__', '__pydantic_serializer__'):
  310. if attr in cls.__dict__ and not isinstance(getattr(cls, attr), _mock_val_ser.MockValSer):
  311. # Deleting the validator/serializer is necessary as otherwise they can get reused in
  312. # pydantic-core. Same applies for the core schema that can be reused in schema generation.
  313. delattr(cls, attr)
  314. cls.__pydantic_complete__ = False
  315. if _types_namespace is not None:
  316. rebuild_ns = _types_namespace
  317. elif _parent_namespace_depth > 0:
  318. rebuild_ns = _typing_extra.parent_frame_namespace(parent_depth=_parent_namespace_depth, force=True) or {}
  319. else:
  320. rebuild_ns = {}
  321. ns_resolver = _namespace_utils.NsResolver(
  322. parent_namespace=rebuild_ns,
  323. )
  324. return _pydantic_dataclasses.complete_dataclass(
  325. cls,
  326. _config.ConfigWrapper(cls.__pydantic_config__, check=False),
  327. raise_errors=raise_errors,
  328. ns_resolver=ns_resolver,
  329. # We could provide a different config instead (with `'defer_build'` set to `True`)
  330. # of this explicit `_force_build` argument, but because config can come from the
  331. # decorator parameter or the `__pydantic_config__` attribute, `complete_dataclass`
  332. # will overwrite `__pydantic_config__` with the provided config above:
  333. _force_build=True,
  334. )
  335. def is_pydantic_dataclass(class_: type[Any], /) -> TypeGuard[type[PydanticDataclass]]:
  336. """Whether a class is a pydantic dataclass.
  337. Args:
  338. class_: The class.
  339. Returns:
  340. `True` if the class is a pydantic dataclass, `False` otherwise.
  341. """
  342. try:
  343. return '__is_pydantic_dataclass__' in class_.__dict__ and dataclasses.is_dataclass(class_)
  344. except AttributeError:
  345. return False