Skip to content

mixins

Package contains functionalities divided by features. All mixins are combined into ModelTableProxy which is one of the parents of Model. The split into mixins was done to ease the maintainability of the proxy class, as it became quite complicated over time.

AliasMixin

Used to translate field names into database column names.

Source code in ormar/models/mixins/alias_mixin.py
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
class AliasMixin:
    """
    Used to translate field names into database column names.
    """

    if TYPE_CHECKING:  # pragma: no cover
        from ormar import ModelMeta

        Meta: ModelMeta

    @classmethod
    def get_column_alias(cls, field_name: str) -> str:
        """
        Returns db alias (column name in db) for given ormar field.
        For fields without alias field name is returned.
        :param field_name: name of the field to get alias from
        :type field_name: str
        :return: alias (db name) if set, otherwise passed name
        :rtype: str
        """
        field = cls.Meta.model_fields.get(field_name)
        return field.get_alias() if field is not None else field_name

    @classmethod
    def get_column_name_from_alias(cls, alias: str) -> str:
        """
        Returns ormar field name for given db alias (column name in db).
        If field do not have alias it's returned as is.
        :param alias:
        :type alias: str
        :return: field name if set, otherwise passed alias (db name)
        :rtype: str
        """
        for field_name, field in cls.Meta.model_fields.items():
            if field.get_alias() == alias:
                return field_name
        return alias  # if not found it's not an alias but actual name

    @classmethod
    def translate_columns_to_aliases(cls, new_kwargs: Dict) -> Dict:
        """
        Translates dictionary of model fields changing field names into aliases.
        If field has no alias the field name remains intact.
        Only fields present in the dictionary are translated.
        :param new_kwargs: dict with fields names and their values
        :type new_kwargs: Dict
        :return: dict with aliases and their values
        :rtype: Dict
        """
        for field_name, field in cls.Meta.model_fields.items():
            if field_name in new_kwargs:
                new_kwargs[field.get_alias()] = new_kwargs.pop(field_name)
        return new_kwargs

    @classmethod
    def translate_aliases_to_columns(cls, new_kwargs: Dict) -> Dict:
        """
        Translates dictionary of model fields changing aliases into field names.
        If field has no alias the alias is already a field name.
        Only fields present in the dictionary are translated.
        :param new_kwargs: dict with aliases and their values
        :type new_kwargs: Dict
        :return: dict with fields names and their values
        :rtype: Dict
        """
        for field_name, field in cls.Meta.model_fields.items():
            if field.get_alias() and field.get_alias() in new_kwargs:
                new_kwargs[field_name] = new_kwargs.pop(field.get_alias())
        return new_kwargs

get_column_alias(field_name) classmethod

Returns db alias (column name in db) for given ormar field. For fields without alias field name is returned.

Parameters:

Name Type Description Default
field_name str

name of the field to get alias from

required

Returns:

Type Description
str

alias (db name) if set, otherwise passed name

Source code in ormar/models/mixins/alias_mixin.py
14
15
16
17
18
19
20
21
22
23
24
25
@classmethod
def get_column_alias(cls, field_name: str) -> str:
    """
    Returns db alias (column name in db) for given ormar field.
    For fields without alias field name is returned.
    :param field_name: name of the field to get alias from
    :type field_name: str
    :return: alias (db name) if set, otherwise passed name
    :rtype: str
    """
    field = cls.Meta.model_fields.get(field_name)
    return field.get_alias() if field is not None else field_name

get_column_name_from_alias(alias) classmethod

Returns ormar field name for given db alias (column name in db). If field do not have alias it's returned as is.

Parameters:

Name Type Description Default
alias str required

Returns:

Type Description
str

field name if set, otherwise passed alias (db name)

Source code in ormar/models/mixins/alias_mixin.py
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@classmethod
def get_column_name_from_alias(cls, alias: str) -> str:
    """
    Returns ormar field name for given db alias (column name in db).
    If field do not have alias it's returned as is.
    :param alias:
    :type alias: str
    :return: field name if set, otherwise passed alias (db name)
    :rtype: str
    """
    for field_name, field in cls.Meta.model_fields.items():
        if field.get_alias() == alias:
            return field_name
    return alias  # if not found it's not an alias but actual name

translate_aliases_to_columns(new_kwargs) classmethod

Translates dictionary of model fields changing aliases into field names. If field has no alias the alias is already a field name. Only fields present in the dictionary are translated.

Parameters:

Name Type Description Default
new_kwargs Dict

dict with aliases and their values

required

Returns:

Type Description
Dict

dict with fields names and their values

Source code in ormar/models/mixins/alias_mixin.py
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
@classmethod
def translate_aliases_to_columns(cls, new_kwargs: Dict) -> Dict:
    """
    Translates dictionary of model fields changing aliases into field names.
    If field has no alias the alias is already a field name.
    Only fields present in the dictionary are translated.
    :param new_kwargs: dict with aliases and their values
    :type new_kwargs: Dict
    :return: dict with fields names and their values
    :rtype: Dict
    """
    for field_name, field in cls.Meta.model_fields.items():
        if field.get_alias() and field.get_alias() in new_kwargs:
            new_kwargs[field_name] = new_kwargs.pop(field.get_alias())
    return new_kwargs

translate_columns_to_aliases(new_kwargs) classmethod

Translates dictionary of model fields changing field names into aliases. If field has no alias the field name remains intact. Only fields present in the dictionary are translated.

Parameters:

Name Type Description Default
new_kwargs Dict

dict with fields names and their values

required

Returns:

Type Description
Dict

dict with aliases and their values

Source code in ormar/models/mixins/alias_mixin.py
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
@classmethod
def translate_columns_to_aliases(cls, new_kwargs: Dict) -> Dict:
    """
    Translates dictionary of model fields changing field names into aliases.
    If field has no alias the field name remains intact.
    Only fields present in the dictionary are translated.
    :param new_kwargs: dict with fields names and their values
    :type new_kwargs: Dict
    :return: dict with aliases and their values
    :rtype: Dict
    """
    for field_name, field in cls.Meta.model_fields.items():
        if field_name in new_kwargs:
            new_kwargs[field.get_alias()] = new_kwargs.pop(field_name)
    return new_kwargs

ExcludableMixin

Bases: RelationMixin

Used to include/exclude given set of fields on models during load and dict() calls.

Source code in ormar/models/mixins/excludable_mixin.py
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
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
        from ormar.models import ModelRow

    @staticmethod
    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

    @staticmethod
    def _populate_pk_column(
        model: Union[Type["Model"], Type["ModelRow"]],
        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
            else model.Meta.pkname
        )
        if pk_alias not in columns:
            columns.append(pk_alias)
        return columns

    @classmethod
    def own_table_columns(
        cls,
        model: Union[Type["Model"], Type["ModelRow"]],
        excludable: ExcludableItems,
        alias: str = "",
        use_alias: bool = False,
        add_pk_columns: bool = True,
    ) -> 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 add_pk_columns: flag if add primary key - always yes if ormar parses data
        :type add_pk_columns: bool
        :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
        if add_pk_columns:
            columns = cls._populate_pk_column(
                model=model, columns=columns, use_alias=use_alias
            )

        return columns

    @classmethod
    def _update_excluded_with_related(cls, exclude: Union[Set, Dict, None]) -> Set:
        """
        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]
        :return: set or dict with excluded fields added.
        :rtype: Union[Set, Dict]
        """
        exclude = exclude or set()
        related_set = cls.extract_related_names()
        if isinstance(exclude, set):
            exclude = {s for s in exclude}
            exclude = exclude.union(related_set)
        elif isinstance(exclude, dict):
            # relations are handled in ormar - take only own fields (ellipsis in dict)
            exclude = {k for k, v in exclude.items() if v is Ellipsis}
            exclude = exclude.union(related_set)
        return exclude

    @classmethod
    def _update_excluded_with_pks_and_through(
        cls, exclude: Set, exclude_primary_keys: bool, exclude_through_models: bool
    ) -> Set:
        """
        Updates excluded names with name of pk column if exclude flag is set.

        :param exclude: set of names to exclude
        :type exclude: Set
        :param exclude_primary_keys: flag if the primary keys should be excluded
        :type exclude_primary_keys: bool
        :return: set updated with pk if flag is set
        :rtype: Set
        """
        if exclude_primary_keys:
            exclude.add(cls.Meta.pkname)
        if exclude_through_models:
            exclude = exclude.union(cls.extract_through_names())
        return exclude

    @classmethod
    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

get_child(items, key=None) staticmethod

Used to get nested dictionaries keys if they exists otherwise returns passed items.

Parameters:

Name Type Description Default
items Union[Set, Dict, None]

bag of items to include or exclude

required
key str

name of the child to extract

None

Returns:

Type Description
Union[Set, Dict, None]

child extracted from items if exists

Source code in ormar/models/mixins/excludable_mixin.py
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
@staticmethod
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

get_names_to_exclude(excludable, alias) classmethod

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.

Parameters:

Name Type Description Default
alias str

alias of current relation

required
excludable ExcludableItems

structure of fields to include and exclude

required

Returns:

Type Description
Set

set of field names that should be excluded

Source code in ormar/models/mixins/excludable_mixin.py
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
@classmethod
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

own_table_columns(model, excludable, alias='', use_alias=False, add_pk_columns=True) classmethod

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).

Parameters:

Name Type Description Default
add_pk_columns bool

flag if add primary key - always yes if ormar parses data

True
alias str

relation prefix

''
excludable ExcludableItems

structure of fields to include and exclude

required
model Union[Type['Model'], Type['ModelRow']]

model on columns are selected

required
use_alias bool

flag if aliases or field names should be used

False

Returns:

Type Description
List[str]

list of column field names or aliases

Source code in ormar/models/mixins/excludable_mixin.py
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
@classmethod
def own_table_columns(
    cls,
    model: Union[Type["Model"], Type["ModelRow"]],
    excludable: ExcludableItems,
    alias: str = "",
    use_alias: bool = False,
    add_pk_columns: bool = True,
) -> 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 add_pk_columns: flag if add primary key - always yes if ormar parses data
    :type add_pk_columns: bool
    :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
    if add_pk_columns:
        columns = cls._populate_pk_column(
            model=model, columns=columns, use_alias=use_alias
        )

    return columns

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.

Source code in ormar/models/mixins/merge_mixin.py
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
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: List["Model"]) -> List["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: Dict = {}

        for model in result_rows:
            grouped_instances.setdefault(model.pk, []).append(model)

        for group in grouped_instances.values():
            model = group.pop(0)
            if group:
                for next_model in group:
                    model = cls.merge_two_instances(next_model, model)
            merged_rows.append(model)

        return merged_rows

    @classmethod
    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 isinstance(other_value, 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

    @classmethod
    def _merge_items_lists(
        cls,
        field_name: str,
        current_field: List,
        other_value: List,
        relation_map: Optional[Dict],
    ) -> List:
        """
        Takes two list of nested models and process them going deeper
        according with the map.

        If model from one's list is in other -> they are merged with relations
        to follow passed from map.

        If one's model is not in other it's simply appended to the list.

        :param field_name: name of the current relation field
        :type field_name: str
        :param current_field: list of nested models from one model
        :type current_field: List[Model]
        :param other_value: list of nested models from other model
        :type other_value: List[Model]
        :param relation_map: map of relations to follow
        :type relation_map: Dict
        :return: merged list of models
        :rtype: List[Model]
        """
        value_to_set = [x for x in other_value]
        for cur_field in current_field:
            if cur_field in other_value:
                old_value = next((x for x in other_value if x == cur_field), None)
                new_val = cls.merge_two_instances(
                    cur_field,
                    cast("Model", old_value),
                    relation_map=cur_field._skip_ellipsis(  # type: ignore
                        relation_map, field_name, default_return=dict()
                    ),
                )
                value_to_set = [x for x in value_to_set if x != cur_field] + [new_val]
            else:
                value_to_set.append(cur_field)
        return value_to_set

merge_instances_list(result_rows) classmethod

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.

Parameters:

Name Type Description Default
result_rows List['Model']

list of already initialized Models with child models populated, each instance is one row in db and some models can duplicate

required

Returns:

Type Description
List["Model"]

list of merged models where each main model is unique

Source code in ormar/models/mixins/merge_mixin.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
@classmethod
def merge_instances_list(cls, result_rows: List["Model"]) -> List["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: Dict = {}

    for model in result_rows:
        grouped_instances.setdefault(model.pk, []).append(model)

    for group in grouped_instances.values():
        model = group.pop(0)
        if group:
            for next_model in group:
                model = cls.merge_two_instances(next_model, model)
        merged_rows.append(model)

    return merged_rows

merge_two_instances(one, other, relation_map=None) classmethod

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.

Parameters:

Name Type Description Default
relation_map Dict

map of models relations to follow

None
one 'Model'

previous model instance

required
other 'Model'

current model instance

required

Returns:

Type Description
Model

current Model instance with data merged from previous one.

Source code in ormar/models/mixins/merge_mixin.py
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
@classmethod
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 isinstance(other_value, 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

PrefetchQueryMixin

Bases: RelationMixin

Used in PrefetchQuery to extract ids and names of models to prefetch.

Source code in ormar/models/mixins/prefetch_mixin.py
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
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

        get_name: Callable  # defined in NewBaseModel

    @staticmethod
    def get_clause_target_and_filter_column_name(
        parent_model: Type["Model"],
        target_model: Type["Model"],
        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].get_related_name()
            field = target_model.Meta.model_fields[field_name]
            if field.is_multi:
                field = cast("ManyToManyField", field)
                field_name = field.default_target_field_name()
                sub_field = field.through.Meta.model_fields[field_name]
                return field.through, sub_field.get_alias()
            return target_model, field.get_alias()
        target_field = target_model.get_column_alias(target_model.Meta.pkname)
        return target_model, target_field

    @staticmethod
    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 (
                parent_model.get_column_alias(column_name) if use_raw else column_name
            )
        column = parent_model.Meta.model_fields[related]
        return column.get_alias() if use_raw else column.name

    @classmethod
    def get_related_field_name(cls, target_field: "ForeignKeyField") -> 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 target_field.is_multi:
            return cls.get_name()
        if target_field.virtual:
            return target_field.get_related_name()
        return target_field.to.Meta.pkname

    @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 = [
                related
                for related in cls.extract_related_names()
                if related in prefetch_dict
            ]
        return related_to_extract

get_clause_target_and_filter_column_name(parent_model, target_model, reverse, related) staticmethod

Returns Model on which query clause should be performed and name of the column.

Parameters:

Name Type Description Default
parent_model Type['Model']

related model that the relation lead to

required
target_model Type['Model']

model on which query should be perfomed

required
reverse bool

flag if the relation is reverse

required
related str

name of the relation field

required

Returns:

Type Description
Tuple[Type[Model], str]

Model on which query clause should be performed and name of the column

Source code in ormar/models/mixins/prefetch_mixin.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@staticmethod
def get_clause_target_and_filter_column_name(
    parent_model: Type["Model"],
    target_model: Type["Model"],
    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].get_related_name()
        field = target_model.Meta.model_fields[field_name]
        if field.is_multi:
            field = cast("ManyToManyField", field)
            field_name = field.default_target_field_name()
            sub_field = field.through.Meta.model_fields[field_name]
            return field.through, sub_field.get_alias()
        return target_model, field.get_alias()
    target_field = target_model.get_column_alias(target_model.Meta.pkname)
    return target_model, target_field

get_column_name_for_id_extraction(parent_model, reverse, related, use_raw) staticmethod

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.

Parameters:

Name Type Description Default
parent_model Type['Model']

model from which id column should be extracted

required
reverse bool

flag if the relation is reverse

required
related str

name of the relation field

required
use_raw bool

flag if aliases or field names should be used

required

Returns:

Type Description
Source code in ormar/models/mixins/prefetch_mixin.py
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
@staticmethod
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 (
            parent_model.get_column_alias(column_name) if use_raw else column_name
        )
    column = parent_model.Meta.model_fields[related]
    return column.get_alias() if use_raw else column.name

get_filtered_names_to_extract(prefetch_dict) classmethod

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.

Parameters:

Name Type Description Default
prefetch_dict Dict

dictionary of fields to extract

required

Returns:

Type Description
List

list of fields names to extract

Source code in ormar/models/mixins/prefetch_mixin.py
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
@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 = [
            related
            for related in cls.extract_related_names()
            if related in prefetch_dict
        ]
    return related_to_extract

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.

Parameters:

Name Type Description Default
target_field 'ForeignKeyField'

relation field that should be used in prefetch

required

Returns:

Type Description
str

name of the field

Source code in ormar/models/mixins/prefetch_mixin.py
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
@classmethod
def get_related_field_name(cls, target_field: "ForeignKeyField") -> 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 target_field.is_multi:
        return cls.get_name()
    if target_field.virtual:
        return target_field.get_related_name()
    return target_field.to.Meta.pkname

PydanticMixin

Bases: RelationMixin

Source code in ormar/models/mixins/pydantic_mixin.py
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
class PydanticMixin(RelationMixin):

    __cache__: Dict[str, Type[pydantic.BaseModel]] = {}

    if TYPE_CHECKING:  # pragma: no cover
        __fields__: Dict[str, ModelField]
        _skip_ellipsis: Callable
        _get_not_excluded_fields: Callable

    @classmethod
    def get_pydantic(
        cls, *, include: Union[Set, Dict] = None, exclude: Union[Set, Dict] = None
    ) -> Type[pydantic.BaseModel]:
        """
        Returns a pydantic model out of ormar model.

        Converts also nested ormar models into pydantic models.

        Can be used to fully exclude certain fields in fastapi response and requests.

        :param include: fields of own and nested models to include
        :type include: Union[Set, Dict, None]
        :param exclude: fields of own and nested models to exclude
        :type exclude: Union[Set, Dict, None]
        """
        relation_map = translate_list_to_dict(cls._iterate_related_models())

        return cls._convert_ormar_to_pydantic(
            include=include, exclude=exclude, relation_map=relation_map
        )

    @classmethod
    def _convert_ormar_to_pydantic(
        cls,
        relation_map: Dict[str, Any],
        include: Union[Set, Dict] = None,
        exclude: Union[Set, Dict] = None,
    ) -> Type[pydantic.BaseModel]:
        if include and isinstance(include, Set):
            include = translate_list_to_dict(include)
        if exclude and isinstance(exclude, Set):
            exclude = translate_list_to_dict(exclude)
        fields_dict: Dict[str, Any] = dict()
        defaults: Dict[str, Any] = dict()
        fields_to_process = cls._get_not_excluded_fields(
            fields={*cls.Meta.model_fields.keys()}, include=include, exclude=exclude
        )
        fields_to_process.sort(
            key=lambda x: list(cls.Meta.model_fields.keys()).index(x)
        )

        cache_key = f"{cls.__name__}_{str(include)}_{str(exclude)}"
        if cache_key in cls.__cache__:
            return cls.__cache__[cache_key]

        for name in fields_to_process:
            field = cls._determine_pydantic_field_type(
                name=name,
                defaults=defaults,
                include=include,
                exclude=exclude,
                relation_map=relation_map,
            )
            if field is not None:
                fields_dict[name] = field
        model = type(
            f"{cls.__name__}_{''.join(choices(string.ascii_uppercase, k=3))}",
            (pydantic.BaseModel,),
            {"__annotations__": fields_dict, **defaults},
        )
        model = cast(Type[pydantic.BaseModel], model)
        cls._copy_field_validators(model=model)
        cls.__cache__[cache_key] = model
        return model

    @classmethod
    def _determine_pydantic_field_type(
        cls,
        name: str,
        defaults: Dict,
        include: Union[Set, Dict, None],
        exclude: Union[Set, Dict, None],
        relation_map: Dict[str, Any],
    ) -> Any:
        field = cls.Meta.model_fields[name]
        target: Any = None
        if field.is_relation and name in relation_map:  # type: ignore
            target = field.to._convert_ormar_to_pydantic(
                include=cls._skip_ellipsis(include, name),
                exclude=cls._skip_ellipsis(exclude, name),
                relation_map=cls._skip_ellipsis(
                    relation_map, name, default_return=dict()
                ),
            )
            if field.is_multi or field.virtual:
                target = List[target]  # type: ignore
        elif not field.is_relation:
            defaults[name] = cls.__fields__[name].field_info
            target = field.__type__
        if target is not None and field.nullable:
            target = Optional[target]
        return target

    @classmethod
    def _copy_field_validators(cls, model: Type[pydantic.BaseModel]) -> None:
        """
        Copy field validators from ormar model to generated pydantic model.
        """
        for field_name, field in model.__fields__.items():
            if (
                field_name not in cls.__fields__
                or cls.Meta.model_fields[field_name].is_relation
            ):
                continue
            validators = cls.__fields__[field_name].validators
            already_attached = [
                validator.__wrapped__ for validator in field.validators  # type: ignore
            ]
            validators_to_copy = [
                validator
                for validator in validators
                if validator.__wrapped__ not in already_attached  # type: ignore
            ]
            field.validators.extend(copy.deepcopy(validators_to_copy))
            class_validators = cls.__fields__[field_name].class_validators
            field.class_validators.update(copy.deepcopy(class_validators))
            field.pre_validators = copy.deepcopy(
                cls.__fields__[field_name].pre_validators
            )
            field.post_validators = copy.deepcopy(
                cls.__fields__[field_name].post_validators
            )

get_pydantic(*, include=None, exclude=None) classmethod

Returns a pydantic model out of ormar model.

Converts also nested ormar models into pydantic models.

Can be used to fully exclude certain fields in fastapi response and requests.

Parameters:

Name Type Description Default
include Union[Set, Dict]

fields of own and nested models to include

None
exclude Union[Set, Dict]

fields of own and nested models to exclude

None
Source code in ormar/models/mixins/pydantic_mixin.py
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@classmethod
def get_pydantic(
    cls, *, include: Union[Set, Dict] = None, exclude: Union[Set, Dict] = None
) -> Type[pydantic.BaseModel]:
    """
    Returns a pydantic model out of ormar model.

    Converts also nested ormar models into pydantic models.

    Can be used to fully exclude certain fields in fastapi response and requests.

    :param include: fields of own and nested models to include
    :type include: Union[Set, Dict, None]
    :param exclude: fields of own and nested models to exclude
    :type exclude: Union[Set, Dict, None]
    """
    relation_map = translate_list_to_dict(cls._iterate_related_models())

    return cls._convert_ormar_to_pydantic(
        include=include, exclude=exclude, relation_map=relation_map
    )

SavePrepareMixin

Bases: RelationMixin, AliasMixin

Used to prepare models to be saved in database

Source code in ormar/models/mixins/save_mixin.py
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
class SavePrepareMixin(RelationMixin, AliasMixin):
    """
    Used to prepare models to be saved in database
    """

    if TYPE_CHECKING:  # pragma: nocover
        _choices_fields: Optional[Set]
        _skip_ellipsis: Callable
        _json_fields: Set[str]
        _bytes_fields: Set[str]
        __fields__: Dict[str, pydantic.fields.ModelField]

    @classmethod
    def prepare_model_to_save(cls, new_kwargs: dict) -> dict:
        """
        Combines all preparation methods before saving.
        Removes primary key for if it's nullable or autoincrement pk field,
        and it's set to None.
        Substitute related models with their primary key values as fk column.
        Populates the default values for field with default set and no value.
        Translate columns into aliases (db names).

        :param new_kwargs: dictionary of model that is about to be saved
        :type new_kwargs: Dict[str, str]
        :return: dictionary of model that is about to be saved
        :rtype: Dict[str, str]
        """
        new_kwargs = cls._remove_pk_from_kwargs(new_kwargs)
        new_kwargs = cls._remove_not_ormar_fields(new_kwargs)
        new_kwargs = cls.substitute_models_with_pks(new_kwargs)
        new_kwargs = cls.populate_default_values(new_kwargs)
        new_kwargs = cls.reconvert_str_to_bytes(new_kwargs)
        new_kwargs = cls.translate_columns_to_aliases(new_kwargs)
        return new_kwargs

    @classmethod
    def prepare_model_to_update(cls, new_kwargs: dict) -> dict:
        """
        Combines all preparation methods before updating.
        :param new_kwargs: dictionary of model that is about to be saved
        :type new_kwargs: Dict[str, str]
        :return: dictionary of model that is about to be updated
        :rtype: Dict[str, str]
        """
        new_kwargs = cls.parse_non_db_fields(new_kwargs)
        new_kwargs = cls.substitute_models_with_pks(new_kwargs)
        new_kwargs = cls.reconvert_str_to_bytes(new_kwargs)
        new_kwargs = cls.dump_all_json_fields_to_str(new_kwargs)
        new_kwargs = cls.translate_columns_to_aliases(new_kwargs)
        new_kwargs = cls.translate_enum_columns(new_kwargs)
        return new_kwargs

    @classmethod
    def translate_enum_columns(cls, new_kwargs: dict) -> dict:
        for k, v in new_kwargs.items():
            if isinstance(v, Enum):
                new_kwargs[k] = v.name
        return new_kwargs

    @classmethod
    def _remove_not_ormar_fields(cls, new_kwargs: dict) -> dict:
        """
        Removes primary key for if it's nullable or autoincrement pk field,
        and it's set to None.

        :param new_kwargs: dictionary of model that is about to be saved
        :type new_kwargs: Dict[str, str]
        :return: dictionary of model that is about to be saved
        :rtype: Dict[str, str]
        """
        ormar_fields = {
            k for k, v in cls.Meta.model_fields.items() if not v.pydantic_only
        }
        new_kwargs = {k: v for k, v in new_kwargs.items() if k in ormar_fields}
        return new_kwargs

    @classmethod
    def _remove_pk_from_kwargs(cls, new_kwargs: dict) -> dict:
        """
        Removes primary key for if it's nullable or autoincrement pk field,
        and it's set to None.

        :param new_kwargs: dictionary of model that is about to be saved
        :type new_kwargs: Dict[str, str]
        :return: dictionary of model that is about to be saved
        :rtype: Dict[str, str]
        """
        pkname = cls.Meta.pkname
        pk = cls.Meta.model_fields[pkname]
        if new_kwargs.get(pkname, ormar.Undefined) is None and (
            pk.nullable or pk.autoincrement
        ):
            del new_kwargs[pkname]
        return new_kwargs

    @classmethod
    def parse_non_db_fields(cls, model_dict: Dict) -> Dict:
        """
        Receives dictionary of model that is about to be saved and changes uuid fields
        to strings in bulk_update.

        :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 name, field in cls.Meta.model_fields.items():
            if field.__type__ == uuid.UUID and name in model_dict:
                parsers = {"string": lambda x: str(x), "hex": lambda x: "%.32x" % x.int}
                uuid_format = field.column_type.uuid_format
                parser: Callable[..., Any] = parsers.get(uuid_format, lambda x: x)
                model_dict[name] = parser(model_dict[name])
        return model_dict

    @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:
                target_field = cls.Meta.model_fields[field]
                target_pkname = target_field.to.Meta.pkname
                if isinstance(field_value, ormar.Model):  # pragma: no cover
                    pk_value = getattr(field_value, target_pkname)
                    if not pk_value:
                        raise ModelPersistenceError(
                            f"You cannot save {field_value.get_name()} "
                            f"model without pk set!"
                        )
                    model_dict[field] = pk_value
                elif isinstance(field_value, (list, dict)) and field_value:
                    if isinstance(field_value, list):
                        model_dict[field] = [
                            target.get(target_pkname) for target in field_value
                        ]
                    else:
                        model_dict[field] = field_value.get(target_pkname)
                else:
                    model_dict.pop(field, None)
        return model_dict

    @classmethod
    def reconvert_str_to_bytes(cls, model_dict: Dict) -> Dict:
        """
        Receives dictionary of model that is about to be saved and changes
        all bytes fields that are represented as strings back into bytes.

        :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
        """
        bytes_base64_fields = {
            name
            for name, field in cls.Meta.model_fields.items()
            if field.represent_as_base64_str
        }
        for key, value in model_dict.items():
            if key in cls._bytes_fields and isinstance(value, str):
                model_dict[key] = (
                    value.encode("utf-8")
                    if key not in bytes_base64_fields
                    else base64.b64decode(value)
                )
        return model_dict

    @classmethod
    def dump_all_json_fields_to_str(cls, model_dict: Dict) -> Dict:
        """
        Receives dictionary of model that is about to be saved and changes
        all json fields into strings

        :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 key, value in model_dict.items():
            if key in cls._json_fields:
                model_dict[key] = encode_json(value)
        return model_dict

    @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
                and field.has_default(use_server=False)
                and not field.pydantic_only
            ):
                new_kwargs[field_name] = field.get_default()
            # clear fields with server_default set as None
            if (
                field.server_default is not None
                and new_kwargs.get(field_name, None) is None
            ):
                new_kwargs.pop(field_name, None)
        return new_kwargs

    @classmethod
    def validate_choices(cls, new_kwargs: Dict) -> Dict:
        """
        Receives dictionary of model that is about to be saved and validates the
        fields with choices set to see if the value is allowed.

        :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
        """
        if not cls._choices_fields:
            return new_kwargs

        fields_to_check = [
            field
            for field in cls.Meta.model_fields.values()
            if field.name in cls._choices_fields and field.name in new_kwargs
        ]
        for field in fields_to_check:
            if new_kwargs[field.name] not in field.choices:
                raise ValueError(
                    f"{field.name}: '{new_kwargs[field.name]}' "
                    f"not in allowed choices set:"
                    f" {field.choices}"
                )
        return new_kwargs

    @staticmethod
    async def _upsert_model(
        instance: "Model",
        save_all: bool,
        previous_model: Optional["Model"],
        relation_field: Optional["ForeignKeyField"],
        update_count: int,
    ) -> int:
        """
        Method updates given instance if:

        * instance is not saved or
        * instance have no pk or
        * save_all=True flag is set

        and instance is not __pk_only__.

        If relation leading to instance is a ManyToMany also the through model is saved

        :param instance: current model to upsert
        :type instance: Model
        :param save_all: flag if all models should be saved or only not saved ones
        :type save_all: bool
        :param relation_field: field with relation
        :type relation_field: Optional[ForeignKeyField]
        :param previous_model: previous model from which method came
        :type previous_model: Model
        :param update_count: no of updated models
        :type update_count: int
        :return: no of updated models
        :rtype: int
        """
        if (
            save_all or not instance.pk or not instance.saved
        ) and not instance.__pk_only__:
            await instance.upsert()
            if relation_field and relation_field.is_multi:
                await instance._upsert_through_model(
                    instance=instance,
                    relation_field=relation_field,
                    previous_model=cast("Model", previous_model),
                )
            update_count += 1
        return update_count

    @staticmethod
    async def _upsert_through_model(
        instance: "Model", previous_model: "Model", relation_field: "ForeignKeyField"
    ) -> None:
        """
        Upsert through model for m2m relation.

        :param instance: current model to upsert
        :type instance: Model
        :param relation_field: field with relation
        :type relation_field: Optional[ForeignKeyField]
        :param previous_model: previous model from which method came
        :type previous_model: Model
        """
        through_name = previous_model.Meta.model_fields[
            relation_field.name
        ].through.get_name()
        through = getattr(instance, through_name)
        if through:
            through_dict = through.dict(exclude=through.extract_related_names())
        else:
            through_dict = {}
        await getattr(
            previous_model, relation_field.name
        ).queryset_proxy.upsert_through_instance(instance, **through_dict)

    async def _update_relation_list(
        self,
        fields_list: Collection["ForeignKeyField"],
        follow: bool,
        save_all: bool,
        relation_map: Dict,
        update_count: int,
    ) -> int:
        """
        Internal method used in save_related to follow deeper from
        related models and update numbers of updated related instances.

        :type save_all: flag if all models should be saved
        :type save_all: bool
        :param fields_list: list of ormar fields to follow and save
        :type fields_list: Collection["ForeignKeyField"]
        :param relation_map: map of relations to follow
        :type relation_map: Dict
        :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: tuple of update count and visited
        :rtype: int
        """
        for field in fields_list:
            values = self._get_field_values(name=field.name)
            for value in values:
                if follow:
                    update_count = await value.save_related(
                        follow=follow,
                        save_all=save_all,
                        relation_map=self._skip_ellipsis(  # type: ignore
                            relation_map, field.name, default_return={}
                        ),
                        update_count=update_count,
                        previous_model=self,
                        relation_field=field,
                    )
                else:
                    update_count = await value._upsert_model(
                        instance=value,
                        save_all=save_all,
                        previous_model=self,
                        relation_field=field,
                        update_count=update_count,
                    )
        return update_count

    def _get_field_values(self, name: str) -> List:
        """
        Extract field values and ensures it is a list.

        :param name: name of the field
        :type name: str
        :return: list of values
        :rtype: List
        """
        values = getattr(self, name) or []
        if not isinstance(values, list):
            values = [values]
        return values

dump_all_json_fields_to_str(model_dict) classmethod

Receives dictionary of model that is about to be saved and changes all json fields into strings

Parameters:

Name Type Description Default
model_dict Dict

dictionary of model that is about to be saved

required

Returns:

Type Description
Dict

dictionary of model that is about to be saved

Source code in ormar/models/mixins/save_mixin.py
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
@classmethod
def dump_all_json_fields_to_str(cls, model_dict: Dict) -> Dict:
    """
    Receives dictionary of model that is about to be saved and changes
    all json fields into strings

    :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 key, value in model_dict.items():
        if key in cls._json_fields:
            model_dict[key] = encode_json(value)
    return model_dict

parse_non_db_fields(model_dict) classmethod

Receives dictionary of model that is about to be saved and changes uuid fields to strings in bulk_update.

Parameters:

Name Type Description Default
model_dict Dict

dictionary of model that is about to be saved

required

Returns:

Type Description
Dict

dictionary of model that is about to be saved

Source code in ormar/models/mixins/save_mixin.py
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
@classmethod
def parse_non_db_fields(cls, model_dict: Dict) -> Dict:
    """
    Receives dictionary of model that is about to be saved and changes uuid fields
    to strings in bulk_update.

    :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 name, field in cls.Meta.model_fields.items():
        if field.__type__ == uuid.UUID and name in model_dict:
            parsers = {"string": lambda x: str(x), "hex": lambda x: "%.32x" % x.int}
            uuid_format = field.column_type.uuid_format
            parser: Callable[..., Any] = parsers.get(uuid_format, lambda x: x)
            model_dict[name] = parser(model_dict[name])
    return model_dict

populate_default_values(new_kwargs) classmethod

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.

Parameters:

Name Type Description Default
new_kwargs Dict

dictionary of model that is about to be saved

required

Returns:

Type Description
Dict

dictionary of model that is about to be saved

Source code in ormar/models/mixins/save_mixin.py
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
@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
            and field.has_default(use_server=False)
            and not field.pydantic_only
        ):
            new_kwargs[field_name] = field.get_default()
        # clear fields with server_default set as None
        if (
            field.server_default is not None
            and new_kwargs.get(field_name, None) is None
        ):
            new_kwargs.pop(field_name, None)
    return new_kwargs

prepare_model_to_save(new_kwargs) classmethod

Combines all preparation methods before saving. Removes primary key for if it's nullable or autoincrement pk field, and it's set to None. Substitute related models with their primary key values as fk column. Populates the default values for field with default set and no value. Translate columns into aliases (db names).

Parameters:

Name Type Description Default
new_kwargs dict

dictionary of model that is about to be saved

required

Returns:

Type Description
Dict[str, str]

dictionary of model that is about to be saved

Source code in ormar/models/mixins/save_mixin.py
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
@classmethod
def prepare_model_to_save(cls, new_kwargs: dict) -> dict:
    """
    Combines all preparation methods before saving.
    Removes primary key for if it's nullable or autoincrement pk field,
    and it's set to None.
    Substitute related models with their primary key values as fk column.
    Populates the default values for field with default set and no value.
    Translate columns into aliases (db names).

    :param new_kwargs: dictionary of model that is about to be saved
    :type new_kwargs: Dict[str, str]
    :return: dictionary of model that is about to be saved
    :rtype: Dict[str, str]
    """
    new_kwargs = cls._remove_pk_from_kwargs(new_kwargs)
    new_kwargs = cls._remove_not_ormar_fields(new_kwargs)
    new_kwargs = cls.substitute_models_with_pks(new_kwargs)
    new_kwargs = cls.populate_default_values(new_kwargs)
    new_kwargs = cls.reconvert_str_to_bytes(new_kwargs)
    new_kwargs = cls.translate_columns_to_aliases(new_kwargs)
    return new_kwargs

prepare_model_to_update(new_kwargs) classmethod

Combines all preparation methods before updating.

Parameters:

Name Type Description Default
new_kwargs dict

dictionary of model that is about to be saved

required

Returns:

Type Description
Dict[str, str]

dictionary of model that is about to be updated

Source code in ormar/models/mixins/save_mixin.py
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
@classmethod
def prepare_model_to_update(cls, new_kwargs: dict) -> dict:
    """
    Combines all preparation methods before updating.
    :param new_kwargs: dictionary of model that is about to be saved
    :type new_kwargs: Dict[str, str]
    :return: dictionary of model that is about to be updated
    :rtype: Dict[str, str]
    """
    new_kwargs = cls.parse_non_db_fields(new_kwargs)
    new_kwargs = cls.substitute_models_with_pks(new_kwargs)
    new_kwargs = cls.reconvert_str_to_bytes(new_kwargs)
    new_kwargs = cls.dump_all_json_fields_to_str(new_kwargs)
    new_kwargs = cls.translate_columns_to_aliases(new_kwargs)
    new_kwargs = cls.translate_enum_columns(new_kwargs)
    return new_kwargs

reconvert_str_to_bytes(model_dict) classmethod

Receives dictionary of model that is about to be saved and changes all bytes fields that are represented as strings back into bytes.

Parameters:

Name Type Description Default
model_dict Dict

dictionary of model that is about to be saved

required

Returns:

Type Description
Dict

dictionary of model that is about to be saved

Source code in ormar/models/mixins/save_mixin.py
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
@classmethod
def reconvert_str_to_bytes(cls, model_dict: Dict) -> Dict:
    """
    Receives dictionary of model that is about to be saved and changes
    all bytes fields that are represented as strings back into bytes.

    :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
    """
    bytes_base64_fields = {
        name
        for name, field in cls.Meta.model_fields.items()
        if field.represent_as_base64_str
    }
    for key, value in model_dict.items():
        if key in cls._bytes_fields and isinstance(value, str):
            model_dict[key] = (
                value.encode("utf-8")
                if key not in bytes_base64_fields
                else base64.b64decode(value)
            )
    return model_dict

substitute_models_with_pks(model_dict) classmethod

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.

Parameters:

Name Type Description Default
model_dict Dict

dictionary of model that is about to be saved

required

Returns:

Type Description
Dict

dictionary of model that is about to be saved

Source code in ormar/models/mixins/save_mixin.py
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
@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:
            target_field = cls.Meta.model_fields[field]
            target_pkname = target_field.to.Meta.pkname
            if isinstance(field_value, ormar.Model):  # pragma: no cover
                pk_value = getattr(field_value, target_pkname)
                if not pk_value:
                    raise ModelPersistenceError(
                        f"You cannot save {field_value.get_name()} "
                        f"model without pk set!"
                    )
                model_dict[field] = pk_value
            elif isinstance(field_value, (list, dict)) and field_value:
                if isinstance(field_value, list):
                    model_dict[field] = [
                        target.get(target_pkname) for target in field_value
                    ]
                else:
                    model_dict[field] = field_value.get(target_pkname)
            else:
                model_dict.pop(field, None)
    return model_dict

validate_choices(new_kwargs) classmethod

Receives dictionary of model that is about to be saved and validates the fields with choices set to see if the value is allowed.

Parameters:

Name Type Description Default
new_kwargs Dict

dictionary of model that is about to be saved

required

Returns:

Type Description
Dict

dictionary of model that is about to be saved

Source code in ormar/models/mixins/save_mixin.py
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
@classmethod
def validate_choices(cls, new_kwargs: Dict) -> Dict:
    """
    Receives dictionary of model that is about to be saved and validates the
    fields with choices set to see if the value is allowed.

    :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
    """
    if not cls._choices_fields:
        return new_kwargs

    fields_to_check = [
        field
        for field in cls.Meta.model_fields.values()
        if field.name in cls._choices_fields and field.name in new_kwargs
    ]
    for field in fields_to_check:
        if new_kwargs[field.name] not in field.choices:
            raise ValueError(
                f"{field.name}: '{new_kwargs[field.name]}' "
                f"not in allowed choices set:"
                f" {field.choices}"
            )
    return new_kwargs