Exemplo n.º 1
0
Arquivo: query.py Projeto: dudil/ormar
    def build_select_expression(self) -> Tuple[sqlalchemy.sql.select, List[str]]:
        """
        Main entry point from outside (after proper initialization).

        Extracts columns list to fetch,
        construct all required joins for select related,
        then applies all conditional and sort clauses.

        Returns ready to run query with all joins and clauses.

        :return: ready to run query with all joins and clauses.
        :rtype: sqlalchemy.sql.selectable.Select
        """
        self_related_fields = self.model_cls.own_table_columns(
            model=self.model_cls, excludable=self.excludable, use_alias=True,
        )
        self.columns = self.model_cls.Meta.alias_manager.prefixed_columns(
            "", self.table, self_related_fields
        )
        self.apply_order_bys_for_primary_model()
        if self._pagination_query_required():
            self.select_from = self._build_pagination_subquery()
        else:
            self.select_from = self.table

        related_models = group_related_list(self._select_related)

        for related in related_models:
            remainder = None
            if isinstance(related_models, dict) and related_models[related]:
                remainder = related_models[related]
            sql_join = SqlJoin(
                used_aliases=self.used_aliases,
                select_from=self.select_from,
                columns=self.columns,
                excludable=self.excludable,
                order_columns=self.order_columns,
                sorted_orders=self.sorted_orders,
                main_model=self.model_cls,
                relation_name=related,
                relation_str=related,
                related_models=remainder,
            )

            (
                self.used_aliases,
                self.select_from,
                self.columns,
                self.sorted_orders,
            ) = sql_join.build_join()

        expr = sqlalchemy.sql.select(self.columns)
        expr = expr.select_from(self.select_from)

        expr = self._apply_expression_modifiers(expr)

        # print("\n", expr.compile(compile_kwargs={"literal_binds": True}))
        self._reset_query_parameters()

        return expr
Exemplo n.º 2
0
def test_group_related_list():
    given = [
        "friends__least_favourite_game",
        "least_favourite_game",
        "friends",
        "favourite_game",
        "friends__favourite_game",
    ]
    expected = {
        "least_favourite_game": [],
        "favourite_game": [],
        "friends": ["favourite_game", "least_favourite_game"],
    }
    assert group_related_list(given) == expected
Exemplo n.º 3
0
    def from_row(  # noqa CCR001
        cls: Type[T],
        row: sqlalchemy.engine.ResultProxy,
        select_related: List = None,
        related_models: Any = None,
        previous_model: Type[T] = None,
        source_model: Type[T] = None,
        related_name: str = None,
        fields: Optional[Union[Dict, Set]] = None,
        exclude_fields: Optional[Union[Dict, Set]] = None,
        current_relation_str: str = None,
    ) -> Optional[T]:
        """
        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 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 previous_model: internal param for nested models to specify table_prefix
        :type previous_model: Model class
        :param related_name: internal parameter - name of current nested model
        :type related_name: str
        :param fields: fields and related model fields to include
        if provided only those are included
        :type fields: Optional[Union[Dict, Set]]
        :param exclude_fields: fields and related model fields to exclude
        excludes the fields even if they are provided in fields
        :type exclude_fields: Optional[Union[Dict, Set]]
        :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 = ""

        if select_related:
            source_model = cls
            related_models = group_related_list(select_related)

        rel_name2 = related_name

        if (previous_model and related_name
                and issubclass(previous_model.Meta.model_fields[related_name],
                               ManyToManyField)):
            through_field = previous_model.Meta.model_fields[related_name]
            if (through_field.self_reference
                    and related_name == through_field.self_reference_primary):
                rel_name2 = through_field.default_source_field_name(
                )  # type: ignore
            else:
                rel_name2 = through_field.default_target_field_name(
                )  # type: ignore
            previous_model = through_field.through  # type: ignore

        if previous_model and rel_name2:
            if current_relation_str and "__" in current_relation_str and source_model:
                table_prefix = cls.Meta.alias_manager.resolve_relation_alias(
                    from_model=source_model,
                    relation_name=current_relation_str)
            if not table_prefix:
                table_prefix = cls.Meta.alias_manager.resolve_relation_alias(
                    from_model=previous_model, relation_name=rel_name2)

        item = cls.populate_nested_models_from_row(
            item=item,
            row=row,
            related_models=related_models,
            fields=fields,
            exclude_fields=exclude_fields,
            current_relation_str=current_relation_str,
            source_model=source_model,
        )
        item = cls.extract_prefixed_table_columns(
            item=item,
            row=row,
            table_prefix=table_prefix,
            fields=fields,
            exclude_fields=exclude_fields,
        )

        instance: Optional[T] = None
        if item.get(cls.Meta.pkname, None) is not None:
            item["__excluded__"] = cls.get_names_to_exclude(
                fields=fields, exclude_fields=exclude_fields)
            instance = cls(**item)
            instance.set_save_status(True)
        return instance
Exemplo n.º 4
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