_dataclasses.py 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. """Private logic for creating pydantic dataclasses."""
  2. from __future__ import annotations as _annotations
  3. import dataclasses
  4. import typing
  5. import warnings
  6. from functools import partial, wraps
  7. from typing import Any, ClassVar
  8. from pydantic_core import (
  9. ArgsKwargs,
  10. SchemaSerializer,
  11. SchemaValidator,
  12. core_schema,
  13. )
  14. from typing_extensions import TypeGuard
  15. from ..errors import PydanticUndefinedAnnotation
  16. from ..plugin._schema_validator import PluggableSchemaValidator, create_schema_validator
  17. from ..warnings import PydanticDeprecatedSince20
  18. from . import _config, _decorators
  19. from ._fields import collect_dataclass_fields
  20. from ._generate_schema import GenerateSchema
  21. from ._generics import get_standard_typevars_map
  22. from ._mock_val_ser import set_dataclass_mocks
  23. from ._namespace_utils import NsResolver
  24. from ._schema_generation_shared import CallbackGetCoreSchemaHandler
  25. from ._signature import generate_pydantic_signature
  26. from ._utils import LazyClassAttribute
  27. if typing.TYPE_CHECKING:
  28. from _typeshed import DataclassInstance as StandardDataclass
  29. from ..config import ConfigDict
  30. from ..fields import FieldInfo
  31. class PydanticDataclass(StandardDataclass, typing.Protocol):
  32. """A protocol containing attributes only available once a class has been decorated as a Pydantic dataclass.
  33. Attributes:
  34. __pydantic_config__: Pydantic-specific configuration settings for the dataclass.
  35. __pydantic_complete__: Whether dataclass building is completed, or if there are still undefined fields.
  36. __pydantic_core_schema__: The pydantic-core schema used to build the SchemaValidator and SchemaSerializer.
  37. __pydantic_decorators__: Metadata containing the decorators defined on the dataclass.
  38. __pydantic_fields__: Metadata about the fields defined on the dataclass.
  39. __pydantic_serializer__: The pydantic-core SchemaSerializer used to dump instances of the dataclass.
  40. __pydantic_validator__: The pydantic-core SchemaValidator used to validate instances of the dataclass.
  41. """
  42. __pydantic_config__: ClassVar[ConfigDict]
  43. __pydantic_complete__: ClassVar[bool]
  44. __pydantic_core_schema__: ClassVar[core_schema.CoreSchema]
  45. __pydantic_decorators__: ClassVar[_decorators.DecoratorInfos]
  46. __pydantic_fields__: ClassVar[dict[str, FieldInfo]]
  47. __pydantic_serializer__: ClassVar[SchemaSerializer]
  48. __pydantic_validator__: ClassVar[SchemaValidator | PluggableSchemaValidator]
  49. else:
  50. # See PyCharm issues https://youtrack.jetbrains.com/issue/PY-21915
  51. # and https://youtrack.jetbrains.com/issue/PY-51428
  52. DeprecationWarning = PydanticDeprecatedSince20
  53. def set_dataclass_fields(
  54. cls: type[StandardDataclass],
  55. ns_resolver: NsResolver | None = None,
  56. config_wrapper: _config.ConfigWrapper | None = None,
  57. ) -> None:
  58. """Collect and set `cls.__pydantic_fields__`.
  59. Args:
  60. cls: The class.
  61. ns_resolver: Namespace resolver to use when getting dataclass annotations.
  62. config_wrapper: The config wrapper instance, defaults to `None`.
  63. """
  64. typevars_map = get_standard_typevars_map(cls)
  65. fields = collect_dataclass_fields(
  66. cls, ns_resolver=ns_resolver, typevars_map=typevars_map, config_wrapper=config_wrapper
  67. )
  68. cls.__pydantic_fields__ = fields # type: ignore
  69. def complete_dataclass(
  70. cls: type[Any],
  71. config_wrapper: _config.ConfigWrapper,
  72. *,
  73. raise_errors: bool = True,
  74. ns_resolver: NsResolver | None = None,
  75. _force_build: bool = False,
  76. ) -> bool:
  77. """Finish building a pydantic dataclass.
  78. This logic is called on a class which has already been wrapped in `dataclasses.dataclass()`.
  79. This is somewhat analogous to `pydantic._internal._model_construction.complete_model_class`.
  80. Args:
  81. cls: The class.
  82. config_wrapper: The config wrapper instance.
  83. raise_errors: Whether to raise errors, defaults to `True`.
  84. ns_resolver: The namespace resolver instance to use when collecting dataclass fields
  85. and during schema building.
  86. _force_build: Whether to force building the dataclass, no matter if
  87. [`defer_build`][pydantic.config.ConfigDict.defer_build] is set.
  88. Returns:
  89. `True` if building a pydantic dataclass is successfully completed, `False` otherwise.
  90. Raises:
  91. PydanticUndefinedAnnotation: If `raise_error` is `True` and there is an undefined annotations.
  92. """
  93. original_init = cls.__init__
  94. # dataclass.__init__ must be defined here so its `__qualname__` can be changed since functions can't be copied,
  95. # and so that the mock validator is used if building was deferred:
  96. def __init__(__dataclass_self__: PydanticDataclass, *args: Any, **kwargs: Any) -> None:
  97. __tracebackhide__ = True
  98. s = __dataclass_self__
  99. s.__pydantic_validator__.validate_python(ArgsKwargs(args, kwargs), self_instance=s)
  100. __init__.__qualname__ = f'{cls.__qualname__}.__init__'
  101. cls.__init__ = __init__ # type: ignore
  102. cls.__pydantic_config__ = config_wrapper.config_dict # type: ignore
  103. set_dataclass_fields(cls, ns_resolver, config_wrapper=config_wrapper)
  104. if not _force_build and config_wrapper.defer_build:
  105. set_dataclass_mocks(cls, cls.__name__)
  106. return False
  107. if hasattr(cls, '__post_init_post_parse__'):
  108. warnings.warn(
  109. 'Support for `__post_init_post_parse__` has been dropped, the method will not be called', DeprecationWarning
  110. )
  111. typevars_map = get_standard_typevars_map(cls)
  112. gen_schema = GenerateSchema(
  113. config_wrapper,
  114. ns_resolver=ns_resolver,
  115. typevars_map=typevars_map,
  116. )
  117. # set __signature__ attr only for the class, but not for its instances
  118. # (because instances can define `__call__`, and `inspect.signature` shouldn't
  119. # use the `__signature__` attribute and instead generate from `__call__`).
  120. cls.__signature__ = LazyClassAttribute(
  121. '__signature__',
  122. partial(
  123. generate_pydantic_signature,
  124. # It's important that we reference the `original_init` here,
  125. # as it is the one synthesized by the stdlib `dataclass` module:
  126. init=original_init,
  127. fields=cls.__pydantic_fields__, # type: ignore
  128. populate_by_name=config_wrapper.populate_by_name,
  129. extra=config_wrapper.extra,
  130. is_dataclass=True,
  131. ),
  132. )
  133. get_core_schema = getattr(cls, '__get_pydantic_core_schema__', None)
  134. try:
  135. if get_core_schema:
  136. schema = get_core_schema(
  137. cls,
  138. CallbackGetCoreSchemaHandler(
  139. partial(gen_schema.generate_schema, from_dunder_get_core_schema=False),
  140. gen_schema,
  141. ref_mode='unpack',
  142. ),
  143. )
  144. else:
  145. schema = gen_schema.generate_schema(cls, from_dunder_get_core_schema=False)
  146. except PydanticUndefinedAnnotation as e:
  147. if raise_errors:
  148. raise
  149. set_dataclass_mocks(cls, cls.__name__, f'`{e.name}`')
  150. return False
  151. core_config = config_wrapper.core_config(title=cls.__name__)
  152. try:
  153. schema = gen_schema.clean_schema(schema)
  154. except gen_schema.CollectedInvalid:
  155. set_dataclass_mocks(cls, cls.__name__, 'all referenced types')
  156. return False
  157. # We are about to set all the remaining required properties expected for this cast;
  158. # __pydantic_decorators__ and __pydantic_fields__ should already be set
  159. cls = typing.cast('type[PydanticDataclass]', cls)
  160. # debug(schema)
  161. cls.__pydantic_core_schema__ = schema
  162. cls.__pydantic_validator__ = validator = create_schema_validator(
  163. schema, cls, cls.__module__, cls.__qualname__, 'dataclass', core_config, config_wrapper.plugin_settings
  164. )
  165. cls.__pydantic_serializer__ = SchemaSerializer(schema, core_config)
  166. if config_wrapper.validate_assignment:
  167. @wraps(cls.__setattr__)
  168. def validated_setattr(instance: Any, field: str, value: str, /) -> None:
  169. validator.validate_assignment(instance, field, value)
  170. cls.__setattr__ = validated_setattr.__get__(None, cls) # type: ignore
  171. cls.__pydantic_complete__ = True
  172. return True
  173. def is_builtin_dataclass(_cls: type[Any]) -> TypeGuard[type[StandardDataclass]]:
  174. """Returns True if a class is a stdlib dataclass and *not* a pydantic dataclass.
  175. We check that
  176. - `_cls` is a dataclass
  177. - `_cls` does not inherit from a processed pydantic dataclass (and thus have a `__pydantic_validator__`)
  178. - `_cls` does not have any annotations that are not dataclass fields
  179. e.g.
  180. ```python
  181. import dataclasses
  182. import pydantic.dataclasses
  183. @dataclasses.dataclass
  184. class A:
  185. x: int
  186. @pydantic.dataclasses.dataclass
  187. class B(A):
  188. y: int
  189. ```
  190. In this case, when we first check `B`, we make an extra check and look at the annotations ('y'),
  191. which won't be a superset of all the dataclass fields (only the stdlib fields i.e. 'x')
  192. Args:
  193. cls: The class.
  194. Returns:
  195. `True` if the class is a stdlib dataclass, `False` otherwise.
  196. """
  197. return (
  198. dataclasses.is_dataclass(_cls)
  199. and not hasattr(_cls, '__pydantic_validator__')
  200. and set(_cls.__dataclass_fields__).issuperset(set(getattr(_cls, '__annotations__', {})))
  201. )