def __new__( # type: ignore # noqa: CCR001 mcs: "ModelMetaclass", name: str, bases: Any, attrs: dict ) -> "ModelMetaclass": """ Metaclass used by ormar Models that performs configuration and build of ormar Models. Sets pydantic configuration. Extract model_fields and convert them to pydantic FieldInfo, updates class namespace. Extracts settings and fields from parent classes. Fetches methods decorated with @property_field decorator to expose them later in dict(). Construct parent pydantic Metaclass/ Model. If class has Meta class declared (so actual ormar Models) it also: * populate sqlalchemy columns, pkname and tables from model_fields * register reverse relationships on related models * registers all relations in alias manager that populates table_prefixes * exposes alias manager on each Model * creates QuerySet for each model and exposes it on a class :param name: name of current class :type name: str :param bases: base classes :type bases: Tuple :param attrs: class namespace :type attrs: Dict """ attrs["Config"] = get_pydantic_base_orm_config() attrs["__name__"] = name attrs, model_fields = extract_annotations_and_default_vals(attrs) for base in reversed(bases): mod = base.__module__ if mod.startswith("ormar.models.") or mod.startswith("pydantic."): continue attrs, model_fields = extract_from_parents_definition( base_class=base, curr_class=mcs, attrs=attrs, model_fields=model_fields ) new_model = super().__new__( # type: ignore mcs, name, bases, attrs ) add_cached_properties(new_model) if hasattr(new_model, "Meta"): populate_default_options_values(new_model, model_fields) check_required_meta_parameters(new_model) add_property_fields(new_model, attrs) register_signals(new_model=new_model) populate_choices_validators(new_model) if not new_model.Meta.abstract: new_model = populate_meta_tablename_columns_and_pk(name, new_model) populate_meta_sqlalchemy_table_if_required(new_model.Meta) expand_reverse_relationships(new_model) # TODO: iterate only related fields for field in new_model.Meta.model_fields.values(): register_relation_in_alias_manager(field=field) if new_model.Meta.pkname not in attrs["__annotations__"]: field_name = new_model.Meta.pkname attrs["__annotations__"][field_name] = Optional[int] # type: ignore attrs[field_name] = None new_model.__fields__[field_name] = get_pydantic_field( field_name=field_name, model=new_model ) new_model.Meta.alias_manager = alias_manager return new_model
def extract_from_parents_definition( # noqa: CCR001 base_class: type, curr_class: type, attrs: Dict, model_fields: Dict[str, Union[BaseField, ForeignKeyField, ManyToManyField]], ) -> Tuple[Dict, Dict]: """ Extracts fields from base classes if they have valid oramr fields. If model was already parsed -> fields definitions need to be removed from class cause pydantic complains about field re-definition so after first child we need to extract from __parsed_fields__ not the class itself. If the class is parsed first time annotations and field definition is parsed from the class.__dict__. If the class is a ormar.Model it is skipped. :param base_class: one of the parent classes :type base_class: Model or model parent class :param curr_class: current constructed class :type curr_class: Model or model parent class :param attrs: new namespace for class being constructed :type attrs: Dict :param model_fields: ormar fields in defined in current class :type model_fields: Dict[str, BaseField] :return: updated attrs and model_fields :rtype: Tuple[Dict, Dict] """ if hasattr(base_class, "Meta"): base_class = cast(Type["Model"], base_class) return copy_data_from_parent_model( base_class=base_class, curr_class=curr_class, attrs=attrs, model_fields=model_fields, ) key = "__annotations__" if hasattr(base_class, PARSED_FIELDS_KEY): # model was already parsed -> fields definitions need to be removed from class # cause pydantic complains about field re-definition so after first child # we need to extract from __parsed_fields__ not the class itself new_attrs, new_model_fields = getattr(base_class, PARSED_FIELDS_KEY) new_fields = set(new_model_fields.keys()) model_fields = update_attrs_and_fields( attrs=attrs, new_attrs=new_attrs, model_fields=model_fields, new_model_fields=new_model_fields, new_fields=new_fields, ) return attrs, model_fields potential_fields = get_potential_fields(base_class.__dict__) if potential_fields: # parent model has ormar fields defined and was not parsed before new_attrs = {key: {k: v for k, v in base_class.__dict__.get(key, {}).items()}} new_attrs.update(potential_fields) new_fields = set(potential_fields.keys()) for name in new_fields: delattr(base_class, name) new_attrs, new_model_fields = extract_annotations_and_default_vals(new_attrs) setattr(base_class, PARSED_FIELDS_KEY, (new_attrs, new_model_fields)) model_fields = update_attrs_and_fields( attrs=attrs, new_attrs=new_attrs, model_fields=model_fields, new_model_fields=new_model_fields, new_fields=new_fields, ) return attrs, model_fields