_fields.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635
  1. """Private logic related to fields (the `Field()` function and `FieldInfo` class), and arguments to `Annotated`."""
  2. from __future__ import annotations as _annotations
  3. import dataclasses
  4. import warnings
  5. from collections.abc import Mapping
  6. from functools import cache
  7. from inspect import Parameter, ismethoddescriptor, signature
  8. from re import Pattern
  9. from typing import TYPE_CHECKING, Any, Callable, TypeVar
  10. from pydantic_core import PydanticUndefined
  11. from typing_extensions import TypeIs
  12. from typing_inspection.introspection import AnnotationSource
  13. from pydantic import PydanticDeprecatedSince211
  14. from pydantic.errors import PydanticUserError
  15. from ..aliases import AliasGenerator
  16. from . import _generics, _typing_extra
  17. from ._config import ConfigWrapper
  18. from ._docs_extraction import extract_docstrings_from_cls
  19. from ._import_utils import import_cached_base_model, import_cached_field_info
  20. from ._namespace_utils import NsResolver
  21. from ._repr import Representation
  22. from ._utils import can_be_positional, get_first_not_none
  23. if TYPE_CHECKING:
  24. from annotated_types import BaseMetadata
  25. from ..fields import FieldInfo
  26. from ..main import BaseModel
  27. from ._dataclasses import PydanticDataclass, StandardDataclass
  28. from ._decorators import DecoratorInfos
  29. class PydanticMetadata(Representation):
  30. """Base class for annotation markers like `Strict`."""
  31. __slots__ = ()
  32. def pydantic_general_metadata(**metadata: Any) -> BaseMetadata:
  33. """Create a new `_PydanticGeneralMetadata` class with the given metadata.
  34. Args:
  35. **metadata: The metadata to add.
  36. Returns:
  37. The new `_PydanticGeneralMetadata` class.
  38. """
  39. return _general_metadata_cls()(metadata) # type: ignore
  40. @cache
  41. def _general_metadata_cls() -> type[BaseMetadata]:
  42. """Do it this way to avoid importing `annotated_types` at import time."""
  43. from annotated_types import BaseMetadata
  44. class _PydanticGeneralMetadata(PydanticMetadata, BaseMetadata):
  45. """Pydantic general metadata like `max_digits`."""
  46. def __init__(self, metadata: Any):
  47. self.__dict__ = metadata
  48. return _PydanticGeneralMetadata # type: ignore
  49. def _check_protected_namespaces(
  50. protected_namespaces: tuple[str | Pattern[str], ...],
  51. ann_name: str,
  52. bases: tuple[type[Any], ...],
  53. cls_name: str,
  54. ) -> None:
  55. BaseModel = import_cached_base_model()
  56. for protected_namespace in protected_namespaces:
  57. ns_violation = False
  58. if isinstance(protected_namespace, Pattern):
  59. ns_violation = protected_namespace.match(ann_name) is not None
  60. elif isinstance(protected_namespace, str):
  61. ns_violation = ann_name.startswith(protected_namespace)
  62. if ns_violation:
  63. for b in bases:
  64. if hasattr(b, ann_name):
  65. if not (issubclass(b, BaseModel) and ann_name in getattr(b, '__pydantic_fields__', {})):
  66. raise ValueError(
  67. f'Field {ann_name!r} conflicts with member {getattr(b, ann_name)}'
  68. f' of protected namespace {protected_namespace!r}.'
  69. )
  70. else:
  71. valid_namespaces: list[str] = []
  72. for pn in protected_namespaces:
  73. if isinstance(pn, Pattern):
  74. if not pn.match(ann_name):
  75. valid_namespaces.append(f're.compile({pn.pattern!r})')
  76. else:
  77. if not ann_name.startswith(pn):
  78. valid_namespaces.append(f"'{pn}'")
  79. valid_namespaces_str = f'({", ".join(valid_namespaces)}{",)" if len(valid_namespaces) == 1 else ")"}'
  80. warnings.warn(
  81. f'Field {ann_name!r} in {cls_name!r} conflicts with protected namespace {protected_namespace!r}.\n\n'
  82. f"You may be able to solve this by setting the 'protected_namespaces' configuration to {valid_namespaces_str}.",
  83. UserWarning,
  84. stacklevel=5,
  85. )
  86. def _update_fields_from_docstrings(cls: type[Any], fields: dict[str, FieldInfo], use_inspect: bool = False) -> None:
  87. fields_docs = extract_docstrings_from_cls(cls, use_inspect=use_inspect)
  88. for ann_name, field_info in fields.items():
  89. if field_info.description is None and ann_name in fields_docs:
  90. field_info.description = fields_docs[ann_name]
  91. def _apply_field_title_generator_to_field_info(
  92. title_generator: Callable[[str, FieldInfo], str],
  93. field_name: str,
  94. field_info: FieldInfo,
  95. ):
  96. if field_info.title is None:
  97. title = title_generator(field_name, field_info)
  98. if not isinstance(title, str):
  99. raise TypeError(f'field_title_generator {title_generator} must return str, not {title.__class__}')
  100. field_info.title = title
  101. def _apply_alias_generator_to_field_info(
  102. alias_generator: Callable[[str], str] | AliasGenerator, field_name: str, field_info: FieldInfo
  103. ):
  104. """Apply an alias generator to aliases on a `FieldInfo` instance if appropriate.
  105. Args:
  106. alias_generator: A callable that takes a string and returns a string, or an `AliasGenerator` instance.
  107. field_name: The name of the field from which to generate the alias.
  108. field_info: The `FieldInfo` instance to which the alias generator is (maybe) applied.
  109. """
  110. # Apply an alias_generator if
  111. # 1. An alias is not specified
  112. # 2. An alias is specified, but the priority is <= 1
  113. if (
  114. field_info.alias_priority is None
  115. or field_info.alias_priority <= 1
  116. or field_info.alias is None
  117. or field_info.validation_alias is None
  118. or field_info.serialization_alias is None
  119. ):
  120. alias, validation_alias, serialization_alias = None, None, None
  121. if isinstance(alias_generator, AliasGenerator):
  122. alias, validation_alias, serialization_alias = alias_generator.generate_aliases(field_name)
  123. elif callable(alias_generator):
  124. alias = alias_generator(field_name)
  125. if not isinstance(alias, str):
  126. raise TypeError(f'alias_generator {alias_generator} must return str, not {alias.__class__}')
  127. # if priority is not set, we set to 1
  128. # which supports the case where the alias_generator from a child class is used
  129. # to generate an alias for a field in a parent class
  130. if field_info.alias_priority is None or field_info.alias_priority <= 1:
  131. field_info.alias_priority = 1
  132. # if the priority is 1, then we set the aliases to the generated alias
  133. if field_info.alias_priority == 1:
  134. field_info.serialization_alias = get_first_not_none(serialization_alias, alias)
  135. field_info.validation_alias = get_first_not_none(validation_alias, alias)
  136. field_info.alias = alias
  137. # if any of the aliases are not set, then we set them to the corresponding generated alias
  138. if field_info.alias is None:
  139. field_info.alias = alias
  140. if field_info.serialization_alias is None:
  141. field_info.serialization_alias = get_first_not_none(serialization_alias, alias)
  142. if field_info.validation_alias is None:
  143. field_info.validation_alias = get_first_not_none(validation_alias, alias)
  144. def update_field_from_config(config_wrapper: ConfigWrapper, field_name: str, field_info: FieldInfo) -> None:
  145. """Update the `FieldInfo` instance from the configuration set on the model it belongs to.
  146. This will apply the title and alias generators from the configuration.
  147. Args:
  148. config_wrapper: The configuration from the model.
  149. field_name: The field name the `FieldInfo` instance is attached to.
  150. field_info: The `FieldInfo` instance to update.
  151. """
  152. field_title_generator = field_info.field_title_generator or config_wrapper.field_title_generator
  153. if field_title_generator is not None:
  154. _apply_field_title_generator_to_field_info(field_title_generator, field_name, field_info)
  155. if config_wrapper.alias_generator is not None:
  156. _apply_alias_generator_to_field_info(config_wrapper.alias_generator, field_name, field_info)
  157. _deprecated_method_names = {'dict', 'json', 'copy', '_iter', '_copy_and_set_values', '_calculate_keys'}
  158. _deprecated_classmethod_names = {
  159. 'parse_obj',
  160. 'parse_raw',
  161. 'parse_file',
  162. 'from_orm',
  163. 'construct',
  164. 'schema',
  165. 'schema_json',
  166. 'validate',
  167. 'update_forward_refs',
  168. '_get_value',
  169. }
  170. def collect_model_fields( # noqa: C901
  171. cls: type[BaseModel],
  172. config_wrapper: ConfigWrapper,
  173. ns_resolver: NsResolver | None,
  174. *,
  175. typevars_map: Mapping[TypeVar, Any] | None = None,
  176. ) -> tuple[dict[str, FieldInfo], set[str]]:
  177. """Collect the fields and class variables names of a nascent Pydantic model.
  178. The fields collection process is *lenient*, meaning it won't error if string annotations
  179. fail to evaluate. If this happens, the original annotation (and assigned value, if any)
  180. is stored on the created `FieldInfo` instance.
  181. The `rebuild_model_fields()` should be called at a later point (e.g. when rebuilding the model),
  182. and will make use of these stored attributes.
  183. Args:
  184. cls: BaseModel or dataclass.
  185. config_wrapper: The config wrapper instance.
  186. ns_resolver: Namespace resolver to use when getting model annotations.
  187. typevars_map: A dictionary mapping type variables to their concrete types.
  188. Returns:
  189. A two-tuple containing model fields and class variables names.
  190. Raises:
  191. NameError:
  192. - If there is a conflict between a field name and protected namespaces.
  193. - If there is a field other than `root` in `RootModel`.
  194. - If a field shadows an attribute in the parent model.
  195. """
  196. FieldInfo_ = import_cached_field_info()
  197. BaseModel_ = import_cached_base_model()
  198. bases = cls.__bases__
  199. parent_fields_lookup: dict[str, FieldInfo] = {}
  200. for base in reversed(bases):
  201. if model_fields := getattr(base, '__pydantic_fields__', None):
  202. parent_fields_lookup.update(model_fields)
  203. type_hints = _typing_extra.get_model_type_hints(cls, ns_resolver=ns_resolver)
  204. # https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older
  205. # annotations is only used for finding fields in parent classes
  206. annotations = _typing_extra.safe_get_annotations(cls)
  207. fields: dict[str, FieldInfo] = {}
  208. class_vars: set[str] = set()
  209. for ann_name, (ann_type, evaluated) in type_hints.items():
  210. if ann_name == 'model_config':
  211. # We never want to treat `model_config` as a field
  212. # Note: we may need to change this logic if/when we introduce a `BareModel` class with no
  213. # protected namespaces (where `model_config` might be allowed as a field name)
  214. continue
  215. _check_protected_namespaces(
  216. protected_namespaces=config_wrapper.protected_namespaces,
  217. ann_name=ann_name,
  218. bases=bases,
  219. cls_name=cls.__name__,
  220. )
  221. if _typing_extra.is_classvar_annotation(ann_type):
  222. class_vars.add(ann_name)
  223. continue
  224. assigned_value = getattr(cls, ann_name, PydanticUndefined)
  225. if assigned_value is not PydanticUndefined and (
  226. # One of the deprecated instance methods was used as a field name (e.g. `dict()`):
  227. any(getattr(BaseModel_, depr_name, None) is assigned_value for depr_name in _deprecated_method_names)
  228. # One of the deprecated class methods was used as a field name (e.g. `schema()`):
  229. or (
  230. hasattr(assigned_value, '__func__')
  231. and any(
  232. getattr(getattr(BaseModel_, depr_name, None), '__func__', None) is assigned_value.__func__ # pyright: ignore[reportAttributeAccessIssue]
  233. for depr_name in _deprecated_classmethod_names
  234. )
  235. )
  236. ):
  237. # Then `assigned_value` would be the method, even though no default was specified:
  238. assigned_value = PydanticUndefined
  239. if not is_valid_field_name(ann_name):
  240. continue
  241. if cls.__pydantic_root_model__ and ann_name != 'root':
  242. raise NameError(
  243. f"Unexpected field with name {ann_name!r}; only 'root' is allowed as a field of a `RootModel`"
  244. )
  245. # when building a generic model with `MyModel[int]`, the generic_origin check makes sure we don't get
  246. # "... shadows an attribute" warnings
  247. generic_origin = getattr(cls, '__pydantic_generic_metadata__', {}).get('origin')
  248. for base in bases:
  249. dataclass_fields = {
  250. field.name for field in (dataclasses.fields(base) if dataclasses.is_dataclass(base) else ())
  251. }
  252. if hasattr(base, ann_name):
  253. if base is generic_origin:
  254. # Don't warn when "shadowing" of attributes in parametrized generics
  255. continue
  256. if ann_name in dataclass_fields:
  257. # Don't warn when inheriting stdlib dataclasses whose fields are "shadowed" by defaults being set
  258. # on the class instance.
  259. continue
  260. if ann_name not in annotations:
  261. # Don't warn when a field exists in a parent class but has not been defined in the current class
  262. continue
  263. warnings.warn(
  264. f'Field name "{ann_name}" in "{cls.__qualname__}" shadows an attribute in parent '
  265. f'"{base.__qualname__}"',
  266. UserWarning,
  267. stacklevel=4,
  268. )
  269. if assigned_value is PydanticUndefined: # no assignment, just a plain annotation
  270. if ann_name in annotations or ann_name not in parent_fields_lookup:
  271. # field is either:
  272. # - present in the current model's annotations (and *not* from parent classes)
  273. # - not found on any base classes; this seems to be caused by fields bot getting
  274. # generated due to models not being fully defined while initializing recursive models.
  275. # Nothing stops us from just creating a `FieldInfo` for this type hint, so we do this.
  276. field_info = FieldInfo_.from_annotation(ann_type, _source=AnnotationSource.CLASS)
  277. if not evaluated:
  278. field_info._complete = False
  279. # Store the original annotation that should be used to rebuild
  280. # the field info later:
  281. field_info._original_annotation = ann_type
  282. else:
  283. # The field was present on one of the (possibly multiple) base classes
  284. # copy the field to make sure typevar substitutions don't cause issues with the base classes
  285. field_info = parent_fields_lookup[ann_name]._copy()
  286. else: # An assigned value is present (either the default value, or a `Field()` function)
  287. if isinstance(assigned_value, FieldInfo_) and ismethoddescriptor(assigned_value.default):
  288. # `assigned_value` was fetched using `getattr`, which triggers a call to `__get__`
  289. # for descriptors, so we do the same if the `= field(default=...)` form is used.
  290. # Note that we only do this for method descriptors for now, we might want to
  291. # extend this to any descriptor in the future (by simply checking for
  292. # `hasattr(assigned_value.default, '__get__')`).
  293. default = assigned_value.default.__get__(None, cls)
  294. assigned_value.default = default
  295. assigned_value._attributes_set['default'] = default
  296. field_info = FieldInfo_.from_annotated_attribute(ann_type, assigned_value, _source=AnnotationSource.CLASS)
  297. # Store the original annotation and assignment value that should be used to rebuild the field info later.
  298. # Note that the assignment is always stored as the annotation might contain a type var that is later
  299. # parameterized with an unknown forward reference (and we'll need it to rebuild the field info):
  300. field_info._original_assignment = assigned_value
  301. if not evaluated:
  302. field_info._complete = False
  303. field_info._original_annotation = ann_type
  304. elif 'final' in field_info._qualifiers and not field_info.is_required():
  305. warnings.warn(
  306. f'Annotation {ann_name!r} is marked as final and has a default value. Pydantic treats {ann_name!r} as a '
  307. 'class variable, but it will be considered as a normal field in V3 to be aligned with dataclasses. If you '
  308. f'still want {ann_name!r} to be considered as a class variable, annotate it as: `ClassVar[<type>] = <default>.`',
  309. category=PydanticDeprecatedSince211,
  310. # Incorrect when `create_model` is used, but the chance that final with a default is used is low in that case:
  311. stacklevel=4,
  312. )
  313. class_vars.add(ann_name)
  314. continue
  315. # attributes which are fields are removed from the class namespace:
  316. # 1. To match the behaviour of annotation-only fields
  317. # 2. To avoid false positives in the NameError check above
  318. try:
  319. delattr(cls, ann_name)
  320. except AttributeError:
  321. pass # indicates the attribute was on a parent class
  322. # Use cls.__dict__['__pydantic_decorators__'] instead of cls.__pydantic_decorators__
  323. # to make sure the decorators have already been built for this exact class
  324. decorators: DecoratorInfos = cls.__dict__['__pydantic_decorators__']
  325. if ann_name in decorators.computed_fields:
  326. raise TypeError(
  327. f'Field {ann_name!r} of class {cls.__name__!r} overrides symbol of same name in a parent class. '
  328. 'This override with a computed_field is incompatible.'
  329. )
  330. fields[ann_name] = field_info
  331. if field_info._complete:
  332. # If not complete, this will be called in `rebuild_model_fields()`:
  333. update_field_from_config(config_wrapper, ann_name, field_info)
  334. if typevars_map:
  335. for field in fields.values():
  336. if field._complete:
  337. field.apply_typevars_map(typevars_map)
  338. if config_wrapper.use_attribute_docstrings:
  339. _update_fields_from_docstrings(cls, fields)
  340. return fields, class_vars
  341. def rebuild_model_fields(
  342. cls: type[BaseModel],
  343. *,
  344. config_wrapper: ConfigWrapper,
  345. ns_resolver: NsResolver,
  346. typevars_map: Mapping[TypeVar, Any],
  347. ) -> dict[str, FieldInfo]:
  348. """Rebuild the (already present) model fields by trying to reevaluate annotations.
  349. This function should be called whenever a model with incomplete fields is encountered.
  350. Raises:
  351. NameError: If one of the annotations failed to evaluate.
  352. Note:
  353. This function *doesn't* mutate the model fields in place, as it can be called during
  354. schema generation, where you don't want to mutate other model's fields.
  355. """
  356. FieldInfo_ = import_cached_field_info()
  357. rebuilt_fields: dict[str, FieldInfo] = {}
  358. with ns_resolver.push(cls):
  359. for f_name, field_info in cls.__pydantic_fields__.items():
  360. if field_info._complete:
  361. rebuilt_fields[f_name] = field_info
  362. else:
  363. existing_desc = field_info.description
  364. ann = _typing_extra.eval_type(
  365. field_info._original_annotation,
  366. *ns_resolver.types_namespace,
  367. )
  368. ann = _generics.replace_types(ann, typevars_map)
  369. if (assign := field_info._original_assignment) is PydanticUndefined:
  370. new_field = FieldInfo_.from_annotation(ann, _source=AnnotationSource.CLASS)
  371. else:
  372. new_field = FieldInfo_.from_annotated_attribute(ann, assign, _source=AnnotationSource.CLASS)
  373. # The description might come from the docstring if `use_attribute_docstrings` was `True`:
  374. new_field.description = new_field.description if new_field.description is not None else existing_desc
  375. update_field_from_config(config_wrapper, f_name, new_field)
  376. rebuilt_fields[f_name] = new_field
  377. return rebuilt_fields
  378. def collect_dataclass_fields(
  379. cls: type[StandardDataclass],
  380. *,
  381. config_wrapper: ConfigWrapper,
  382. ns_resolver: NsResolver | None = None,
  383. typevars_map: dict[Any, Any] | None = None,
  384. ) -> dict[str, FieldInfo]:
  385. """Collect the fields of a dataclass.
  386. Args:
  387. cls: dataclass.
  388. config_wrapper: The config wrapper instance.
  389. ns_resolver: Namespace resolver to use when getting dataclass annotations.
  390. Defaults to an empty instance.
  391. typevars_map: A dictionary mapping type variables to their concrete types.
  392. Returns:
  393. The dataclass fields.
  394. """
  395. FieldInfo_ = import_cached_field_info()
  396. fields: dict[str, FieldInfo] = {}
  397. ns_resolver = ns_resolver or NsResolver()
  398. dataclass_fields = cls.__dataclass_fields__
  399. # The logic here is similar to `_typing_extra.get_cls_type_hints`,
  400. # although we do it manually as stdlib dataclasses already have annotations
  401. # collected in each class:
  402. for base in reversed(cls.__mro__):
  403. if not dataclasses.is_dataclass(base):
  404. continue
  405. with ns_resolver.push(base):
  406. for ann_name, dataclass_field in dataclass_fields.items():
  407. base_anns = _typing_extra.safe_get_annotations(base)
  408. if ann_name not in base_anns:
  409. # `__dataclass_fields__`contains every field, even the ones from base classes.
  410. # Only collect the ones defined on `base`.
  411. continue
  412. globalns, localns = ns_resolver.types_namespace
  413. ann_type, evaluated = _typing_extra.try_eval_type(dataclass_field.type, globalns, localns)
  414. if _typing_extra.is_classvar_annotation(ann_type):
  415. continue
  416. if (
  417. not dataclass_field.init
  418. and dataclass_field.default is dataclasses.MISSING
  419. and dataclass_field.default_factory is dataclasses.MISSING
  420. ):
  421. # TODO: We should probably do something with this so that validate_assignment behaves properly
  422. # Issue: https://github.com/pydantic/pydantic/issues/5470
  423. continue
  424. if isinstance(dataclass_field.default, FieldInfo_):
  425. if dataclass_field.default.init_var:
  426. if dataclass_field.default.init is False:
  427. raise PydanticUserError(
  428. f'Dataclass field {ann_name} has init=False and init_var=True, but these are mutually exclusive.',
  429. code='clashing-init-and-init-var',
  430. )
  431. # TODO: same note as above re validate_assignment
  432. continue
  433. field_info = FieldInfo_.from_annotated_attribute(
  434. ann_type, dataclass_field.default, _source=AnnotationSource.DATACLASS
  435. )
  436. field_info._original_assignment = dataclass_field.default
  437. else:
  438. field_info = FieldInfo_.from_annotated_attribute(
  439. ann_type, dataclass_field, _source=AnnotationSource.DATACLASS
  440. )
  441. field_info._original_assignment = dataclass_field
  442. if not evaluated:
  443. field_info._complete = False
  444. field_info._original_annotation = ann_type
  445. fields[ann_name] = field_info
  446. update_field_from_config(config_wrapper, ann_name, field_info)
  447. if field_info.default is not PydanticUndefined and isinstance(
  448. getattr(cls, ann_name, field_info), FieldInfo_
  449. ):
  450. # We need this to fix the default when the "default" from __dataclass_fields__ is a pydantic.FieldInfo
  451. setattr(cls, ann_name, field_info.default)
  452. if typevars_map:
  453. for field in fields.values():
  454. # We don't pass any ns, as `field.annotation`
  455. # was already evaluated. TODO: is this method relevant?
  456. # Can't we juste use `_generics.replace_types`?
  457. field.apply_typevars_map(typevars_map)
  458. if config_wrapper.use_attribute_docstrings:
  459. _update_fields_from_docstrings(
  460. cls,
  461. fields,
  462. # We can't rely on the (more reliable) frame inspection method
  463. # for stdlib dataclasses:
  464. use_inspect=not hasattr(cls, '__is_pydantic_dataclass__'),
  465. )
  466. return fields
  467. def rebuild_dataclass_fields(
  468. cls: type[PydanticDataclass],
  469. *,
  470. config_wrapper: ConfigWrapper,
  471. ns_resolver: NsResolver,
  472. typevars_map: Mapping[TypeVar, Any],
  473. ) -> dict[str, FieldInfo]:
  474. """Rebuild the (already present) dataclass fields by trying to reevaluate annotations.
  475. This function should be called whenever a dataclass with incomplete fields is encountered.
  476. Raises:
  477. NameError: If one of the annotations failed to evaluate.
  478. Note:
  479. This function *doesn't* mutate the dataclass fields in place, as it can be called during
  480. schema generation, where you don't want to mutate other dataclass's fields.
  481. """
  482. FieldInfo_ = import_cached_field_info()
  483. rebuilt_fields: dict[str, FieldInfo] = {}
  484. with ns_resolver.push(cls):
  485. for f_name, field_info in cls.__pydantic_fields__.items():
  486. if field_info._complete:
  487. rebuilt_fields[f_name] = field_info
  488. else:
  489. existing_desc = field_info.description
  490. ann = _typing_extra.eval_type(
  491. field_info._original_annotation,
  492. *ns_resolver.types_namespace,
  493. )
  494. ann = _generics.replace_types(ann, typevars_map)
  495. new_field = FieldInfo_.from_annotated_attribute(
  496. ann,
  497. field_info._original_assignment,
  498. _source=AnnotationSource.DATACLASS,
  499. )
  500. # The description might come from the docstring if `use_attribute_docstrings` was `True`:
  501. new_field.description = new_field.description if new_field.description is not None else existing_desc
  502. update_field_from_config(config_wrapper, f_name, new_field)
  503. rebuilt_fields[f_name] = new_field
  504. return rebuilt_fields
  505. def is_valid_field_name(name: str) -> bool:
  506. return not name.startswith('_')
  507. def is_valid_privateattr_name(name: str) -> bool:
  508. return name.startswith('_') and not name.startswith('__')
  509. def takes_validated_data_argument(
  510. default_factory: Callable[[], Any] | Callable[[dict[str, Any]], Any],
  511. ) -> TypeIs[Callable[[dict[str, Any]], Any]]:
  512. """Whether the provided default factory callable has a validated data parameter."""
  513. try:
  514. sig = signature(default_factory)
  515. except (ValueError, TypeError):
  516. # `inspect.signature` might not be able to infer a signature, e.g. with C objects.
  517. # In this case, we assume no data argument is present:
  518. return False
  519. parameters = list(sig.parameters.values())
  520. return len(parameters) == 1 and can_be_positional(parameters[0]) and parameters[0].default is Parameter.empty