Example #1
0
 async def _prefetch_related_models(
     self, models: Sequence["Model"], rows: List
 ) -> Sequence["Model"]:
     self.already_extracted = {self.model.get_name(): {"raw": rows}}
     select_dict = translate_list_to_dict(self._select_related)
     prefetch_dict = translate_list_to_dict(self._prefetch_related)
     target_model = self.model
     fields = self._columns
     exclude_fields = self._exclude_columns
     orders_by = self.order_dict
     for related in prefetch_dict.keys():
         await self._extract_related_models(
             related=related,
             target_model=target_model,
             prefetch_dict=prefetch_dict.get(related, {}),
             select_dict=select_dict.get(related, {}),
             fields=fields,
             exclude_fields=exclude_fields,
             orders_by=orders_by.get(related, {}),
         )
     final_models = []
     for model in models:
         final_models.append(
             self._populate_nested_related(
                 model=model, prefetch_dict=prefetch_dict, orders_by=self.order_dict
             )
         )
     return models
Example #2
0
    def _update_excluded_with_related_not_required(
        cls,
        exclude: Union["AbstractSetIntStr", "MappingIntStrAny", None],
        nested: bool = False,
    ) -> Union[Set, Dict]:
        """
        Used during generation of the dict().
        To avoid cyclical references and max recurrence limit nested models have to
        exclude related models that are not mandatory.

        For a main model (not nested) only nullable related field names are added to
        exclusion, for nested models all related models are excluded.

        :param exclude: set/dict with fields to exclude
        :type exclude: Union[Set, Dict, None]
        :param nested: flag setting nested models (child of previous one, not main one)
        :type nested: bool
        :return: set or dict with excluded fields added.
        :rtype: Union[Set, Dict]
        """
        exclude = exclude or {}
        related_set = cls._exclude_related_names_not_required(nested=nested)
        if isinstance(exclude, set):
            exclude.union(related_set)
        else:
            related_dict = translate_list_to_dict(related_set)
            exclude = update(related_dict, exclude)
        return exclude
Example #3
0
def generate_model_example(model: Type["Model"],
                           relation_map: Dict = None) -> Dict:
    """
    Generates example to be included in schema in fastapi.

    :param model: ormar.Model
    :type model: Type["Model"]
    :param relation_map: dict with relations to follow
    :type relation_map: Optional[Dict]
    :return:
    :rtype: Dict[str, int]
    """
    example: Dict[str, Any] = dict()
    relation_map = (relation_map if relation_map is not None else
                    translate_list_to_dict(model._iterate_related_models()))
    for name, field in model.Meta.model_fields.items():
        if not field.is_relation:
            example[name] = field.__sample__
        elif isinstance(relation_map, dict) and name in relation_map:
            example[name] = get_nested_model_example(name=name,
                                                     field=field,
                                                     relation_map=relation_map)
    to_exclude = {name for name in model.Meta.model_fields}
    pydantic_repr = generate_pydantic_example(pydantic_model=model,
                                              exclude=to_exclude)
    example.update(pydantic_repr)

    return example
Example #4
0
def test_list_to_dict_translation():
    tet_list = ["aa", "bb", "cc__aa", "cc__bb", "cc__aa__xx", "cc__aa__yy"]
    test = translate_list_to_dict(tet_list)
    assert test == {
        "aa": Ellipsis,
        "bb": Ellipsis,
        "cc": {"aa": {"xx": Ellipsis, "yy": Ellipsis}, "bb": Ellipsis},
    }
Example #5
0
    async def _prefetch_related_models(self, models: Sequence["Model"],
                                       rows: List) -> Sequence["Model"]:
        """
        Main method of the query.

        Translates select nad prefetch list into dictionaries to avoid querying the
        same related models multiple times.

        Keeps the list of already extracted models.

        Extracts the related models from the database and later populate all children
        on each of the parent models from list.

        :param models: list of parent models from main query
        :type models: List[Model]
        :param rows: raw response from sql query
        :type rows: List[sqlalchemy.engine.result.RowProxy]
        :return: list of models with prefetch children populated
        :rtype: List[Model]
        """
        self.already_extracted = {self.model.get_name(): {"raw": rows}}
        select_dict = translate_list_to_dict(self._select_related)
        prefetch_dict = translate_list_to_dict(self._prefetch_related)
        target_model = self.model
        fields = self._columns
        exclude_fields = self._exclude_columns
        orders_by = self.order_dict
        for related in prefetch_dict.keys():
            await self._extract_related_models(
                related=related,
                target_model=target_model,
                prefetch_dict=prefetch_dict.get(related, {}),
                select_dict=select_dict.get(related, {}),
                fields=fields,
                exclude_fields=exclude_fields,
                orders_by=orders_by.get(related, {}),
            )
        final_models = []
        for model in models:
            final_models.append(
                self._populate_nested_related(model=model,
                                              prefetch_dict=prefetch_dict,
                                              orders_by=self.order_dict))
        return models
Example #6
0
    def dict(  # type: ignore # noqa A003
        self,
        *,
        include: Union[Set, Dict] = None,
        exclude: Union[Set, Dict] = None,
        by_alias: bool = False,
        skip_defaults: bool = None,
        exclude_unset: bool = False,
        exclude_defaults: bool = False,
        exclude_none: bool = False,
        nested: bool = False,
    ) -> "DictStrAny":  # noqa: A003'
        dict_instance = super().dict(
            include=include,
            exclude=self._update_excluded_with_related_not_required(
                exclude, nested),
            by_alias=by_alias,
            skip_defaults=skip_defaults,
            exclude_unset=exclude_unset,
            exclude_defaults=exclude_defaults,
            exclude_none=exclude_none,
        )

        if include and isinstance(include, Set):
            include = translate_list_to_dict(include)
        if exclude and isinstance(exclude, Set):
            exclude = translate_list_to_dict(exclude)

        dict_instance = self._extract_nested_models(
            nested=nested,
            dict_instance=dict_instance,
            include=include,  # type: ignore
            exclude=exclude,  # type: ignore
        )

        # include model properties as fields in dict
        if object.__getattribute__(self, "Meta").property_fields:
            props = self.get_properties(include=include, exclude=exclude)
            if props:
                dict_instance.update(
                    {prop: getattr(self, prop)
                     for prop in props})

        return dict_instance
Example #7
0
    def __init__(  # noqa: CFQ002
        self,
        model_cls: Type["Model"],
        fields: Optional[Union[Dict, Set]],
        exclude_fields: Optional[Union[Dict, Set]],
        prefetch_related: List,
        select_related: List,
        orders_by: List,
    ) -> None:

        self.model = model_cls
        self.database = self.model.Meta.database
        self._prefetch_related = prefetch_related
        self._select_related = select_related
        self._exclude_columns = exclude_fields
        self._columns = fields
        self.already_extracted: Dict = dict()
        self.models: Dict = {}
        self.select_dict = translate_list_to_dict(self._select_related)
        self.orders_by = orders_by or []
        self.order_dict = translate_list_to_dict(self.orders_by, is_order=True)
Example #8
0
 def _update_excluded_with_related_not_required(
     cls,
     exclude: Union["AbstractSetIntStr", "MappingIntStrAny", None],
     nested: bool = False,
 ) -> Union[Set, Dict]:
     exclude = exclude or {}
     related_set = cls._exclude_related_names_not_required(nested=nested)
     if isinstance(exclude, set):
         exclude.union(related_set)
     else:
         related_dict = translate_list_to_dict(related_set)
         exclude = update(related_dict, exclude)
     return exclude
Example #9
0
    def merge_two_instances(
        cls, one: "Model", other: "Model", relation_map: Dict = None
    ) -> "Model":
        """
        Merges current (other) Model and previous one (one) and returns the current
        Model instance with data merged from previous one.

        If needed it's calling itself recurrently and merges also children models.

        :param relation_map: map of models relations to follow
        :type relation_map: Dict
        :param one: previous model instance
        :type one: Model
        :param other: current model instance
        :type other: Model
        :return: current Model instance with data merged from previous one.
        :rtype: Model
        """
        relation_map = (
            relation_map
            if relation_map is not None
            else translate_list_to_dict(one._iterate_related_models())
        )
        for field_name in relation_map:
            current_field = getattr(one, field_name)
            other_value = getattr(other, field_name, [])
            if isinstance(current_field, list):
                value_to_set = cls._merge_items_lists(
                    field_name=field_name,
                    current_field=current_field,
                    other_value=other_value,
                    relation_map=relation_map,
                )
                setattr(other, field_name, value_to_set)
            elif (
                isinstance(current_field, ormar.Model)
                and current_field.pk == other_value.pk
            ):
                setattr(
                    other,
                    field_name,
                    cls.merge_two_instances(
                        current_field,
                        other_value,
                        relation_map=one._skip_ellipsis(  # type: ignore
                            relation_map, field_name, default_return=dict()
                        ),
                    ),
                )
        other.set_save_status(True)
        return other
Example #10
0
    def dict(  # type: ignore # noqa A003
        self,
        *,
        include: Union[Set, Dict] = None,
        exclude: Union[Set, Dict] = None,
        by_alias: bool = False,
        skip_defaults: bool = None,
        exclude_unset: bool = False,
        exclude_defaults: bool = False,
        exclude_none: bool = False,
        nested: bool = False,
    ) -> "DictStrAny":  # noqa: A003'
        """

        Generate a dictionary representation of the model,
        optionally specifying which fields to include or exclude.

        Nested models are also parsed to dictionaries.

        Additionally fields decorated with @property_field are also added.

        :param include: fields to include
        :type include: Union[Set, Dict, None]
        :param exclude: fields to exclude
        :type exclude: Union[Set, Dict, None]
        :param by_alias: flag to get values by alias - passed to pydantic
        :type by_alias: bool
        :param skip_defaults: flag to not set values - passed to pydantic
        :type skip_defaults: bool
        :param exclude_unset: flag to exclude not set values - passed to pydantic
        :type exclude_unset: bool
        :param exclude_defaults: flag to exclude default values - passed to pydantic
        :type exclude_defaults: bool
        :param exclude_none: flag to exclude None values - passed to pydantic
        :type exclude_none: bool
        :param nested: flag if the current model is nested
        :type nested: bool
        :return:
        :rtype:
        """
        dict_instance = super().dict(
            include=include,
            exclude=self._update_excluded_with_related_not_required(
                exclude, nested),
            by_alias=by_alias,
            skip_defaults=skip_defaults,
            exclude_unset=exclude_unset,
            exclude_defaults=exclude_defaults,
            exclude_none=exclude_none,
        )

        if include and isinstance(include, Set):
            include = translate_list_to_dict(include)
        if exclude and isinstance(exclude, Set):
            exclude = translate_list_to_dict(exclude)

        dict_instance = self._extract_nested_models(
            nested=nested,
            dict_instance=dict_instance,
            include=include,  # type: ignore
            exclude=exclude,  # type: ignore
        )

        # include model properties as fields in dict
        if object.__getattribute__(self, "Meta").property_fields:
            props = self.get_properties(include=include, exclude=exclude)
            if props:
                dict_instance.update(
                    {prop: getattr(self, prop)
                     for prop in props})

        return dict_instance
Example #11
0
    async def save_related(  # noqa: CCR001
        self,
        follow: bool = False,
        save_all: bool = False,
        relation_map: Dict = None,
        exclude: Union[Set, Dict] = None,
        update_count: int = 0,
    ) -> int:
        """
        Triggers a upsert method on all related models
        if the instances are not already saved.
        By default saves only the directly related ones.

        If follow=True is set it saves also related models of related models.

        To not get stuck in an infinite loop as related models also keep a relation
        to parent model visited models set is kept.

        That way already visited models that are nested are saved, but the save do not
        follow them inside. So Model A -> Model B -> Model A -> Model C will save second
        Model A but will never follow into Model C.
        Nested relations of those kind need to be persisted manually.

        :param exclude: items to exclude during saving of relations
        :type exclude: Union[Set, Dict]
        :param relation_map: map of relations to follow
        :type relation_map: Dict
        :param save_all: flag if all models should be saved or only not saved ones
        :type save_all: bool
        :param follow: flag to trigger deep save -
        by default only directly related models are saved
        with follow=True also related models of related models are saved
        :type follow: bool
        :param update_count: internal parameter for recursive calls -
        number of updated instances
        :type update_count: int
        :return: number of updated/saved models
        :rtype: int
        """
        relation_map = (
            relation_map
            if relation_map is not None
            else translate_list_to_dict(self._iterate_related_models())
        )
        if exclude and isinstance(exclude, Set):
            exclude = translate_list_to_dict(exclude)
        relation_map = subtract_dict(relation_map, exclude or {})

        for related in self.extract_related_names():
            if relation_map and related in relation_map:
                value = getattr(self, related)
                if value:
                    update_count = await self._update_and_follow(
                        value=value,
                        follow=follow,
                        save_all=save_all,
                        relation_map=self._skip_ellipsis(  # type: ignore
                            relation_map, related, default_return={}
                        ),
                        update_count=update_count,
                    )
        return update_count
Example #12
0
    async def save_related(  # noqa: CCR001
        self,
        follow: bool = False,
        save_all: bool = False,
        relation_map: Dict = None,
        exclude: Union[Set, Dict] = None,
        update_count: int = 0,
        previous_model: "Model" = None,
        relation_field: Optional["ForeignKeyField"] = None,
    ) -> int:
        """
        Triggers a upsert method on all related models
        if the instances are not already saved.
        By default saves only the directly related ones.

        If follow=True is set it saves also related models of related models.

        To not get stuck in an infinite loop as related models also keep a relation
        to parent model visited models set is kept.

        That way already visited models that are nested are saved, but the save do not
        follow them inside. So Model A -> Model B -> Model A -> Model C will save second
        Model A but will never follow into Model C.
        Nested relations of those kind need to be persisted manually.

        :param relation_field: field with relation leading to this model
        :type relation_field: Optional[ForeignKeyField]
        :param previous_model: previous model from which method came
        :type previous_model: Model
        :param exclude: items to exclude during saving of relations
        :type exclude: Union[Set, Dict]
        :param relation_map: map of relations to follow
        :type relation_map: Dict
        :param save_all: flag if all models should be saved or only not saved ones
        :type save_all: bool
        :param follow: flag to trigger deep save -
        by default only directly related models are saved
        with follow=True also related models of related models are saved
        :type follow: bool
        :param update_count: internal parameter for recursive calls -
        number of updated instances
        :type update_count: int
        :return: number of updated/saved models
        :rtype: int
        """
        relation_map = (relation_map if relation_map is not None else
                        translate_list_to_dict(self._iterate_related_models()))
        if exclude and isinstance(exclude, Set):
            exclude = translate_list_to_dict(exclude)
        relation_map = subtract_dict(relation_map, exclude or {})

        if relation_map:
            fields_to_visit = {
                field
                for field in self.extract_related_fields()
                if field.name in relation_map
            }
            pre_save = {
                field
                for field in fields_to_visit
                if not field.virtual and not field.is_multi
            }

            update_count = await self._update_relation_list(
                fields_list=pre_save,
                follow=follow,
                save_all=save_all,
                relation_map=relation_map,
                update_count=update_count,
            )

            update_count = await self._upsert_model(
                instance=self,
                save_all=save_all,
                previous_model=previous_model,
                relation_field=relation_field,
                update_count=update_count,
            )

            post_save = fields_to_visit - pre_save

            update_count = await self._update_relation_list(
                fields_list=post_save,
                follow=follow,
                save_all=save_all,
                relation_map=relation_map,
                update_count=update_count,
            )

        else:
            update_count = await self._upsert_model(
                instance=self,
                save_all=save_all,
                previous_model=previous_model,
                relation_field=relation_field,
                update_count=update_count,
            )

        return update_count
Example #13
0
def generate_exclude_for_ids(model: Type[ormar.Model]) -> Dict:
    to_exclude_base = translate_list_to_dict(model._iterate_related_models())
    return cast(Dict, auto_exclude_id_field(to_exclude=to_exclude_base))