Coverage for /pythoncovmergedfiles/medio/medio/src/pydantic/pydantic/_internal/_fields.py: 47%
130 statements
« prev ^ index » next coverage.py v7.2.3, created at 2023-04-27 07:38 +0000
« prev ^ index » next coverage.py v7.2.3, created at 2023-04-27 07:38 +0000
1"""
2Private logic related to fields (the `Field()` function and `FieldInfo` class), and arguments to `Annotated`.
3"""
4from __future__ import annotations as _annotations
6import dataclasses
7import sys
8from abc import ABC, abstractmethod
9from copy import copy
10from typing import TYPE_CHECKING, Any
12from pydantic_core import core_schema
14from . import _typing_extra
15from ._forward_ref import PydanticForwardRef
16from ._repr import Representation
17from ._typing_extra import get_cls_type_hints_lenient, get_type_hints, is_classvar, is_finalvar
19if TYPE_CHECKING:
20 from ..fields import FieldInfo
21 from ._dataclasses import StandardDataclass
24def get_type_hints_infer_globalns(
25 obj: Any,
26 localns: dict[str, Any] | None = None,
27 include_extras: bool = False,
28) -> dict[str, Any]:
29 module_name = getattr(obj, '__module__', None)
30 globalns: dict[str, Any] | None = None
31 if module_name:
32 try:
33 globalns = sys.modules[module_name].__dict__
34 except KeyError:
35 # happens occasionally, see https://github.com/pydantic/pydantic/issues/2363
36 pass
37 return get_type_hints(obj, globalns=globalns, localns=localns, include_extras=include_extras)
40class _UndefinedType:
41 """
42 Singleton class to represent an undefined value.
43 """
45 def __repr__(self) -> str:
46 return 'PydanticUndefined'
48 def __copy__(self) -> _UndefinedType:
49 return self
51 def __reduce__(self) -> str:
52 return 'Undefined'
54 def __deepcopy__(self, _: Any) -> _UndefinedType:
55 return self
58Undefined = _UndefinedType()
61class PydanticMetadata(Representation):
62 """
63 Base class for annotation markers like `Strict`.
64 """
66 __slots__ = ()
69class PydanticGeneralMetadata(PydanticMetadata):
70 def __init__(self, **metadata: Any):
71 self.__dict__ = metadata
74class SchemaRef(Representation):
75 """
76 Holds a reference to another schema.
77 """
79 __slots__ = ('__pydantic_core_schema__',)
81 def __init__(self, schema: core_schema.CoreSchema):
82 self.__pydantic_core_schema__ = schema
85class CustomValidator(ABC):
86 """
87 Used to define functional validators which can be updated with constraints.
88 """
90 @abstractmethod
91 def __pydantic_update_schema__(self, schema: core_schema.CoreSchema, **constraints: Any) -> None:
92 raise NotImplementedError()
94 @abstractmethod
95 def __call__(self, __input_value: Any, __info: core_schema.ValidationInfo) -> Any:
96 raise NotImplementedError()
98 def _update_attrs(self, constraints: dict[str, Any], attrs: set[str] | None = None) -> None:
99 """
100 Utility for updating attributes/slots and raising an error if they don't exist, to be used by
101 implementations of `CustomValidator`.
102 """
103 attrs = attrs or set(self.__slots__) # type: ignore[attr-defined]
104 for k, v in constraints.items():
105 if k not in attrs:
106 raise TypeError(f'{k!r} is not a valid constraint for {self.__class__.__name__}')
107 setattr(self, k, v)
110# KW_ONLY is only available in Python 3.10+
111DC_KW_ONLY = getattr(dataclasses, 'KW_ONLY', None)
114def collect_model_fields( # noqa: C901
115 cls: type[Any],
116 bases: tuple[type[Any], ...],
117 types_namespace: dict[str, Any] | None,
118 *,
119 typevars_map: dict[Any, Any] | None = None,
120) -> tuple[dict[str, FieldInfo], set[str]]:
121 """
122 Collect the fields of a nascent pydantic model
124 Also collect the names of any ClassVars present in the type hints.
126 The returned value is a tuple of two items: the fields dict, and the set of ClassVar names.
128 :param cls: BaseModel or dataclass
129 :param bases: parents of the class, generally `cls.__bases__`
130 :param types_namespace: optional extra namespace to look for types in
131 """
132 from ..fields import FieldInfo
134 type_hints = get_cls_type_hints_lenient(cls, types_namespace)
136 # https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older
137 # annotations is only used for finding fields in parent classes
138 annotations = cls.__dict__.get('__annotations__', {})
139 fields: dict[str, FieldInfo] = {}
141 class_vars: set[str] = set()
142 for ann_name, ann_type in type_hints.items():
143 if is_classvar(ann_type):
144 class_vars.add(ann_name)
145 continue
146 if _is_finalvar_with_default_val(ann_type, getattr(cls, ann_name, Undefined)):
147 class_vars.add(ann_name)
148 continue
149 if ann_name.startswith('_'):
150 continue
152 # when building a generic model with `MyModel[int]`, the generic_origin check makes sure we don't get
153 # "... shadows an attribute" errors
154 generic_origin = getattr(cls, '__pydantic_generic_metadata__', {}).get('origin')
155 for base in bases:
156 if hasattr(base, ann_name):
157 if base is generic_origin:
158 # Don't error when "shadowing" of attributes in parametrized generics
159 continue
160 raise NameError(
161 f'Field name "{ann_name}" shadows an attribute in parent "{base.__qualname__}"; '
162 f'you might want to use a different field name with "alias=\'{ann_name}\'".'
163 )
165 try:
166 default = getattr(cls, ann_name, Undefined)
167 if default is Undefined:
168 raise AttributeError
169 except AttributeError:
170 if ann_name in annotations or isinstance(ann_type, PydanticForwardRef):
171 field_info = FieldInfo.from_annotation(ann_type)
172 else:
173 # if field has no default value and is not in __annotations__ this means that it is
174 # defined in a base class and we can take it from there
175 model_fields_lookup: dict[str, FieldInfo] = {}
176 for x in cls.__bases__[::-1]:
177 model_fields_lookup.update(getattr(x, 'model_fields', {}))
178 if ann_name in model_fields_lookup:
179 # The field was present on one of the (possibly multiple) base classes
180 # copy the field to make sure typevar substitutions don't cause issues with the base classes
181 field_info = copy(model_fields_lookup[ann_name])
182 else:
183 # The field was not found on any base classes; this seems to be caused by fields not getting
184 # generated thanks to models not being fully defined while initializing recursive models.
185 # Nothing stops us from just creating a new FieldInfo for this type hint, so we do this.
186 field_info = FieldInfo.from_annotation(ann_type)
187 else:
188 field_info = FieldInfo.from_annotated_attribute(ann_type, default)
189 # attributes which are fields are removed from the class namespace:
190 # 1. To match the behaviour of annotation-only fields
191 # 2. To avoid false positives in the NameError check above
192 try:
193 delattr(cls, ann_name)
194 except AttributeError:
195 pass # indicates the attribute was on a parent class
197 fields[ann_name] = field_info
199 if typevars_map:
200 for field in fields.values():
201 field.apply_typevars_map(typevars_map, types_namespace)
203 return fields, class_vars
206def _is_finalvar_with_default_val(type_: type[Any], val: Any) -> bool:
207 from ..fields import FieldInfo
209 if not is_finalvar(type_):
210 return False
211 elif val is Undefined:
212 return False
213 elif isinstance(val, FieldInfo) and (val.default is Undefined and val.default_factory is None):
214 return False
215 else:
216 return True
219def collect_dataclass_fields(
220 cls: type[StandardDataclass], types_namespace: dict[str, Any] | None, *, typevars_map: dict[Any, Any] | None = None
221) -> dict[str, FieldInfo]:
222 from ..fields import FieldInfo
224 fields: dict[str, FieldInfo] = {}
225 dataclass_fields: dict[str, dataclasses.Field] = cls.__dataclass_fields__
226 cls_localns = dict(vars(cls)) # this matches get_cls_type_hints_lenient, but all tests pass with `= None` instead
228 for ann_name, dataclass_field in dataclass_fields.items():
229 ann_type = _typing_extra.eval_type_lenient(dataclass_field.type, types_namespace, cls_localns)
230 if is_classvar(ann_type):
231 continue
233 if not dataclass_field.init:
234 # TODO: We should probably do something with this so that validate_assignment behaves properly
235 # See https://github.com/pydantic/pydantic/issues/5470
236 continue
238 if isinstance(dataclass_field.default, FieldInfo):
239 field_info = FieldInfo.from_annotated_attribute(ann_type, dataclass_field.default)
240 else:
241 field_info = FieldInfo.from_annotated_attribute(ann_type, dataclass_field)
242 fields[ann_name] = field_info
244 if field_info.default is not Undefined:
245 # We need this to fix the default when the "default" from __dataclass_fields__ is a pydantic.FieldInfo
246 setattr(cls, ann_name, field_info.default)
248 if typevars_map:
249 for field in fields.values():
250 field.apply_typevars_map(typevars_map, types_namespace)
252 return fields