Пример #1
0
    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
Пример #2
0
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