Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pydantic/_internal/_dataclasses.py: 5%

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

111 statements  

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

2 

3from __future__ import annotations as _annotations 

4 

5import copy 

6import dataclasses 

7import sys 

8import warnings 

9from collections.abc import Generator 

10from contextlib import contextmanager 

11from functools import partial 

12from typing import TYPE_CHECKING, Any, ClassVar, Protocol, cast 

13 

14from pydantic_core import ( 

15 ArgsKwargs, 

16 SchemaSerializer, 

17 SchemaValidator, 

18 core_schema, 

19) 

20from typing_extensions import TypeAlias, TypeIs 

21 

22from ..errors import PydanticUndefinedAnnotation 

23from ..fields import FieldInfo 

24from ..plugin._schema_validator import PluggableSchemaValidator, create_schema_validator 

25from ..warnings import PydanticDeprecatedSince20 

26from . import _config, _decorators 

27from ._fields import collect_dataclass_fields 

28from ._generate_schema import GenerateSchema, InvalidSchemaError 

29from ._generics import get_standard_typevars_map 

30from ._mock_val_ser import set_dataclass_mocks 

31from ._namespace_utils import NsResolver 

32from ._signature import generate_pydantic_signature 

33from ._utils import LazyClassAttribute 

34 

35if TYPE_CHECKING: 

36 from _typeshed import DataclassInstance as StandardDataclass 

37 

38 from ..config import ConfigDict 

39 

40 class PydanticDataclass(StandardDataclass, Protocol): 

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

42 

43 Attributes: 

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

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

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

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

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

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

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

51 """ 

52 

53 __pydantic_config__: ClassVar[ConfigDict] 

54 __pydantic_complete__: ClassVar[bool] 

55 __pydantic_core_schema__: ClassVar[core_schema.CoreSchema] 

56 __pydantic_decorators__: ClassVar[_decorators.DecoratorInfos] 

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

58 __pydantic_serializer__: ClassVar[SchemaSerializer] 

59 __pydantic_validator__: ClassVar[SchemaValidator | PluggableSchemaValidator] 

60 

61 @classmethod 

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

63 

64 

65def set_dataclass_fields( 

66 cls: type[StandardDataclass], 

67 config_wrapper: _config.ConfigWrapper, 

68 ns_resolver: NsResolver | None = None, 

69) -> None: 

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

71 

72 Args: 

73 cls: The class. 

74 config_wrapper: The config wrapper instance. 

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

76 """ 

77 typevars_map = get_standard_typevars_map(cls) 

78 fields = collect_dataclass_fields( 

79 cls, ns_resolver=ns_resolver, typevars_map=typevars_map, config_wrapper=config_wrapper 

80 ) 

81 

82 cls.__pydantic_fields__ = fields # type: ignore 

83 

84 

85def complete_dataclass( 

86 cls: type[Any], 

87 config_wrapper: _config.ConfigWrapper, 

88 *, 

89 raise_errors: bool = True, 

90 ns_resolver: NsResolver | None = None, 

91 _force_build: bool = False, 

92) -> bool: 

93 """Finish building a pydantic dataclass. 

94 

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

96 

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

98 

99 Args: 

100 cls: The class. 

101 config_wrapper: The config wrapper instance. 

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

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

104 and during schema building. 

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

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

107 

108 Returns: 

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

110 

111 Raises: 

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

113 """ 

114 original_init = cls.__init__ 

115 

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

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

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

119 __tracebackhide__ = True 

120 s = __dataclass_self__ 

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

122 

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

124 

125 cls.__init__ = __init__ # type: ignore 

126 cls.__pydantic_config__ = config_wrapper.config_dict # type: ignore 

127 

128 set_dataclass_fields(cls, config_wrapper=config_wrapper, ns_resolver=ns_resolver) 

129 

130 if not _force_build and config_wrapper.defer_build: 

131 set_dataclass_mocks(cls) 

132 return False 

133 

134 if hasattr(cls, '__post_init_post_parse__'): 

135 warnings.warn( 

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

137 PydanticDeprecatedSince20, 

138 ) 

139 

140 typevars_map = get_standard_typevars_map(cls) 

141 gen_schema = GenerateSchema( 

142 config_wrapper, 

143 ns_resolver=ns_resolver, 

144 typevars_map=typevars_map, 

145 ) 

146 

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

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

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

150 cls.__signature__ = LazyClassAttribute( 

151 '__signature__', 

152 partial( 

153 generate_pydantic_signature, 

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

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

156 init=original_init, 

157 fields=cls.__pydantic_fields__, # type: ignore 

158 validate_by_name=config_wrapper.validate_by_name, 

159 extra=config_wrapper.extra, 

160 is_dataclass=True, 

161 ), 

162 ) 

163 

164 try: 

165 schema = gen_schema.generate_schema(cls) 

166 except PydanticUndefinedAnnotation as e: 

167 if raise_errors: 

168 raise 

169 set_dataclass_mocks(cls, f'`{e.name}`') 

170 return False 

171 

172 core_config = config_wrapper.core_config(title=cls.__name__) 

173 

174 try: 

175 schema = gen_schema.clean_schema(schema) 

176 except InvalidSchemaError: 

177 set_dataclass_mocks(cls) 

178 return False 

179 

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

181 # __pydantic_decorators__ and __pydantic_fields__ should already be set 

182 cls = cast('type[PydanticDataclass]', cls) 

183 

184 cls.__pydantic_core_schema__ = schema 

185 cls.__pydantic_validator__ = create_schema_validator( 

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

187 ) 

188 cls.__pydantic_serializer__ = SchemaSerializer(schema, core_config) 

189 cls.__pydantic_complete__ = True 

190 return True 

191 

192 

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

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

195 

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

197 of a dataclass that are themselves not dataclasses. 

198 

199 Args: 

200 cls: The class. 

201 

202 Returns: 

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

204 """ 

205 return '__dataclass_fields__' in cls.__dict__ and not hasattr(cls, '__pydantic_validator__') 

206 

207 

208def as_dataclass_field(pydantic_field: FieldInfo) -> dataclasses.Field[Any]: 

209 field_args: dict[str, Any] = {'default': pydantic_field} 

210 

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

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

213 field_args['doc'] = pydantic_field.description 

214 

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

216 if sys.version_info >= (3, 10) and pydantic_field.kw_only: 

217 field_args['kw_only'] = True 

218 

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

220 if pydantic_field.repr is not True: 

221 field_args['repr'] = pydantic_field.repr 

222 

223 return dataclasses.field(**field_args) 

224 

225 

226DcFields: TypeAlias = dict[str, dataclasses.Field[Any]] 

227 

228 

229@contextmanager 

230def patch_base_fields(cls: type[Any]) -> Generator[None]: 

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

232 

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

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

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

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

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

238 bases. 

239 

240 For instance, with the following example: 

241 

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

243 import dataclasses as stdlib_dc 

244 

245 import pydantic 

246 import pydantic.dataclasses as pydantic_dc 

247 

248 @stdlib_dc.dataclass 

249 class A: 

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

251 

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

253 A.__dataclass_fields__['a'] 

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

255 

256 @pydantic_dc.dataclass 

257 class B(A): 

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

259 ``` 

260 

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

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

263 equivalent to: 

264 

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

266 @stdlib_dc.dataclass 

267 class A: 

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

269 ``` 

270 

271 !!! note 

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

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

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

275 

276 !!! note 

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

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

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

280 """ 

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

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

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

284 original_fields_list: list[tuple[DcFields, DcFields]] = [] 

285 

286 for base in cls.__mro__[1:]: 

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

288 dc_fields_with_pydantic_field_defaults = { 

289 field_name: field 

290 for field_name, field in dc_fields.items() 

291 if isinstance(field.default, FieldInfo) 

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

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

294 } 

295 if dc_fields_with_pydantic_field_defaults: 

296 original_fields_list.append((dc_fields, dc_fields_with_pydantic_field_defaults)) 

297 for field_name, field in dc_fields_with_pydantic_field_defaults.items(): 

298 default = cast(FieldInfo, field.default) 

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

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

301 new_dc_field = copy.copy(field) 

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

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

304 if sys.version_info >= (3, 10) and default.kw_only: 

305 new_dc_field.kw_only = True 

306 if default.repr is not True: 

307 new_dc_field.repr = default.repr 

308 dc_fields[field_name] = new_dc_field 

309 

310 try: 

311 yield 

312 finally: 

313 for fields, original_fields in original_fields_list: 

314 for field_name, original_field in original_fields.items(): 

315 fields[field_name] = original_field