_fields.py 27 KB

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