Esempio n. 1
0
def test_nested_includes():
    fields = [
        "id",
        "name",
        "manufacturer__name",
        "manufacturer__hq__name",
        "manufacturer__hq__nicks__name",
    ]
    excludable = ExcludableItems()
    excludable.build(items=fields, model_cls=Car, is_exclude=False)
    compare_results_include(excludable)
Esempio n. 2
0
def test_gradual_build_from_lists():
    fields_col = [
        "year",
        ["gearbox_type", "gears"],
        "aircon_type",
        ["manufacturer__founded"],
    ]
    excludable = ExcludableItems()
    for fields in fields_col:
        excludable.build(items=fields, model_cls=Car, is_exclude=True)
    compare_results(excludable)
Esempio n. 3
0
def test_excluding_fields_from_dict_with_set():
    fields = {
        "gearbox_type":...,
        "gears":...,
        "aircon_type":...,
        "year":...,
        "manufacturer": {"founded"},
    }
    excludable = ExcludableItems()
    excludable.build(items=fields, model_cls=Car, is_exclude=True)
    compare_results(excludable)
Esempio n. 4
0
def test_excluding_fields_from_list():
    fields = [
        "gearbox_type",
        "gears",
        "aircon_type",
        "year",
        "manufacturer__founded",
    ]
    excludable = ExcludableItems()
    excludable.build(items=fields, model_cls=Car, is_exclude=True)
    compare_results(excludable)
Esempio n. 5
0
def test_includes_and_excludes_combo():
    fields_inc1 = ["id", "name", "year", "gearbox_type", "gears"]
    fields_inc2 = {"manufacturer": {"name"}}
    fields_exc1 = {"manufacturer__founded"}
    fields_exc2 = "aircon_type"
    excludable = ExcludableItems()
    excludable.build(items=fields_inc1, model_cls=Car, is_exclude=False)
    excludable.build(items=fields_inc2, model_cls=Car, is_exclude=False)
    excludable.build(items=fields_exc1, model_cls=Car, is_exclude=True)
    excludable.build(items=fields_exc2, model_cls=Car, is_exclude=True)

    car_excludable = excludable.get(Car)
    assert car_excludable.include == {
        "id", "name", "year", "gearbox_type", "gears"
    }
    assert car_excludable.exclude == {"aircon_type"}

    assert car_excludable.is_excluded("aircon_type")
    assert car_excludable.is_included("name")

    alias = Company.Meta.alias_manager.resolve_relation_alias(
        Car, "manufacturer")
    manu_excludable = excludable.get(Company, alias=alias)
    assert manu_excludable.include == {"name"}
    assert manu_excludable.exclude == {"founded"}

    assert manu_excludable.is_excluded("founded")
Esempio n. 6
0
def test_nested_includes_from_dict_with_set():
    fields = {
        "id":...,
        "name":...,
        "manufacturer": {
            "name":...,
            "hq": {
                "name":...,
                "nicks": {"name"}
            },
        },
    }
    excludable = ExcludableItems()
    excludable.build(items=fields, model_cls=Car, is_exclude=False)
    compare_results_include(excludable)
Esempio n. 7
0
    def own_table_columns(
        cls,
        model: Union[Type["Model"], Type["ModelRow"]],
        excludable: ExcludableItems,
        alias: str = "",
        use_alias: bool = False,
    ) -> List[str]:
        """
        Returns list of aliases or field names for given model.
        Aliases/names switch is use_alias flag.

        If provided only fields included in fields will be returned.
        If provided fields in exclude_fields will be excluded in return.

        Primary key field is always added and cannot be excluded (will be added anyway).

        :param alias: relation prefix
        :type alias: str
        :param excludable: structure of fields to include and exclude
        :type excludable: ExcludableItems
        :param model: model on columns are selected
        :type model: Type["Model"]
        :param use_alias: flag if aliases or field names should be used
        :type use_alias: bool
        :return: list of column field names or aliases
        :rtype: List[str]
        """
        model_excludable = excludable.get(model_cls=model,
                                          alias=alias)  # type: ignore
        columns = [
            model.get_column_name_from_alias(col.name)
            if not use_alias else col.name for col in model.Meta.table.columns
        ]
        field_names = [
            model.get_column_name_from_alias(col.name)
            for col in model.Meta.table.columns
        ]
        if model_excludable.include:
            columns = [
                col for col, name in zip(columns, field_names)
                if model_excludable.is_included(name)
            ]
        if model_excludable.exclude:
            columns = [
                col for col, name in zip(columns, field_names)
                if not model_excludable.is_excluded(name)
            ]

        # always has to return pk column for ormar to work
        columns = cls._populate_pk_column(model=model,
                                          columns=columns,
                                          use_alias=use_alias)

        return columns
Esempio n. 8
0
    def get_names_to_exclude(cls, excludable: ExcludableItems,
                             alias: str) -> Set:
        """
        Returns a set of models field names that should be explicitly excluded
        during model initialization.

        Those fields will be set to None to avoid ormar/pydantic setting default
        values on them. They should be returned as None in any case.

        Used in parsing data from database rows that construct Models by initializing
        them with dicts constructed from those db rows.

        :param alias: alias of current relation
        :type alias: str
        :param excludable: structure of fields to include and exclude
        :type excludable: ExcludableItems
        :return: set of field names that should be excluded
        :rtype: Set
        """
        model = cast(Type["Model"], cls)
        model_excludable = excludable.get(model_cls=model, alias=alias)
        fields_names = cls.extract_db_own_fields()
        if model_excludable.include:
            fields_to_keep = model_excludable.include.intersection(
                fields_names)
        else:
            fields_to_keep = fields_names

        fields_to_exclude = fields_names - fields_to_keep

        if model_excludable.exclude:
            fields_to_exclude = fields_to_exclude.union(
                model_excludable.exclude.intersection(fields_names))
        fields_to_exclude = fields_to_exclude - {cls.Meta.pkname}

        return fields_to_exclude
Esempio n. 9
0
    def populate_through_instance(
        cls,
        row: sqlalchemy.engine.ResultProxy,
        through_name: str,
        related: str,
        excludable: ExcludableItems,
    ) -> "ModelRow":
        """
        Initialize the through model from db row.
        Excluded all relation fields and other exclude/include set in excludable.

        :param row: loaded row from database
        :type row: sqlalchemy.engine.ResultProxy
        :param through_name: name of the through field
        :type through_name: str
        :param related: name of the relation
        :type related: str
        :param excludable: structure of fields to include and exclude
        :type excludable: ExcludableItems
        :return: initialized through model without relation
        :rtype: "ModelRow"
        """
        model_cls = cls.Meta.model_fields[through_name].to
        table_prefix = cls.Meta.alias_manager.resolve_relation_alias(
            from_model=cls, relation_name=related)
        # remove relations on through field
        model_excludable = excludable.get(model_cls=model_cls,
                                          alias=table_prefix)
        model_excludable.set_values(value=model_cls.extract_related_names(),
                                    is_exclude=True)
        child_dict = model_cls.extract_prefixed_table_columns(
            item={}, row=row, excludable=excludable, table_prefix=table_prefix)
        child_dict["__excluded__"] = model_cls.get_names_to_exclude(
            excludable=excludable, alias=table_prefix)
        child = model_cls(**child_dict)  # type: ignore
        return child
Esempio n. 10
0
    def from_row(  # noqa: CFQ002
        cls,
        row: sqlalchemy.engine.ResultProxy,
        source_model: Type["Model"],
        select_related: List = None,
        related_models: Any = None,
        related_field: Type["ForeignKeyField"] = None,
        excludable: ExcludableItems = None,
        current_relation_str: str = "",
        proxy_source_model: Optional[Type["Model"]] = None,
        used_prefixes: List[str] = None,
    ) -> Optional["Model"]:
        """
        Model method to convert raw sql row from database into ormar.Model instance.
        Traverses nested models if they were specified in select_related for query.

        Called recurrently and returns model instance if it's present in the row.
        Note that it's processing one row at a time, so if there are duplicates of
        parent row that needs to be joined/combined
        (like parent row in sql join with 2+ child rows)
        instances populated in this method are later combined in the QuerySet.
        Other method working directly on raw database results is in prefetch_query,
        where rows are populated in a different way as they do not have
        nested models in result.

        :param used_prefixes: list of already extracted prefixes
        :type used_prefixes: List[str]
        :param proxy_source_model: source model from which querysetproxy is constructed
        :type proxy_source_model: Optional[Type["ModelRow"]]
        :param excludable: structure of fields to include and exclude
        :type excludable: ExcludableItems
        :param current_relation_str: name of the relation field
        :type current_relation_str: str
        :param source_model: model on which relation was defined
        :type source_model: Type[Model]
        :param row: raw result row from the database
        :type row: sqlalchemy.engine.result.ResultProxy
        :param select_related: list of names of related models fetched from database
        :type select_related: List
        :param related_models: list or dict of related models
        :type related_models: Union[List, Dict]
        :param related_field: field with relation declaration
        :type related_field: Type[ForeignKeyField]
        :return: returns model if model is populated from database
        :rtype: Optional[Model]
        """
        item: Dict[str, Any] = {}
        select_related = select_related or []
        related_models = related_models or []
        table_prefix = ""
        used_prefixes = used_prefixes if used_prefixes is not None else []
        excludable = excludable or ExcludableItems()

        if select_related:
            related_models = group_related_list(select_related)

        if related_field:
            if related_field.is_multi:
                previous_model = related_field.through
            else:
                previous_model = related_field.owner
            table_prefix = cls.Meta.alias_manager.resolve_relation_alias(
                from_model=previous_model, relation_name=related_field.name)
            if not table_prefix or table_prefix in used_prefixes:
                manager = cls.Meta.alias_manager
                table_prefix = manager.resolve_relation_alias_after_complex(
                    source_model=source_model,
                    relation_str=current_relation_str,
                    relation_field=related_field,
                )
            used_prefixes.append(table_prefix)

        item = cls._populate_nested_models_from_row(
            item=item,
            row=row,
            related_models=related_models,
            excludable=excludable,
            current_relation_str=current_relation_str,
            source_model=source_model,  # type: ignore
            proxy_source_model=proxy_source_model,  # type: ignore
            table_prefix=table_prefix,
            used_prefixes=used_prefixes,
        )
        item = cls.extract_prefixed_table_columns(item=item,
                                                  row=row,
                                                  table_prefix=table_prefix,
                                                  excludable=excludable)

        instance: Optional["Model"] = None
        if item.get(cls.Meta.pkname, None) is not None:
            item["__excluded__"] = cls.get_names_to_exclude(
                excludable=excludable, alias=table_prefix)
            instance = cast("Model", cls(**item))
            instance.set_save_status(True)
        return instance
Esempio n. 11
0
    def _populate_nested_models_from_row(  # noqa: CFQ002
        cls,
        item: dict,
        row: sqlalchemy.engine.ResultProxy,
        source_model: Type["Model"],
        related_models: Any,
        excludable: ExcludableItems,
        table_prefix: str,
        used_prefixes: List[str],
        current_relation_str: str = None,
        proxy_source_model: Type["Model"] = None,
    ) -> dict:
        """
        Traverses structure of related models and populates the nested models
        from the database row.
        Related models can be a list if only directly related models are to be
        populated, converted to dict if related models also have their own related
        models to be populated.

        Recurrently calls from_row method on nested instances and create nested
        instances. In the end those instances are added to the final model dictionary.

        :param proxy_source_model: source model from which querysetproxy is constructed
        :type proxy_source_model: Optional[Type["ModelRow"]]
        :param excludable: structure of fields to include and exclude
        :type excludable: ExcludableItems
        :param source_model: source model from which relation started
        :type source_model: Type[Model]
        :param current_relation_str: joined related parts into one string
        :type current_relation_str: str
        :param item: dictionary of already populated nested models, otherwise empty dict
        :type item: Dict
        :param row: raw result row from the database
        :type row: sqlalchemy.engine.result.ResultProxy
        :param related_models: list or dict of related models
        :type related_models: Union[Dict, List]
        :return: dictionary with keys corresponding to model fields names
        and values are database values
        :rtype: Dict
        """

        for related in related_models:
            field = cls.Meta.model_fields[related]
            field = cast(Type["ForeignKeyField"], field)
            model_cls = field.to
            model_excludable = excludable.get(model_cls=cast(
                Type["Model"], cls),
                                              alias=table_prefix)
            if model_excludable.is_excluded(related):
                return item

            relation_str = ("__".join([current_relation_str, related])
                            if current_relation_str else related)
            remainder = None
            if isinstance(related_models, dict) and related_models[related]:
                remainder = related_models[related]
            child = model_cls.from_row(
                row,
                related_models=remainder,
                related_field=field,
                excludable=excludable,
                current_relation_str=relation_str,
                source_model=source_model,
                proxy_source_model=proxy_source_model,
                used_prefixes=used_prefixes,
            )
            item[model_cls.get_column_name_from_alias(related)] = child
            if field.is_multi and child:
                through_name = cls.Meta.model_fields[related].through.get_name(
                )
                through_child = cls.populate_through_instance(
                    row=row,
                    related=related,
                    through_name=through_name,
                    excludable=excludable,
                )

                if child.__class__ != proxy_source_model:
                    setattr(child, through_name, through_child)
                else:
                    item[through_name] = through_child
                child.set_save_status(True)

        return item