Collects the fields for the model, accounting for parent classes.
(
self, model_config: ModelConfigData, is_root_model: bool
)
| 625 | return config |
| 626 | |
| 627 | def collect_fields_and_class_vars( |
| 628 | self, model_config: ModelConfigData, is_root_model: bool |
| 629 | ) -> tuple[list[PydanticModelField] | None, list[PydanticModelClassVar] | None]: |
| 630 | """Collects the fields for the model, accounting for parent classes.""" |
| 631 | cls = self._cls |
| 632 | |
| 633 | # First, collect fields and ClassVars belonging to any class in the MRO, ignoring duplicates. |
| 634 | # |
| 635 | # We iterate through the MRO in reverse because attrs defined in the parent must appear |
| 636 | # earlier in the attributes list than attrs defined in the child. See: |
| 637 | # https://docs.python.org/3/library/dataclasses.html#inheritance |
| 638 | # |
| 639 | # However, we also want fields defined in the subtype to override ones defined |
| 640 | # in the parent. We can implement this via a dict without disrupting the attr order |
| 641 | # because dicts preserve insertion order in Python 3.7+. |
| 642 | found_fields: dict[str, PydanticModelField] = {} |
| 643 | found_class_vars: dict[str, PydanticModelClassVar] = {} |
| 644 | for info in reversed(cls.info.mro[1:-1]): # 0 is the current class, -2 is BaseModel, -1 is object |
| 645 | # if BASEMODEL_METADATA_TAG_KEY in info.metadata and BASEMODEL_METADATA_KEY not in info.metadata: |
| 646 | # # We haven't processed the base class yet. Need another pass. |
| 647 | # return None, None |
| 648 | if METADATA_KEY not in info.metadata: |
| 649 | continue |
| 650 | |
| 651 | # Each class depends on the set of attributes in its dataclass ancestors. |
| 652 | self._api.add_plugin_dependency(make_wildcard_trigger(info.fullname)) |
| 653 | |
| 654 | for name, data in info.metadata[METADATA_KEY]['fields'].items(): |
| 655 | field = PydanticModelField.deserialize(info, data, self._api) |
| 656 | # (The following comment comes directly from the dataclasses plugin) |
| 657 | # TODO: We shouldn't be performing type operations during the main |
| 658 | # semantic analysis pass, since some TypeInfo attributes might |
| 659 | # still be in flux. This should be performed in a later phase. |
| 660 | field.expand_typevar_from_subtype(cls.info, self._api) |
| 661 | found_fields[name] = field |
| 662 | |
| 663 | sym_node = cls.info.names.get(name) |
| 664 | if sym_node and sym_node.node and not isinstance(sym_node.node, (Var, PlaceholderNode)): |
| 665 | self._api.fail( |
| 666 | 'BaseModel field may only be overridden by another field', |
| 667 | sym_node.node, |
| 668 | ) |
| 669 | # Collect ClassVars |
| 670 | for name, data in info.metadata[METADATA_KEY]['class_vars'].items(): |
| 671 | found_class_vars[name] = PydanticModelClassVar.deserialize(data) |
| 672 | |
| 673 | # Second, collect fields and ClassVars belonging to the current class. |
| 674 | current_field_names: set[str] = set() |
| 675 | current_class_vars_names: set[str] = set() |
| 676 | for stmt in self._get_assignment_statements_from_block(cls.defs): |
| 677 | maybe_field = self.collect_field_or_class_var_from_stmt(stmt, model_config, found_class_vars) |
| 678 | if maybe_field is None: |
| 679 | continue |
| 680 | |
| 681 | lhs = stmt.lvalues[0] |
| 682 | assert isinstance(lhs, NameExpr) # collect_field_or_class_var_from_stmt guarantees this |
| 683 | if isinstance(maybe_field, PydanticModelField): |
| 684 | if is_root_model and lhs.name != 'root': |
no test coverage detected