Coverage for pydantic/_internal/_fields.py: 98.07%
197 statements
« prev ^ index » next coverage.py v7.8.1, created at 2025-05-22 20:36 +0000
« prev ^ index » next coverage.py v7.8.1, created at 2025-05-22 20:36 +0000
1"""Private logic related to fields (the `Field()` function and `FieldInfo` class), and arguments to `Annotated`."""
3from __future__ import annotations as _annotations 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
5import dataclasses 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
6import warnings 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
7from collections.abc import Mapping 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
8from copy import copy 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
9from functools import cache 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
10from inspect import Parameter, ismethoddescriptor, signature 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
11from re import Pattern 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
12from typing import TYPE_CHECKING, Any, Callable, TypeVar 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
14from pydantic_core import PydanticUndefined 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
15from typing_extensions import TypeIs, get_origin 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
16from typing_inspection import typing_objects 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
17from typing_inspection.introspection import AnnotationSource 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
19from pydantic import PydanticDeprecatedSince211 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
20from pydantic.errors import PydanticUserError 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
22from . import _generics, _typing_extra 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
23from ._config import ConfigWrapper 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
24from ._docs_extraction import extract_docstrings_from_cls 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
25from ._import_utils import import_cached_base_model, import_cached_field_info 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
26from ._namespace_utils import NsResolver 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
27from ._repr import Representation 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
28from ._utils import can_be_positional 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
30if TYPE_CHECKING: 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
31 from annotated_types import BaseMetadata
33 from ..fields import FieldInfo
34 from ..main import BaseModel
35 from ._dataclasses import StandardDataclass
36 from ._decorators import DecoratorInfos
39class PydanticMetadata(Representation): 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
40 """Base class for annotation markers like `Strict`."""
42 __slots__ = () 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
45def pydantic_general_metadata(**metadata: Any) -> BaseMetadata: 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
46 """Create a new `_PydanticGeneralMetadata` class with the given metadata.
48 Args:
49 **metadata: The metadata to add.
51 Returns:
52 The new `_PydanticGeneralMetadata` class.
53 """
54 return _general_metadata_cls()(metadata) # type: ignore 1esakblftguvIHwxmnopyzABChDcqdriEjFG
57@cache 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
58def _general_metadata_cls() -> type[BaseMetadata]: 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
59 """Do it this way to avoid importing `annotated_types` at import time."""
60 from annotated_types import BaseMetadata 1esakblftguvIHwxmnopyzABChDcqdriEjFG
62 class _PydanticGeneralMetadata(PydanticMetadata, BaseMetadata): 1esakblftguvIHwxmnopyzABChDcqdriEjFG
63 """Pydantic general metadata like `max_digits`."""
65 def __init__(self, metadata: Any): 1esakblftguvIHwxmnopyzABChDcqdriEjFG
66 self.__dict__ = metadata 1esakblftguvIHwxmnopyzABChDcqdriEjFG
68 return _PydanticGeneralMetadata # type: ignore 1esakblftguvIHwxmnopyzABChDcqdriEjFG
71def _update_fields_from_docstrings(cls: type[Any], fields: dict[str, FieldInfo], use_inspect: bool = False) -> None: 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
72 fields_docs = extract_docstrings_from_cls(cls, use_inspect=use_inspect) 1esakblftguvIHwxmnopyzABChDcqdriEjFG
73 for ann_name, field_info in fields.items(): 1esakblftguvIHwxmnopyzABChDcqdriEjFG
74 if field_info.description is None and ann_name in fields_docs: 1esakblftguvIHwxmnopyzABChDcqdriEjFG
75 field_info.description = fields_docs[ann_name] 1esakblftguvIHwxmnopyzABChDcqdriEjFG
78def collect_model_fields( # noqa: C901 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
79 cls: type[BaseModel],
80 config_wrapper: ConfigWrapper,
81 ns_resolver: NsResolver | None,
82 *,
83 typevars_map: Mapping[TypeVar, Any] | None = None,
84) -> tuple[dict[str, FieldInfo], set[str]]:
85 """Collect the fields and class variables names of a nascent Pydantic model.
87 The fields collection process is *lenient*, meaning it won't error if string annotations
88 fail to evaluate. If this happens, the original annotation (and assigned value, if any)
89 is stored on the created `FieldInfo` instance.
91 The `rebuild_model_fields()` should be called at a later point (e.g. when rebuilding the model),
92 and will make use of these stored attributes.
94 Args:
95 cls: BaseModel or dataclass.
96 config_wrapper: The config wrapper instance.
97 ns_resolver: Namespace resolver to use when getting model annotations.
98 typevars_map: A dictionary mapping type variables to their concrete types.
100 Returns:
101 A two-tuple containing model fields and class variables names.
103 Raises:
104 NameError:
105 - If there is a conflict between a field name and protected namespaces.
106 - If there is a field other than `root` in `RootModel`.
107 - If a field shadows an attribute in the parent model.
108 """
109 BaseModel = import_cached_base_model() 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
110 FieldInfo_ = import_cached_field_info() 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
112 bases = cls.__bases__ 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
113 parent_fields_lookup: dict[str, FieldInfo] = {} 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
114 for base in reversed(bases): 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
115 if model_fields := getattr(base, '__pydantic_fields__', None): 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
116 parent_fields_lookup.update(model_fields) 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
118 type_hints = _typing_extra.get_model_type_hints(cls, ns_resolver=ns_resolver) 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
120 # https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older
121 # annotations is only used for finding fields in parent classes
122 annotations = cls.__dict__.get('__annotations__', {}) 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
123 fields: dict[str, FieldInfo] = {} 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
125 class_vars: set[str] = set() 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
126 for ann_name, (ann_type, evaluated) in type_hints.items(): 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
127 if ann_name == 'model_config': 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
128 # We never want to treat `model_config` as a field
129 # Note: we may need to change this logic if/when we introduce a `BareModel` class with no
130 # protected namespaces (where `model_config` might be allowed as a field name)
131 continue 1esakblftguvIHwxmnopyzABChDcqdriEjFG
133 for protected_namespace in config_wrapper.protected_namespaces: 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
134 ns_violation: bool = False 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
135 if isinstance(protected_namespace, Pattern): 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
136 ns_violation = protected_namespace.match(ann_name) is not None 1esakblftguvIHwxmnopyzABChDcqdriEjFG
137 elif isinstance(protected_namespace, str): 137 ↛ 140line 137 didn't jump to line 140 because the condition on line 137 was always true1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
138 ns_violation = ann_name.startswith(protected_namespace) 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
140 if ns_violation: 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
141 for b in bases: 1esakblftguvIHwxmnopyzABChDcqdriEjFG
142 if hasattr(b, ann_name): 1esakblftguvIHwxmnopyzABChDcqdriEjFG
143 if not (issubclass(b, BaseModel) and ann_name in getattr(b, '__pydantic_fields__', {})): 143 ↛ 141line 143 didn't jump to line 141 because the condition on line 143 was always true1eabfghcdij
144 raise NameError( 1eabfghcdij
145 f'Field "{ann_name}" conflicts with member {getattr(b, ann_name)}'
146 f' of protected namespace "{protected_namespace}".'
147 )
148 else:
149 valid_namespaces = () 1esakblftguvIHwxmnopyzABChDcqdriEjFG
150 for pn in config_wrapper.protected_namespaces: 1esakblftguvIHwxmnopyzABChDcqdriEjFG
151 if isinstance(pn, Pattern): 1esakblftguvIHwxmnopyzABChDcqdriEjFG
152 if not pn.match(ann_name): 152 ↛ 153line 152 didn't jump to line 153 because the condition on line 152 was never true1esakblftguvIHwxmnopyzABChDcqdriEjFG
153 valid_namespaces += (f're.compile({pn.pattern})',)
154 else:
155 if not ann_name.startswith(pn): 1esakblftguvIHwxmnopyzABChDcqdriEjFG
156 valid_namespaces += (pn,) 1esakblftguvIHwxmnopyzABChDcqdriEjFG
158 warnings.warn( 1esakblftguvIHwxmnopyzABChDcqdriEjFG
159 f'Field "{ann_name}" in {cls.__name__} has conflict with protected namespace "{protected_namespace}".'
160 '\n\nYou may be able to resolve this warning by setting'
161 f" `model_config['protected_namespaces'] = {valid_namespaces}`.",
162 UserWarning,
163 )
164 if _typing_extra.is_classvar_annotation(ann_type): 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
165 class_vars.add(ann_name) 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
166 continue 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
168 assigned_value = getattr(cls, ann_name, PydanticUndefined) 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
170 if not is_valid_field_name(ann_name): 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
171 continue 1esakblftguvIHwxmnopyzABChDcqdriEjFG
172 if cls.__pydantic_root_model__ and ann_name != 'root': 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
173 raise NameError( 1esakblftguvIHwxmnopyzABChDcqdriEjFG
174 f"Unexpected field with name {ann_name!r}; only 'root' is allowed as a field of a `RootModel`"
175 )
177 # when building a generic model with `MyModel[int]`, the generic_origin check makes sure we don't get
178 # "... shadows an attribute" warnings
179 generic_origin = getattr(cls, '__pydantic_generic_metadata__', {}).get('origin') 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
180 for base in bases: 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
181 dataclass_fields = { 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
182 field.name for field in (dataclasses.fields(base) if dataclasses.is_dataclass(base) else ())
183 }
184 if hasattr(base, ann_name): 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
185 if base is generic_origin: 185 ↛ 187line 185 didn't jump to line 187 because the condition on line 185 was never true1esakblftguvIHwxmnopyzABChDcqdriEjFG
186 # Don't warn when "shadowing" of attributes in parametrized generics
187 continue
189 if ann_name in dataclass_fields: 1esakblftguvIHwxmnopyzABChDcqdriEjFG
190 # Don't warn when inheriting stdlib dataclasses whose fields are "shadowed" by defaults being set
191 # on the class instance.
192 continue 1esakblftguvIHwxmnopyzABChDcqdriEjFG
194 if ann_name not in annotations: 1esakblftguvIHwxmnopyzABChDcqdriEjFG
195 # Don't warn when a field exists in a parent class but has not been defined in the current class
196 continue 1esakblftguvIHwxmnopyzABChDcqdriEjFG
198 warnings.warn( 1esakblftguvIHwxmnopyzABChDcqdriEjFG
199 f'Field name "{ann_name}" in "{cls.__qualname__}" shadows an attribute in parent '
200 f'"{base.__qualname__}"',
201 UserWarning,
202 )
204 if assigned_value is PydanticUndefined: # no assignment, just a plain annotation 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
205 if ann_name in annotations or ann_name not in parent_fields_lookup: 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
206 # field is either:
207 # - present in the current model's annotations (and *not* from parent classes)
208 # - not found on any base classes; this seems to be caused by fields bot getting
209 # generated due to models not being fully defined while initializing recursive models.
210 # Nothing stops us from just creating a `FieldInfo` for this type hint, so we do this.
211 field_info = FieldInfo_.from_annotation(ann_type, _source=AnnotationSource.CLASS) 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
212 if not evaluated: 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
213 field_info._complete = False 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
214 # Store the original annotation that should be used to rebuild
215 # the field info later:
216 field_info._original_annotation = ann_type 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
217 else:
218 # The field was present on one of the (possibly multiple) base classes
219 # copy the field to make sure typevar substitutions don't cause issues with the base classes
220 field_info = copy(parent_fields_lookup[ann_name]) 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
222 else: # An assigned value is present (either the default value, or a `Field()` function)
223 _warn_on_nested_alias_in_annotation(ann_type, ann_name) 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
224 if isinstance(assigned_value, FieldInfo_) and ismethoddescriptor(assigned_value.default): 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
225 # `assigned_value` was fetched using `getattr`, which triggers a call to `__get__`
226 # for descriptors, so we do the same if the `= field(default=...)` form is used.
227 # Note that we only do this for method descriptors for now, we might want to
228 # extend this to any descriptor in the future (by simply checking for
229 # `hasattr(assigned_value.default, '__get__')`).
230 assigned_value.default = assigned_value.default.__get__(None, cls) 1esakblftguvIHwxmnopyzABChDcqdriEjFG
232 # The `from_annotated_attribute()` call below mutates the assigned `Field()`, so make a copy:
233 original_assignment = ( 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
234 assigned_value._copy() if not evaluated and isinstance(assigned_value, FieldInfo_) else assigned_value
235 )
237 field_info = FieldInfo_.from_annotated_attribute(ann_type, assigned_value, _source=AnnotationSource.CLASS) 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
238 if not evaluated: 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
239 field_info._complete = False 1esakblftguvIHwxmnopyzABChDcqdriEjFG
240 # Store the original annotation and assignment value that should be used to rebuild
241 # the field info later:
242 field_info._original_annotation = ann_type 1esakblftguvIHwxmnopyzABChDcqdriEjFG
243 field_info._original_assignment = original_assignment 1esakblftguvIHwxmnopyzABChDcqdriEjFG
244 elif 'final' in field_info._qualifiers and not field_info.is_required(): 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
245 warnings.warn( 1esakblftguvIHwxmnopyzABChDcqdriEjFG
246 f'Annotation {ann_name!r} is marked as final and has a default value. Pydantic treats {ann_name!r} as a '
247 'class variable, but it will be considered as a normal field in V3 to be aligned with dataclasses. If you '
248 f'still want {ann_name!r} to be considered as a class variable, annotate it as: `ClassVar[<type>] = <default>.`',
249 category=PydanticDeprecatedSince211,
250 # Incorrect when `create_model` is used, but the chance that final with a default is used is low in that case:
251 stacklevel=4,
252 )
253 class_vars.add(ann_name) 1esakblftguvIHwxmnopyzABChDcqdriEjFG
254 continue 1esakblftguvIHwxmnopyzABChDcqdriEjFG
256 # attributes which are fields are removed from the class namespace:
257 # 1. To match the behaviour of annotation-only fields
258 # 2. To avoid false positives in the NameError check above
259 try: 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
260 delattr(cls, ann_name) 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
261 except AttributeError: 1esakblftguvIHwxmnopyzABChDcqdriEjFG
262 pass # indicates the attribute was on a parent class 1esakblftguvIHwxmnopyzABChDcqdriEjFG
264 # Use cls.__dict__['__pydantic_decorators__'] instead of cls.__pydantic_decorators__
265 # to make sure the decorators have already been built for this exact class
266 decorators: DecoratorInfos = cls.__dict__['__pydantic_decorators__'] 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
267 if ann_name in decorators.computed_fields: 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
268 raise TypeError( 1esakblftguvIHwxmnopyzABChDcqdriEjFG
269 f'Field {ann_name!r} of class {cls.__name__!r} overrides symbol of same name in a parent class. '
270 'This override with a computed_field is incompatible.'
271 )
272 fields[ann_name] = field_info 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
274 if typevars_map: 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
275 for field in fields.values(): 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
276 if field._complete: 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
277 field.apply_typevars_map(typevars_map) 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
279 if config_wrapper.use_attribute_docstrings: 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
280 _update_fields_from_docstrings(cls, fields) 1esakblftguvIHwxmnopyzABChDcqdriEjFG
281 return fields, class_vars 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
284def _warn_on_nested_alias_in_annotation(ann_type: type[Any], ann_name: str) -> None: 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
285 FieldInfo = import_cached_field_info() 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
287 args = getattr(ann_type, '__args__', None) 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
288 if args: 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
289 for anno_arg in args: 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
290 if typing_objects.is_annotated(get_origin(anno_arg)): 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
291 for anno_type_arg in _typing_extra.get_args(anno_arg): 1esakblftguvIHwxmnopyzABChDcqdriEjFG
292 if isinstance(anno_type_arg, FieldInfo) and anno_type_arg.alias is not None: 1esakblftguvIHwxmnopyzABChDcqdriEjFG
293 warnings.warn( 1esakblftguvIHwxmnopyzABChDcqdriEjFG
294 f'`alias` specification on field "{ann_name}" must be set on outermost annotation to take effect.',
295 UserWarning,
296 )
297 return 1esakblftguvIHwxmnopyzABChDcqdriEjFG
300def rebuild_model_fields( 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
301 cls: type[BaseModel],
302 *,
303 ns_resolver: NsResolver,
304 typevars_map: Mapping[TypeVar, Any],
305) -> dict[str, FieldInfo]:
306 """Rebuild the (already present) model fields by trying to reevaluate annotations.
308 This function should be called whenever a model with incomplete fields is encountered.
310 Raises:
311 NameError: If one of the annotations failed to evaluate.
313 Note:
314 This function *doesn't* mutate the model fields in place, as it can be called during
315 schema generation, where you don't want to mutate other model's fields.
316 """
317 FieldInfo_ = import_cached_field_info() 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
319 rebuilt_fields: dict[str, FieldInfo] = {} 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
320 with ns_resolver.push(cls): 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
321 for f_name, field_info in cls.__pydantic_fields__.items(): 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
322 if field_info._complete: 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
323 rebuilt_fields[f_name] = field_info 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
324 else:
325 existing_desc = field_info.description 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
326 ann = _typing_extra.eval_type( 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
327 field_info._original_annotation,
328 *ns_resolver.types_namespace,
329 )
330 ann = _generics.replace_types(ann, typevars_map) 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
332 if (assign := field_info._original_assignment) is PydanticUndefined: 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
333 new_field = FieldInfo_.from_annotation(ann, _source=AnnotationSource.CLASS) 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
334 else:
335 new_field = FieldInfo_.from_annotated_attribute(ann, assign, _source=AnnotationSource.CLASS) 1esakblftguvIHwxmnopyzABChDcqdriEjFG
336 # The description might come from the docstring if `use_attribute_docstrings` was `True`:
337 new_field.description = new_field.description if new_field.description is not None else existing_desc 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
338 rebuilt_fields[f_name] = new_field 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
340 return rebuilt_fields 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
343def collect_dataclass_fields( 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
344 cls: type[StandardDataclass],
345 *,
346 ns_resolver: NsResolver | None = None,
347 typevars_map: dict[Any, Any] | None = None,
348 config_wrapper: ConfigWrapper | None = None,
349) -> dict[str, FieldInfo]:
350 """Collect the fields of a dataclass.
352 Args:
353 cls: dataclass.
354 ns_resolver: Namespace resolver to use when getting dataclass annotations.
355 Defaults to an empty instance.
356 typevars_map: A dictionary mapping type variables to their concrete types.
357 config_wrapper: The config wrapper instance.
359 Returns:
360 The dataclass fields.
361 """
362 FieldInfo_ = import_cached_field_info() 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
364 fields: dict[str, FieldInfo] = {} 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
365 ns_resolver = ns_resolver or NsResolver() 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
366 dataclass_fields = cls.__dataclass_fields__ 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
368 # The logic here is similar to `_typing_extra.get_cls_type_hints`,
369 # although we do it manually as stdlib dataclasses already have annotations
370 # collected in each class:
371 for base in reversed(cls.__mro__): 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
372 if not dataclasses.is_dataclass(base): 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
373 continue 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
375 with ns_resolver.push(base): 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
376 for ann_name, dataclass_field in dataclass_fields.items(): 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
377 if ann_name not in base.__dict__.get('__annotations__', {}): 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
378 # `__dataclass_fields__`contains every field, even the ones from base classes.
379 # Only collect the ones defined on `base`.
380 continue 1esakblftguvIHwxmnopyzABChDcqdriEjFG
382 globalns, localns = ns_resolver.types_namespace 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
383 ann_type, _ = _typing_extra.try_eval_type(dataclass_field.type, globalns, localns) 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
385 if _typing_extra.is_classvar_annotation(ann_type): 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
386 continue 1esakblftguvIHwxmnopyzABChDcqdriEjFG
388 if ( 1esakblIHwxmnopJKLhDcqdr
389 not dataclass_field.init
390 and dataclass_field.default is dataclasses.MISSING
391 and dataclass_field.default_factory is dataclasses.MISSING
392 ):
393 # TODO: We should probably do something with this so that validate_assignment behaves properly
394 # Issue: https://github.com/pydantic/pydantic/issues/5470
395 continue 1esakblftguvIHwxmnopyzABChDcqdriEjFG
397 if isinstance(dataclass_field.default, FieldInfo_): 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
398 if dataclass_field.default.init_var: 1esakblftguvIHwxmnopyzABChDcqdriEjFG
399 if dataclass_field.default.init is False: 1esakblftguvIHwxmnopyzABChDcqdriEjFG
400 raise PydanticUserError( 1esakblftguvIHwxmnopyzABChDcqdriEjFG
401 f'Dataclass field {ann_name} has init=False and init_var=True, but these are mutually exclusive.',
402 code='clashing-init-and-init-var',
403 )
405 # TODO: same note as above re validate_assignment
406 continue 1akblftguvHmnopyzABCcqdriEjFG
407 field_info = FieldInfo_.from_annotated_attribute( 1esakblftguvIHwxmnopyzABChDcqdriEjFG
408 ann_type, dataclass_field.default, _source=AnnotationSource.DATACLASS
409 )
410 else:
411 field_info = FieldInfo_.from_annotated_attribute( 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
412 ann_type, dataclass_field, _source=AnnotationSource.DATACLASS
413 )
415 fields[ann_name] = field_info 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
417 if field_info.default is not PydanticUndefined and isinstance( 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
418 getattr(cls, ann_name, field_info), FieldInfo_
419 ):
420 # We need this to fix the default when the "default" from __dataclass_fields__ is a pydantic.FieldInfo
421 setattr(cls, ann_name, field_info.default) 1esakblftguvIHwxmnopyzABChDcqdriEjFG
423 if typevars_map: 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
424 for field in fields.values(): 1esakblftguvIHwxmnopyzABChDcqdriEjFG
425 # We don't pass any ns, as `field.annotation`
426 # was already evaluated. TODO: is this method relevant?
427 # Can't we juste use `_generics.replace_types`?
428 field.apply_typevars_map(typevars_map) 1esakblftguvIHwxmnopyzABChDcqdriEjFG
430 if config_wrapper is not None and config_wrapper.use_attribute_docstrings: 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
431 _update_fields_from_docstrings( 1esakblftguvIHwxmnopyzABChDcqdriEjFG
432 cls,
433 fields,
434 # We can't rely on the (more reliable) frame inspection method
435 # for stdlib dataclasses:
436 use_inspect=not hasattr(cls, '__is_pydantic_dataclass__'),
437 )
439 return fields 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
442def is_valid_field_name(name: str) -> bool: 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
443 return not name.startswith('_') 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
446def is_valid_privateattr_name(name: str) -> bool: 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
447 return name.startswith('_') and not name.startswith('__') 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
450def takes_validated_data_argument( 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
451 default_factory: Callable[[], Any] | Callable[[dict[str, Any]], Any],
452) -> TypeIs[Callable[[dict[str, Any]], Any]]:
453 """Whether the provided default factory callable has a validated data parameter."""
454 try: 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
455 sig = signature(default_factory) 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
456 except (ValueError, TypeError): 1esakblftguvwxmnopyzABChDcqdriEjFG
457 # `inspect.signature` might not be able to infer a signature, e.g. with C objects.
458 # In this case, we assume no data argument is present:
459 return False 1esakblftguvwxmnopyzABChDcqdriEjFG
461 parameters = list(sig.parameters.values()) 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG
463 return len(parameters) == 1 and can_be_positional(parameters[0]) and parameters[0].default is Parameter.empty 1esakblftguvIHwxmnopyzABCJKLMNOPhDcqdriEjFG