Collect the fields and class variables names of a nascent Pydantic model. The fields collection process is *lenient*, meaning it won't error if string annotations fail to evaluate. If this happens, the original annotation (and assigned value, if any) is stored on the created `FieldInfo`
( # noqa: C901
cls: type[BaseModel],
config_wrapper: ConfigWrapper,
ns_resolver: NsResolver,
*,
typevars_map: Mapping[TypeVar, Any] | None = None,
)
| 222 | |
| 223 | |
| 224 | def collect_model_fields( # noqa: C901 |
| 225 | cls: type[BaseModel], |
| 226 | config_wrapper: ConfigWrapper, |
| 227 | ns_resolver: NsResolver, |
| 228 | *, |
| 229 | typevars_map: Mapping[TypeVar, Any] | None = None, |
| 230 | ) -> tuple[dict[str, FieldInfo], PydanticExtraInfo | None, set[str]]: |
| 231 | """Collect the fields and class variables names of a nascent Pydantic model. |
| 232 | |
| 233 | The fields collection process is *lenient*, meaning it won't error if string annotations |
| 234 | fail to evaluate. If this happens, the original annotation (and assigned value, if any) |
| 235 | is stored on the created `FieldInfo` instance. |
| 236 | |
| 237 | The `rebuild_model_fields()` should be called at a later point (e.g. when rebuilding the model), |
| 238 | and will make use of these stored attributes. |
| 239 | |
| 240 | Args: |
| 241 | cls: BaseModel or dataclass. |
| 242 | config_wrapper: The config wrapper instance. |
| 243 | ns_resolver: Namespace resolver to use when getting model annotations. |
| 244 | typevars_map: A dictionary mapping type variables to their concrete types. |
| 245 | |
| 246 | Returns: |
| 247 | A three-tuple containing the model fields, the `PydanticExtraInfo` instance if the `__pydantic_extra__` annotation is set, |
| 248 | and class variables names. |
| 249 | |
| 250 | Raises: |
| 251 | NameError: |
| 252 | - If there is a conflict between a field name and protected namespaces. |
| 253 | - If there is a field other than `root` in `RootModel`. |
| 254 | - If a field shadows an attribute in the parent model. |
| 255 | """ |
| 256 | FieldInfo_ = import_cached_field_info() |
| 257 | BaseModel_ = import_cached_base_model() |
| 258 | |
| 259 | bases = cls.__bases__ |
| 260 | parent_fields_lookup: dict[str, FieldInfo] = {} |
| 261 | for base in reversed(bases): |
| 262 | if model_fields := getattr(base, '__pydantic_fields__', None): |
| 263 | parent_fields_lookup.update(model_fields) |
| 264 | |
| 265 | type_hints = _typing_extra.get_model_type_hints(cls, ns_resolver=ns_resolver) |
| 266 | |
| 267 | # `cls_annotations` is only used to determine if an annotation comes from a parent class |
| 268 | cls_annotations = _typing_extra.safe_get_annotations(cls) |
| 269 | |
| 270 | fields: dict[str, FieldInfo] = {} |
| 271 | |
| 272 | class_vars: set[str] = set() |
| 273 | for ann_name, (ann_type, evaluated) in type_hints.items(): |
| 274 | if ann_name == 'model_config': |
| 275 | # We never want to treat `model_config` as a field |
| 276 | # Note: we may need to change this logic if/when we introduce a `BareModel` class with no |
| 277 | # protected namespaces (where `model_config` might be allowed as a field name) |
| 278 | continue |
| 279 | |
| 280 | _check_protected_namespaces( |
| 281 | protected_namespaces=config_wrapper.protected_namespaces, |
no test coverage detected