Coverage for /pythoncovmergedfiles/medio/medio/src/pydantic/pydantic/_internal/_dataclasses.py: 26%

78 statements  

« prev     ^ index     » next       coverage.py v7.2.3, created at 2023-04-27 07:38 +0000

1""" 

2Private logic for creating pydantic dataclasses. 

3""" 

4from __future__ import annotations as _annotations 

5 

6import dataclasses 

7import typing 

8import warnings 

9from functools import partial, wraps 

10from typing import Any, Callable, ClassVar 

11 

12from pydantic_core import ArgsKwargs, SchemaSerializer, SchemaValidator, core_schema 

13from typing_extensions import TypeGuard 

14 

15from ..errors import PydanticUndefinedAnnotation 

16from ..fields import FieldInfo 

17from . import _decorators, _typing_extra 

18from ._fields import collect_dataclass_fields 

19from ._generate_schema import GenerateSchema 

20from ._generics import get_standard_typevars_map 

21from ._model_construction import MockValidator 

22 

23if typing.TYPE_CHECKING: 

24 from ..config import ConfigDict 

25 from ._config import ConfigWrapper 

26 

27 class StandardDataclass(typing.Protocol): 

28 __dataclass_fields__: ClassVar[dict[str, Any]] 

29 __dataclass_params__: ClassVar[Any] # in reality `dataclasses._DataclassParams` 

30 __post_init__: ClassVar[Callable[..., None]] 

31 

32 def __init__(self, *args: object, **kwargs: object) -> None: 

33 pass 

34 

35 class PydanticDataclass(StandardDataclass, typing.Protocol): 

36 __pydantic_core_schema__: typing.ClassVar[core_schema.CoreSchema] 

37 __pydantic_validator__: typing.ClassVar[SchemaValidator] 

38 __pydantic_serializer__: typing.ClassVar[SchemaSerializer] 

39 __pydantic_decorators__: typing.ClassVar[_decorators.DecoratorInfos] 

40 """metadata for `@validator`, `@root_validator` and `@serializer` decorators""" 

41 __pydantic_fields__: typing.ClassVar[dict[str, FieldInfo]] 

42 __pydantic_config__: typing.ClassVar[ConfigDict] 

43 

44 

45def set_dataclass_fields(cls: type[StandardDataclass], types_namespace: dict[str, Any] | None = None) -> None: 

46 """ 

47 Collect and set `cls.__pydantic_fields__` 

48 """ 

49 typevars_map = get_standard_typevars_map(cls) 

50 fields = collect_dataclass_fields(cls, types_namespace, typevars_map=typevars_map) 

51 

52 cls.__pydantic_fields__ = fields # type: ignore 

53 

54 

55def complete_dataclass( 

56 cls: type[Any], 

57 config_wrapper: ConfigWrapper, 

58 *, 

59 raise_errors: bool = True, 

60 types_namespace: dict[str, Any] | None = None, 

61) -> bool: 

62 """ 

63 Prepare a raw class to become a pydantic dataclass. 

64 

65 Returns `True` if the validation construction is successfully completed, else `False`. 

66 

67 This logic is called on a class which is yet to be wrapped in `dataclasses.dataclass()`. 

68 """ 

69 cls_name = cls.__name__ 

70 

71 if hasattr(cls, '__post_init_post_parse__'): 

72 warnings.warn( 

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

74 ) 

75 

76 types_namespace = _typing_extra.get_cls_types_namespace(cls, types_namespace) 

77 typevars_map = get_standard_typevars_map(cls) 

78 gen_schema = GenerateSchema( 

79 config_wrapper, 

80 types_namespace, 

81 typevars_map, 

82 ) 

83 

84 try: 

85 get_core_schema = getattr(cls, '__get_pydantic_core_schema__', None) 

86 if get_core_schema: 

87 schema = get_core_schema(cls, partial(gen_schema.generate_schema, from_dunder_get_core_schema=False)) 

88 else: 

89 schema = gen_schema.generate_schema(cls, False) 

90 except PydanticUndefinedAnnotation as e: 

91 if raise_errors: 

92 raise 

93 if config_wrapper.undefined_types_warning: 

94 config_warning_string = ( 

95 f'`{cls_name}` has an undefined annotation: `{e.name}`. ' 

96 f'It may be possible to resolve this by setting ' 

97 f'undefined_types_warning=False in the config for `{cls_name}`.' 

98 ) 

99 # FIXME UserWarning should not be raised here, but rather warned! 

100 raise UserWarning(config_warning_string) 

101 usage_warning_string = ( 

102 f'`{cls_name}` is not fully defined; you should define `{e.name}`, then call' 

103 f' TODO! `methods.rebuild({cls_name})` before the first `{cls_name}` instance is created.' # <--- TODO 

104 ) 

105 cls.__pydantic_validator__ = MockValidator( # type: ignore[assignment] 

106 usage_warning_string, code='dataclass-not-fully-defined' 

107 ) 

108 return False 

109 

110 core_config = config_wrapper.core_config(cls) 

111 

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

113 # __pydantic_decorators__ and __pydantic_fields__ should already be set 

114 cls = typing.cast('type[PydanticDataclass]', cls) 

115 # debug(schema) 

116 cls.__pydantic_core_schema__ = schema 

117 cls.__pydantic_validator__ = validator = SchemaValidator(schema, core_config) 

118 cls.__pydantic_serializer__ = SchemaSerializer(schema, core_config) 

119 # dataclasses only: 

120 cls.__pydantic_config__ = config_wrapper.config_dict 

121 

122 if config_wrapper.validate_assignment: 

123 

124 @wraps(cls.__setattr__) 

125 def validated_setattr(instance: Any, __field: str, __value: str) -> None: 

126 validator.validate_assignment(instance, __field, __value) 

127 

128 cls.__setattr__ = validated_setattr.__get__(None, cls) # type: ignore 

129 

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

131 

132 def __init__(__dataclass_self__: PydanticDataclass, *args: Any, **kwargs: Any) -> None: 

133 __tracebackhide__ = True 

134 s = __dataclass_self__ 

135 s.__pydantic_validator__.validate_python(ArgsKwargs(args, kwargs), self_instance=s) 

136 

137 __init__.__qualname__ = f'{cls.__qualname__}.__init__' 

138 cls.__init__ = __init__ # type: ignore 

139 

140 return True 

141 

142 

143def is_builtin_dataclass(_cls: type[Any]) -> TypeGuard[type[StandardDataclass]]: 

144 """ 

145 Whether a class is a stdlib dataclass 

146 (useful to discriminated a pydantic dataclass that is actually a wrapper around a stdlib dataclass) 

147 

148 we check that 

149 - `_cls` is a dataclass 

150 - `_cls` is not a processed pydantic dataclass (with a basemodel attached) 

151 - `_cls` is not a pydantic dataclass inheriting directly from a stdlib dataclass 

152 e.g. 

153 ```py 

154 @dataclasses.dataclass 

155 class A: 

156 x: int 

157 

158 @pydantic.dataclasses.dataclass 

159 class B(A): 

160 y: int 

161 ``` 

162 In this case, when we first check `B`, we make an extra check and look at the annotations ('y'), 

163 which won't be a superset of all the dataclass fields (only the stdlib fields i.e. 'x') 

164 """ 

165 return ( 

166 dataclasses.is_dataclass(_cls) 

167 and not hasattr(_cls, '__pydantic_validator__') 

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

169 ) 

170 

171 

172def is_pydantic_dataclass(_cls: type[Any]) -> TypeGuard[type[PydanticDataclass]]: 

173 return dataclasses.is_dataclass(_cls) and hasattr(_cls, '__pydantic_validator__')