From c4ff69b6830c08cd567fb1489c70aab84db994c3 Mon Sep 17 00:00:00 2001 From: collerek Date: Thu, 31 Dec 2020 11:52:05 +0100 Subject: [PATCH] fill docstrings on mixins --- ormar/models/mixins/alias_mixin.py | 4 + ormar/models/mixins/excludable_mixin.py | 119 ++++++++++++++++++++++++ ormar/models/mixins/merge_mixin.py | 33 +++++++ ormar/models/mixins/prefetch_mixin.py | 58 ++++++++++++ ormar/models/mixins/relation_mixin.py | 39 ++++++++ ormar/models/mixins/save_mixin.py | 23 +++++ 6 files changed, 276 insertions(+) diff --git a/ormar/models/mixins/alias_mixin.py b/ormar/models/mixins/alias_mixin.py index 6dedb95..2108554 100644 --- a/ormar/models/mixins/alias_mixin.py +++ b/ormar/models/mixins/alias_mixin.py @@ -2,6 +2,10 @@ from typing import Dict, TYPE_CHECKING class AliasMixin: + """ + Used to translate field names into database column names. + """ + if TYPE_CHECKING: # pragma: no cover from ormar import ModelMeta diff --git a/ormar/models/mixins/excludable_mixin.py b/ormar/models/mixins/excludable_mixin.py index 5dbd260..961e284 100644 --- a/ormar/models/mixins/excludable_mixin.py +++ b/ormar/models/mixins/excludable_mixin.py @@ -25,6 +25,10 @@ if TYPE_CHECKING: # pragma no cover class ExcludableMixin(RelationMixin): + """ + Used to include/exclude given set of fields on models during load and dict() calls. + """ + if TYPE_CHECKING: # pragma: no cover from ormar import Model @@ -32,6 +36,16 @@ class ExcludableMixin(RelationMixin): def get_child( items: Union[Set, Dict, None], key: str = None ) -> Union[Set, Dict, None]: + """ + Used to get nested dictionaries keys if they exists otherwise returns + passed items. + :param items: bag of items to include or exclude + :type items: Union[Set, Dict, None] + :param key: name of the child to extract + :type key: str + :return: child extracted from items if exists + :rtype: Union[Set, Dict, None] + """ if isinstance(items, dict): return items.get(key, {}) return items @@ -40,16 +54,46 @@ class ExcludableMixin(RelationMixin): def get_excluded( exclude: Union[Set, Dict, None], key: str = None ) -> Union[Set, Dict, None]: + """ + Proxy to ExcludableMixin.get_child for exclusions. + + :param exclude: bag of items to exclude + :type exclude: Union[Set, Dict, None] + :param key: name of the child to extract + :type key: str + :return: child extracted from items if exists + :rtype: Union[Set, Dict, None] + """ return ExcludableMixin.get_child(items=exclude, key=key) @staticmethod def get_included( include: Union[Set, Dict, None], key: str = None ) -> Union[Set, Dict, None]: + """ + Proxy to ExcludableMixin.get_child for inclusions. + + :param include: bag of items to include + :type include: Union[Set, Dict, None] + :param key: name of the child to extract + :type key: str + :return: child extracted from items if exists + :rtype: Union[Set, Dict, None] + """ return ExcludableMixin.get_child(items=include, key=key) @staticmethod def is_excluded(exclude: Union[Set, Dict, None], key: str = None) -> bool: + """ + Checks if given key should be excluded on model/ dict. + + :param exclude: bag of items to exclude + :type exclude: Union[Set, Dict, None] + :param key: name of the child to extract + :type key: str + :return: child extracted from items if exists + :rtype: Union[Set, Dict, None] + """ if exclude is None: return False if exclude is Ellipsis: # pragma: nocover @@ -63,6 +107,16 @@ class ExcludableMixin(RelationMixin): @staticmethod def is_included(include: Union[Set, Dict, None], key: str = None) -> bool: + """ + Checks if given key should be included on model/ dict. + + :param include: bag of items to include + :type include: Union[Set, Dict, None] + :param key: name of the child to extract + :type key: str + :return: child extracted from items if exists + :rtype: Union[Set, Dict, None] + """ if include is None: return True if include is Ellipsis: @@ -78,6 +132,19 @@ class ExcludableMixin(RelationMixin): def _populate_pk_column( model: Type["Model"], columns: List[str], use_alias: bool = False, ) -> List[str]: + """ + Adds primary key column/alias (depends on use_alias flag) to list of + column names that are selected. + + :param model: model on columns are selected + :type model: Type["Model"] + :param columns: list of columns names + :type columns: List[str] + :param use_alias: flag to set if aliases or field names should be used + :type use_alias: bool + :return: list of columns names with pk column in it + :rtype: List[str] + """ pk_alias = ( model.get_column_alias(model.Meta.pkname) if use_alias @@ -95,6 +162,26 @@ class ExcludableMixin(RelationMixin): exclude_fields: Optional[Union[Set, Dict]], 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 model: model on columns are selected + :type model: Type["Model"] + :param fields: set/dict of fields to include + :type fields: Optional[Union[Set, Dict]] + :param exclude_fields: set/dict of fields to exclude + :type exclude_fields: Optional[Union[Set, Dict]] + :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] + """ columns = [ model.get_column_name_from_alias(col.name) if not use_alias else col.name for col in model.Meta.table.columns @@ -129,6 +216,21 @@ class ExcludableMixin(RelationMixin): 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): @@ -144,6 +246,23 @@ class ExcludableMixin(RelationMixin): fields: Optional[Union[Dict, Set]] = None, exclude_fields: Optional[Union[Dict, Set]] = None, ) -> 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 fields: set/dict of fields to include + :type fields: Optional[Union[Set, Dict]] + :param exclude_fields: set/dict of fields to exclude + :type exclude_fields: Optional[Union[Set, Dict]] + :return: set of field names that should be excluded + :rtype: Set + """ fields_names = cls.extract_db_own_fields() if fields and fields is not Ellipsis: fields_to_keep = {name for name in fields if name in fields_names} diff --git a/ormar/models/mixins/merge_mixin.py b/ormar/models/mixins/merge_mixin.py index e01464a..0ad471d 100644 --- a/ormar/models/mixins/merge_mixin.py +++ b/ormar/models/mixins/merge_mixin.py @@ -8,8 +8,28 @@ if TYPE_CHECKING: # pragma no cover class MergeModelMixin: + """ + Used to merge models instances returned by database, + but already initialized to ormar Models.keys + + Models can duplicate during joins when parent model has multiple child rows, + in the end all parent (main) models should be unique. + """ + @classmethod def merge_instances_list(cls, result_rows: Sequence["Model"]) -> Sequence["Model"]: + """ + Merges a list of models into list of unique models. + + Models can duplicate during joins when parent model has multiple child rows, + in the end all parent (main) models should be unique. + + :param result_rows: list of already initialized Models with child models + populated, each instance is one row in db and some models can duplicate + :type result_rows: List["Model"] + :return: list of merged models where each main model is unique + :rtype: List["Model"] + """ merged_rows: List["Model"] = [] grouped_instances: OrderedDict = OrderedDict() @@ -27,6 +47,19 @@ class MergeModelMixin: @classmethod def merge_two_instances(cls, one: "Model", other: "Model") -> "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 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 + """ for field in one.Meta.model_fields.keys(): current_field = getattr(one, field) if isinstance(current_field, list) and not isinstance( diff --git a/ormar/models/mixins/prefetch_mixin.py b/ormar/models/mixins/prefetch_mixin.py index eec200f..04a11c8 100644 --- a/ormar/models/mixins/prefetch_mixin.py +++ b/ormar/models/mixins/prefetch_mixin.py @@ -6,6 +6,10 @@ from ormar.models.mixins.relation_mixin import RelationMixin class PrefetchQueryMixin(RelationMixin): + """ + Used in PrefetchQuery to extract ids and names of models to prefetch. + """ + if TYPE_CHECKING: # pragma no cover from ormar import Model @@ -18,6 +22,20 @@ class PrefetchQueryMixin(RelationMixin): reverse: bool, related: str, ) -> Tuple[Type["Model"], str]: + """ + Returns Model on which query clause should be performed and name of the column. + + :param parent_model: related model that the relation lead to + :type parent_model: Type[Model] + :param target_model: model on which query should be perfomed + :type target_model: Type[Model] + :param reverse: flag if the relation is reverse + :type reverse: bool + :param related: name of the relation field + :type related: str + :return: Model on which query clause should be performed and name of the column + :rtype: Tuple[Type[Model], str] + """ if reverse: field_name = ( parent_model.Meta.model_fields[related].related_name @@ -36,6 +54,22 @@ class PrefetchQueryMixin(RelationMixin): def get_column_name_for_id_extraction( parent_model: Type["Model"], reverse: bool, related: str, use_raw: bool, ) -> str: + """ + Returns name of the column that should be used to extract ids from model. + Depending on the relation side it's either primary key column of parent model + or field name specified by related parameter. + + :param parent_model: model from which id column should be extracted + :type parent_model: Type[Model] + :param reverse: flag if the relation is reverse + :type reverse: bool + :param related: name of the relation field + :type related: str + :param use_raw: flag if aliases or field names should be used + :type use_raw: bool + :return: + :rtype: + """ if reverse: column_name = parent_model.Meta.pkname return ( @@ -46,6 +80,16 @@ class PrefetchQueryMixin(RelationMixin): @classmethod def get_related_field_name(cls, target_field: Type["BaseField"]) -> str: + """ + Returns name of the relation field that should be used in prefetch query. + This field is later used to register relation in prefetch query, + populate relations dict, and populate nested model in prefetch query. + + :param target_field: relation field that should be used in prefetch + :type target_field: Type[BaseField] + :return: name of the field + :rtype: str + """ if issubclass(target_field, ormar.fields.ManyToManyField): return cls.get_name() if target_field.virtual: @@ -54,6 +98,20 @@ class PrefetchQueryMixin(RelationMixin): @classmethod def get_filtered_names_to_extract(cls, prefetch_dict: Dict) -> List: + """ + Returns list of related fields names that should be followed to prefetch related + models from. + + List of models is translated into dict to assure each model is extracted only + once in one query, that's why this function accepts prefetch_dict not list. + + Only relations from current model are returned. + + :param prefetch_dict: dictionary of fields to extract + :type prefetch_dict: Dict + :return: list of fields names to extract + :rtype: List + """ related_to_extract = [] if prefetch_dict and prefetch_dict is not Ellipsis: related_to_extract = [ diff --git a/ormar/models/mixins/relation_mixin.py b/ormar/models/mixins/relation_mixin.py index c12b486..48688d6 100644 --- a/ormar/models/mixins/relation_mixin.py +++ b/ormar/models/mixins/relation_mixin.py @@ -5,6 +5,10 @@ from ormar.fields.foreign_key import ForeignKeyField class RelationMixin: + """ + Used to return relation fields/names etc. from given model + """ + if TYPE_CHECKING: # pragma no cover from ormar import ModelMeta @@ -14,6 +18,12 @@ class RelationMixin: @classmethod def extract_db_own_fields(cls) -> Set: + """ + Returns only fields that are stored in the own database table, exclude all + related fields. + :return: set of model fields with relation fields excluded + :rtype: Set + """ related_names = cls.extract_related_names() self_fields = { name for name in cls.Meta.model_fields.keys() if name not in related_names @@ -22,7 +32,13 @@ class RelationMixin: @classmethod def extract_related_fields(cls) -> List: + """ + Returns List of ormar Fields for all relations declared on a model. + List is cached in cls._related_fields for quicker access. + :return: list of related fields + :rtype: List + """ if isinstance(cls._related_fields, List): return cls._related_fields @@ -35,7 +51,13 @@ class RelationMixin: @classmethod def extract_related_names(cls) -> Set: + """ + Returns List of fields names for all relations declared on a model. + List is cached in cls._related_names for quicker access. + :return: list of related fields names + :rtype: List + """ if isinstance(cls._related_names, Set): return cls._related_names @@ -49,6 +71,12 @@ class RelationMixin: @classmethod def _extract_db_related_names(cls) -> Set: + """ + Returns only fields that are stored in the own database table, exclude + related fields that are not stored as foreign keys on given model. + :return: set of model fields with non fk relation fields excluded + :rtype: Set + """ related_names = cls.extract_related_names() related_names = { name @@ -59,6 +87,17 @@ class RelationMixin: @classmethod def _exclude_related_names_not_required(cls, nested: bool = False) -> Set: + """ + Returns a set of non mandatory related models field names. + + For a main model (not nested) only nullable related field names are returned, + for nested models all related models are returned. + + :param nested: flag setting nested models (child of previous one, not main one) + :type nested: bool + :return: set of non mandatory related fields + :rtype: Set + """ if nested: return cls.extract_related_names() related_names = cls.extract_related_names() diff --git a/ormar/models/mixins/save_mixin.py b/ormar/models/mixins/save_mixin.py index 985b20a..2287b86 100644 --- a/ormar/models/mixins/save_mixin.py +++ b/ormar/models/mixins/save_mixin.py @@ -6,8 +6,21 @@ from ormar.models.mixins.relation_mixin import RelationMixin class SavePrepareMixin(RelationMixin): + """ + Used to prepare models to be saved in database + """ + @classmethod def substitute_models_with_pks(cls, model_dict: Dict) -> Dict: # noqa CCR001 + """ + Receives dictionary of model that is about to be saved and changes all related + models that are stored as foreign keys to their fk value. + + :param model_dict: dictionary of model that is about to be saved + :type model_dict: Dict + :return: dictionary of model that is about to be saved + :rtype: Dict + """ for field in cls.extract_related_names(): field_value = model_dict.get(field, None) if field_value is not None: @@ -34,6 +47,16 @@ class SavePrepareMixin(RelationMixin): @classmethod def populate_default_values(cls, new_kwargs: Dict) -> Dict: + """ + Receives dictionary of model that is about to be saved and populates the default + value on the fields that have the default value set, but no actual value was + passed by the user. + + :param new_kwargs: dictionary of model that is about to be saved + :type new_kwargs: Dict + :return: dictionary of model that is about to be saved + :rtype: Dict + """ for field_name, field in cls.Meta.model_fields.items(): if ( field_name not in new_kwargs