Skip to content

models

Definition of Model, it's parents NewBaseModel and mixins used by models. Also defines a Metaclass that handles all constructions and relations registration, ass well as vast number of helper functions for pydantic, sqlalchemy and relations.

ExcludableItems

Keeps a dictionary of Excludables by alias + model_name keys to allow quick lookup by nested models without need to travers deeply nested dictionaries and passing include/exclude around.

Source code in ormar/models/excludable.py
Python
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
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
class ExcludableItems:
    """
    Keeps a dictionary of Excludables by alias + model_name keys
    to allow quick lookup by nested models without need to travers
    deeply nested dictionaries and passing include/exclude around.
    """

    def __init__(self) -> None:
        self.items: dict[str, Excludable] = dict()
        self._flatten_paths: set[PathParts] = set()
        self._flatten_map_cache: Optional[FlattenMap] = None

    @classmethod
    def from_excludable(cls, other: "ExcludableItems") -> "ExcludableItems":
        """
        Copy passed ExcludableItems to avoid inplace modifications.

        :param other: other excludable items to be copied
        :type other: ormar.models.excludable.ExcludableItems
        :return: copy of other
        :rtype: ormar.models.excludable.ExcludableItems
        """
        new_excludable = cls()
        for key, value in other.items.items():
            new_excludable.items[key] = value.get_copy()
        new_excludable._flatten_paths = set(other._flatten_paths)
        return new_excludable

    def include_entry_count(self) -> int:
        """
        Returns count of include items inside.
        """
        count = 0
        for key in self.items.keys():
            count += len(self.items[key].include)
        return count

    def flatten_map(self) -> Optional[FlattenMap]:
        """
        Return a :class:`FlattenMap` over the stored flatten paths, built
        lazily on first call and cached for reuse. The cache is invalidated
        whenever ``_set_slot`` adds a new flatten path. Returns ``None`` when
        no flatten paths are stored, so callers can short-circuit.

        :return: FlattenMap wrapping the nested-Ellipsis dict, or ``None``
            when no flatten paths are stored
        :rtype: Optional[FlattenMap]
        """
        if not self._flatten_paths:
            return None
        if self._flatten_map_cache is None:
            self._flatten_map_cache = FlattenMap(
                data=build_flatten_map(self._flatten_paths)
            )
        return self._flatten_map_cache

    def get(self, model_cls: type["Model"], alias: str = "") -> Excludable:
        """
        Return Excludable for given model and alias.

        :param model_cls: target model to check
        :type model_cls: ormar.models.metaclass.ModelMetaclass
        :param alias: table alias from relation manager
        :type alias: str
        :return: Excludable for given model and alias
        :rtype: ormar.models.excludable.Excludable
        """
        key = f"{alias + '_' if alias else ''}{model_cls.get_name(lower=True)}"
        excludable = self.items.get(key)
        if not excludable:
            excludable = Excludable()
            self.items[key] = excludable
        return excludable

    def build(
        self,
        items: Union[list[str], str, tuple[str], set[str], dict],
        model_cls: type["Model"],
        slot: Slot = "include",
    ) -> None:
        """
        Receives the one of the types of items and parses them as to achieve
        a end situation with one excludable per alias/model in relation.

        Each excludable has three sets of values - include, exclude, and flatten.

        :param items: values to be included, excluded or flattened
        :type items: Union[list[str], str, tuple[str], set[str], dict]
        :param model_cls: source model from which relations are constructed
        :type model_cls: ormar.models.metaclass.ModelMetaclass
        :param slot: which slot to write parsed values into
        :type slot: Slot
        """
        if isinstance(items, str):
            items = {items}

        if isinstance(items, dict):
            self._traverse_dict(
                values=items,
                source_model=model_cls,
                model_cls=model_cls,
                slot=slot,
            )
        else:
            items = set(items)
            nested_items = set(x for x in items if "__" in x)
            items.difference_update(nested_items)
            if items:
                self._set_slot(
                    items=items,
                    model_cls=model_cls,
                    slot=slot,
                )
            if nested_items:
                self._traverse_list(values=nested_items, model_cls=model_cls, slot=slot)

        if slot == "flatten":
            self._validate_flatten_prefix_collisions()

    def _set_slot(
        self,
        items: set,
        model_cls: type["Model"],
        slot: Slot,
        alias: str = "",
        path_parts: PathParts = (),
    ) -> None:
        """
        Sets set of values to be stored for the given slot on the key that
        corresponds to the passed model + alias.

        :param items: items to write
        :type items: set
        :param model_cls: target model on which the items are stored
        :type model_cls: type[Model]
        :param slot: which slot to write to
        :type slot: Slot
        :param alias: table alias from relation manager
        :type alias: str
        :param path_parts: tuple of dunder path segments leading to this model
        :type path_parts: PathParts
        """
        if slot == "flatten":
            self._validate_flatten_leaves(
                items=items, model_cls=model_cls, path_parts=path_parts
            )
            for item in items:
                self._flatten_paths.add(path_parts + (item,))
            self._flatten_map_cache = None

        key = f"{alias + '_' if alias else ''}{model_cls.get_name(lower=True)}"
        excludable = self.items.setdefault(key, Excludable())
        excludable.set_values(value=items, slot=slot)

    def _traverse_dict(  # noqa: CFQ002
        self,
        values: dict,
        source_model: type["Model"],
        model_cls: type["Model"],
        slot: Slot,
        path_parts: PathParts = (),
        alias: str = "",
    ) -> None:
        """
        Goes through dict of nested values and construct/update Excludables.

        :param values: items to include/exclude/flatten
        :type values: dict
        :param source_model: source model from which relations are constructed
        :type source_model: ormar.models.metaclass.ModelMetaclass
        :param model_cls: model reached via ``path_parts``
        :type model_cls: ormar.models.metaclass.ModelMetaclass
        :param slot: which slot to write into
        :type slot: Slot
        :param path_parts: tuple of dunder path segments leading to ``model_cls``
        :type path_parts: PathParts
        :param alias: alias of relation
        :type alias: str
        """
        self_fields = set()
        for key, value in values.items():
            if value is ...:
                self_fields.add(key)
            elif isinstance(value, set):
                nested_parts = path_parts + (key,)
                table_prefix, target_model, _, _ = get_relationship_alias_model_and_str(
                    source_model=source_model,
                    related_parts=list(nested_parts),
                )
                self._set_slot(
                    items=value,
                    model_cls=target_model,
                    slot=slot,
                    alias=table_prefix,
                    path_parts=nested_parts,
                )
            else:
                nested_parts = path_parts + (key,)
                table_prefix, target_model, _, _ = get_relationship_alias_model_and_str(
                    source_model=source_model,
                    related_parts=list(nested_parts),
                )
                self._traverse_dict(
                    values=value,
                    source_model=source_model,
                    model_cls=target_model,
                    slot=slot,
                    path_parts=nested_parts,
                    alias=table_prefix,
                )
        if self_fields:
            self._set_slot(
                items=self_fields,
                model_cls=model_cls,
                slot=slot,
                alias=alias,
                path_parts=path_parts,
            )

    def _traverse_list(
        self, values: set[str], model_cls: type["Model"], slot: Slot
    ) -> None:
        """
        Consume a set of dunder-style paths (``"a__b__c"``) and write each leaf
        to the Excludable for its target model/alias. Each path is split once
        into a tuple and threaded through ``_set_slot`` — no further string
        joins happen downstream.

        :param values: set of dunder-style path strings
        :type values: set[str]
        :param model_cls: source model from which relations are resolved
        :type model_cls: type[Model]
        :param slot: which slot to write into
        :type slot: Slot
        """
        for dunder_path in values:
            parts = tuple(dunder_path.split("__"))
            table_prefix, target_model, _, _ = get_relationship_alias_model_and_str(
                source_model=model_cls,
                related_parts=list(parts[:-1]),
            )
            self._set_slot(
                items={parts[-1]},
                model_cls=target_model,
                slot=slot,
                alias=table_prefix,
                path_parts=parts[:-1],
            )

    @staticmethod
    def _validate_flatten_leaves(
        items: set, model_cls: type["Model"], path_parts: PathParts
    ) -> None:
        """
        Ensure every leaf addressed by a flatten spec is a real relation on the
        target model (not a scalar column and not a through model).

        :param items: set of leaf names being flattened on ``model_cls``
        :type items: set
        :param model_cls: target model on which leaves must resolve to relations
        :type model_cls: type[Model]
        :param path_parts: tuple of segments leading to ``model_cls`` (only
            joined for error messages, never parsed)
        :type path_parts: PathParts
        """
        model_fields = model_cls.ormar_config.model_fields
        for item in items:
            related_field = model_fields.get(item)
            if related_field is None:
                raise QueryDefinitionError(
                    f"Unknown relation '{item}' on model "
                    f"{model_cls.get_name(lower=False)} in flatten_fields path "
                    f"'{join_path(path_parts, item)}'."
                )
            if getattr(related_field, "is_through", False):
                raise QueryDefinitionError(
                    f"Cannot flatten through model '{item}' at path "
                    f"'{join_path(path_parts, item)}'. Flatten the many-to-many "
                    f"relation itself instead."
                )
            if not getattr(related_field, "is_relation", False):
                raise QueryDefinitionError(
                    f"flatten_fields target '{join_path(path_parts, item)}' is "
                    f"not a relation on model {model_cls.get_name(lower=False)}. "
                    f"Only foreign keys, many-to-many, and reverse relations can "
                    f"be flattened."
                )

    def _validate_flatten_prefix_collisions(self) -> None:
        """
        Ensure no flatten path is a strict ancestor of another. Flattening a
        relation replaces it with a scalar PK, so a deeper flatten through the
        same chain is unreachable.

        Works directly on tuple paths — sorted so each prefix's descendants
        follow it immediately, enabling an early break once the prefix no
        longer matches.

        :raises QueryDefinitionError: on any prefix collision
        """
        paths = sorted(self._flatten_paths)
        for i, short in enumerate(paths):
            short_len = len(short)
            for longer in paths[i + 1 :]:
                if longer[:short_len] != short:
                    break
                raise QueryDefinitionError(
                    f"Conflicting flatten directives: "
                    f"'{join_path(short)}' is flattened to its primary key, "
                    f"so nested flatten '{join_path(longer)}' is unreachable."
                )

    def validate_flatten_vs_excludable(self, source_model: type["Model"]) -> None:
        """
        Ensure no flattened relation has sub-field include/exclude on its target.
        Whole-relation include/exclude at the parent level is allowed; only
        sub-field selection on the flattened child is flagged.

        :param source_model: model from which flatten paths are rooted
        :type source_model: type[Model]
        :raises QueryDefinitionError: when a flattened relation has sub-field
            include/exclude on its target model
        """
        for parts in self._flatten_paths:
            table_prefix, target_model, _, _ = get_relationship_alias_model_and_str(
                source_model=source_model,
                related_parts=list(parts),
            )
            child_key = (
                f"{table_prefix + '_' if table_prefix else ''}"
                f"{target_model.get_name(lower=True)}"
            )
            child = self.items.get(child_key)
            if not child:
                continue
            conflicts = (child.include | child.exclude) - {...}
            if conflicts:
                raise QueryDefinitionError(
                    f"Flatten conflict: relation '{join_path(parts)}' is "
                    f"flattened but include/exclude specifies children "
                    f"{sorted(conflicts)}. A flattened relation renders only "
                    f"its primary key and cannot have children selected."
                )

build(items, model_cls, slot='include')

Receives the one of the types of items and parses them as to achieve a end situation with one excludable per alias/model in relation.

Each excludable has three sets of values - include, exclude, and flatten.

:param items: values to be included, excluded or flattened :type items: Union[list[str], str, tuple[str], set[str], dict] :param model_cls: source model from which relations are constructed :type model_cls: ormar.models.metaclass.ModelMetaclass :param slot: which slot to write parsed values into :type slot: Slot

Source code in ormar/models/excludable.py
Python
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
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
def build(
    self,
    items: Union[list[str], str, tuple[str], set[str], dict],
    model_cls: type["Model"],
    slot: Slot = "include",
) -> None:
    """
    Receives the one of the types of items and parses them as to achieve
    a end situation with one excludable per alias/model in relation.

    Each excludable has three sets of values - include, exclude, and flatten.

    :param items: values to be included, excluded or flattened
    :type items: Union[list[str], str, tuple[str], set[str], dict]
    :param model_cls: source model from which relations are constructed
    :type model_cls: ormar.models.metaclass.ModelMetaclass
    :param slot: which slot to write parsed values into
    :type slot: Slot
    """
    if isinstance(items, str):
        items = {items}

    if isinstance(items, dict):
        self._traverse_dict(
            values=items,
            source_model=model_cls,
            model_cls=model_cls,
            slot=slot,
        )
    else:
        items = set(items)
        nested_items = set(x for x in items if "__" in x)
        items.difference_update(nested_items)
        if items:
            self._set_slot(
                items=items,
                model_cls=model_cls,
                slot=slot,
            )
        if nested_items:
            self._traverse_list(values=nested_items, model_cls=model_cls, slot=slot)

    if slot == "flatten":
        self._validate_flatten_prefix_collisions()

flatten_map()

Return a :class:FlattenMap over the stored flatten paths, built lazily on first call and cached for reuse. The cache is invalidated whenever _set_slot adds a new flatten path. Returns None when no flatten paths are stored, so callers can short-circuit.

:return: FlattenMap wrapping the nested-Ellipsis dict, or None when no flatten paths are stored :rtype: Optional[FlattenMap]

Source code in ormar/models/excludable.py
Python
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
def flatten_map(self) -> Optional[FlattenMap]:
    """
    Return a :class:`FlattenMap` over the stored flatten paths, built
    lazily on first call and cached for reuse. The cache is invalidated
    whenever ``_set_slot`` adds a new flatten path. Returns ``None`` when
    no flatten paths are stored, so callers can short-circuit.

    :return: FlattenMap wrapping the nested-Ellipsis dict, or ``None``
        when no flatten paths are stored
    :rtype: Optional[FlattenMap]
    """
    if not self._flatten_paths:
        return None
    if self._flatten_map_cache is None:
        self._flatten_map_cache = FlattenMap(
            data=build_flatten_map(self._flatten_paths)
        )
    return self._flatten_map_cache

from_excludable(other) classmethod

Copy passed ExcludableItems to avoid inplace modifications.

:param other: other excludable items to be copied :type other: ormar.models.excludable.ExcludableItems :return: copy of other :rtype: ormar.models.excludable.ExcludableItems

Source code in ormar/models/excludable.py
Python
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
@classmethod
def from_excludable(cls, other: "ExcludableItems") -> "ExcludableItems":
    """
    Copy passed ExcludableItems to avoid inplace modifications.

    :param other: other excludable items to be copied
    :type other: ormar.models.excludable.ExcludableItems
    :return: copy of other
    :rtype: ormar.models.excludable.ExcludableItems
    """
    new_excludable = cls()
    for key, value in other.items.items():
        new_excludable.items[key] = value.get_copy()
    new_excludable._flatten_paths = set(other._flatten_paths)
    return new_excludable

get(model_cls, alias='')

Return Excludable for given model and alias.

:param model_cls: target model to check :type model_cls: ormar.models.metaclass.ModelMetaclass :param alias: table alias from relation manager :type alias: str :return: Excludable for given model and alias :rtype: ormar.models.excludable.Excludable

Source code in ormar/models/excludable.py
Python
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
def get(self, model_cls: type["Model"], alias: str = "") -> Excludable:
    """
    Return Excludable for given model and alias.

    :param model_cls: target model to check
    :type model_cls: ormar.models.metaclass.ModelMetaclass
    :param alias: table alias from relation manager
    :type alias: str
    :return: Excludable for given model and alias
    :rtype: ormar.models.excludable.Excludable
    """
    key = f"{alias + '_' if alias else ''}{model_cls.get_name(lower=True)}"
    excludable = self.items.get(key)
    if not excludable:
        excludable = Excludable()
        self.items[key] = excludable
    return excludable

include_entry_count()

Returns count of include items inside.

Source code in ormar/models/excludable.py
Python
338
339
340
341
342
343
344
345
def include_entry_count(self) -> int:
    """
    Returns count of include items inside.
    """
    count = 0
    for key in self.items.keys():
        count += len(self.items[key].include)
    return count

validate_flatten_vs_excludable(source_model)

Ensure no flattened relation has sub-field include/exclude on its target. Whole-relation include/exclude at the parent level is allowed; only sub-field selection on the flattened child is flagged.

:param source_model: model from which flatten paths are rooted :type source_model: type[Model] :raises QueryDefinitionError: when a flattened relation has sub-field include/exclude on its target model

Source code in ormar/models/excludable.py
Python
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
def validate_flatten_vs_excludable(self, source_model: type["Model"]) -> None:
    """
    Ensure no flattened relation has sub-field include/exclude on its target.
    Whole-relation include/exclude at the parent level is allowed; only
    sub-field selection on the flattened child is flagged.

    :param source_model: model from which flatten paths are rooted
    :type source_model: type[Model]
    :raises QueryDefinitionError: when a flattened relation has sub-field
        include/exclude on its target model
    """
    for parts in self._flatten_paths:
        table_prefix, target_model, _, _ = get_relationship_alias_model_and_str(
            source_model=source_model,
            related_parts=list(parts),
        )
        child_key = (
            f"{table_prefix + '_' if table_prefix else ''}"
            f"{target_model.get_name(lower=True)}"
        )
        child = self.items.get(child_key)
        if not child:
            continue
        conflicts = (child.include | child.exclude) - {...}
        if conflicts:
            raise QueryDefinitionError(
                f"Flatten conflict: relation '{join_path(parts)}' is "
                f"flattened but include/exclude specifies children "
                f"{sorted(conflicts)}. A flattened relation renders only "
                f"its primary key and cannot have children selected."
            )

Model

Bases: ModelRow

Source code in ormar/models/model.py
Python
 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
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
409
410
411
412
413
414
class Model(ModelRow):
    __abstract__ = False
    if TYPE_CHECKING:  # pragma nocover
        ormar_config: OrmarConfig

    def __repr__(self) -> str:  # pragma nocover
        _repr = {
            k: getattr(self, k)
            for k, v in self.ormar_config.model_fields.items()
            if not v.skip_field
        }
        return f"{self.__class__.__name__}({str(_repr)})"

    async def _execute_query(self, expr: Executable, is_select: bool = False) -> Any:
        async with self.ormar_config.database.get_query_executor() as executor:
            row = (
                await executor.fetch_one(expr)
                if is_select
                else await executor.execute(expr)
            )
        return row

    async def _emit_signal(self, name: str, **kwargs: Any) -> None:
        """
        Emit a lifecycle signal on this model's SignalEmitter.

        When ``ormar_config.emit_parent_signals`` is True, the same signal is
        also dispatched on every concrete ormar ancestor in the MRO. Each
        emit uses ``sender=ancestor_cls`` so handlers registered with
        ``@pre_save(Parent)`` see the parent class as the sender.

        :param name: signal name on the SignalEmitter (e.g. ``"pre_save"``).
        :type name: str
        :param kwargs: extra payload forwarded to receivers.
        :type kwargs: Any
        """
        cls = type(self)
        await getattr(self.ormar_config.signals, name).send(sender=cls, **kwargs)
        if not self.ormar_config.emit_parent_signals:
            return
        seen = {id(self.ormar_config.signals)}
        for ancestor in cls.__mro__[1:]:
            cfg = getattr(ancestor, "ormar_config", None)
            if cfg is None or cfg.abstract or id(cfg.signals) in seen:
                continue
            seen.add(id(cfg.signals))
            await getattr(cfg.signals, name).send(sender=ancestor, **kwargs)

    async def upsert(self: T, **kwargs: Any) -> T:
        """
        Performs either a save or an update depending on the presence of the pk.
        If the pk field is filled it's an update, otherwise the save is performed.
        For save kwargs are ignored, used only in update if provided.

        :param kwargs: list of fields to update
        :type kwargs: Any
        :return: saved Model
        :rtype: Model
        """

        force_save = kwargs.pop("__force_save__", False)
        if force_save:
            expr = self.ormar_config.table.select().where(self.pk_column == self.pk)
            row = await self._execute_query(expr, is_select=True)
            if not row:
                return await self.save()
            return await self.update(**kwargs)

        if not self.pk:
            return await self.save()
        return await self.update(**kwargs)

    async def save(self: T) -> T:
        """
        Performs a save of given Model instance.
        If primary key is already saved, db backend will throw integrity error.

        Related models are saved by pk number, reverse relation and many to many fields
        are not saved - use corresponding relations methods.

        If there are fields with server_default set and those fields
        are not already filled save will trigger also a second query
        to refreshed the fields populated server side.

        Does not recognize if model was previously saved.
        If you want to perform update or insert depending on the pk
        fields presence use upsert.

        Sends pre_save and post_save signals.

        Sets model save status to True.

        :return: saved Model
        :rtype: Model
        """
        await self._emit_signal("pre_save", instance=self)
        self_fields = self._extract_model_db_fields()

        if (
            not self.pk
            and self.ormar_config.model_fields[self.ormar_config.pkname].autoincrement
        ):
            self_fields.pop(self.ormar_config.pkname, None)
        self_fields = self.populate_default_values(self_fields)
        self.update_from_dict(
            {
                k: v
                for k, v in self_fields.items()
                if k not in self.extract_related_names()
            }
        )

        self_fields = self.translate_columns_to_aliases(self_fields)
        expr = self.ormar_config.table.insert()
        expr = expr.values(**self_fields)

        pkname = self.ormar_config.pkname
        pk_returned_from_insert = False
        pk = await self._execute_query(expr)
        if pk and isinstance(pk, self.pk_type()):
            setattr(self, pkname, pk)
            pk_returned_from_insert = True

        if self.pk is None:
            raise ModelPersistenceError(  # pragma: no cover
                f"Could not recover the generated primary key for "
                f"{self.__class__.__name__} after INSERT. This happens on "
                "backends that lack RETURNING support for server-side "
                "defaults on non-AUTO_INCREMENT primary keys — most notably "
                "Oracle MySQL, which does not implement RETURNING in any "
                "version. Use autoincrement=True, provide the primary key "
                "client-side, or switch to a RETURNING-capable backend "
                "(PostgreSQL, SQLite 3.35+, MariaDB 10.5+)."
            )

        self.set_save_status(True)
        # refresh server-side defaults — but skip the pk if the insert already
        # returned it, so save() stays a single round-trip when the pk is the
        # only server_default field.
        if any(
            field.server_default is not None
            for name, field in self.ormar_config.model_fields.items()
            if name not in self_fields
            and not (pk_returned_from_insert and name == pkname)
        ):
            await self.load()

        self.__setattr_fields__.clear()
        await self._emit_signal("post_save", instance=self)
        return self

    async def save_related(  # noqa: CCR001, CFQ002
        self,
        follow: bool = False,
        save_all: bool = False,
        relation_map: Optional[builtins.dict] = None,
        exclude: Union[set, builtins.dict, None] = None,
        update_count: int = 0,
        previous_model: Optional["Model"] = None,
        relation_field: Optional["ForeignKeyField"] = None,
    ) -> int:
        """
        Triggers a upsert method on all related models
        if the instances are not already saved.
        By default saves only the directly related ones.

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

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

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

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

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

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

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

            post_save = fields_to_visit - pre_save

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

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

        return update_count

    async def update(self: T, _columns: Optional[list[str]] = None, **kwargs: Any) -> T:
        """
        Performs update of Model instance in the database.
        Fields can be updated before or you can pass them as kwargs.

        Sends pre_update and post_update signals.

        Sets model save status to True.

        :param _columns: list of columns to update, if None all are updated
        :type _columns: list
        :raises ModelPersistenceError: If the pk column is not set

        :param kwargs: list of fields to update as field=value pairs
        :type kwargs: Any
        :return: updated Model
        :rtype: Model
        """
        explicit_fields = self.__setattr_fields__ | kwargs.keys()
        values = self.populate_onupdate_value(
            dict(kwargs), explicit_fields=explicit_fields
        )
        if values:
            self.update_from_dict(values)

        if not self.pk:
            raise ModelPersistenceError(
                "You cannot update not saved model! Use save or upsert method."
            )

        await self._emit_signal("pre_update", instance=self, passed_args=kwargs)
        self_fields = self._extract_model_db_fields()
        self_fields.pop(self.get_column_name_from_alias(self.ormar_config.pkname))
        if _columns:
            self_fields = {
                k: v
                for k, v in self_fields.items()
                if k in _columns or k in self._onupdate_fields
            }
        if self_fields:
            self_fields = self.translate_columns_to_aliases(self_fields)
            expr = self.ormar_config.table.update().values(**self_fields)
            expr = expr.where(self.pk_column == getattr(self, self.ormar_config.pkname))
            await self._execute_query(expr)
        self.set_save_status(True)
        self.__setattr_fields__.clear()
        await self._emit_signal("post_update", instance=self)
        return self

    async def delete(self) -> int:
        """
        Removes the Model instance from the database.

        Sends pre_delete and post_delete signals.

        Sets model save status to False.

        Note it does not delete the Model itself (python object).
        So you can delete and later save (since pk is deleted no conflict will arise)
        or update and the Model will be saved in database again.

        :return: number of deleted rows (for some backends)
        :rtype: int
        """
        await self._emit_signal("pre_delete", instance=self)
        expr = self.ormar_config.table.delete()
        expr = expr.where(self.pk_column == (getattr(self, self.ormar_config.pkname)))
        result = await self._execute_query(expr)
        self.set_save_status(False)
        await self._emit_signal("post_delete", instance=self)
        return result

    async def load(self: T) -> T:
        """
        Allow to refresh existing Models fields from database.
        Be careful as the related models can be overwritten by pk_only models in load.
        Does NOT refresh the related models fields if they were loaded before.

        :raises NoMatch: If given pk is not found in database.

        :return: reloaded Model
        :rtype: Model
        """
        expr = self.ormar_config.table.select().where(self.pk_column == self.pk)
        row = await self._execute_query(expr, is_select=True)
        if not row:  # pragma nocover
            raise NoMatch("Instance was deleted from database and cannot be refreshed")
        kwargs = dict(row)
        kwargs = self.translate_aliases_to_columns(kwargs)
        self.update_from_dict(kwargs)
        self.set_save_status(True)
        self.__setattr_fields__.clear()
        return self

    async def load_all(
        self: T,
        follow: bool = False,
        exclude: Union[list, str, set, dict, None] = None,
        order_by: Union[list, str, None] = None,
    ) -> T:
        """
        Allow to refresh existing Models fields from database.
        Performs refresh of the related models fields.

        By default, loads only self and the directly related ones.

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

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

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

        :param order_by: columns by which models should be sorted
        :type order_by: Union[list, str]
        :raises NoMatch: If given pk is not found in database.

        :param exclude: related models to exclude
        :type exclude: Union[list, str, set, 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
        :return: reloaded Model
        :rtype: Model
        """
        relations = list(self.extract_related_names())
        if follow:
            relations = self._iterate_related_models()
        queryset = self.__class__.objects
        if exclude:
            queryset = queryset.exclude_fields(exclude)
        if order_by:
            queryset = queryset.order_by(order_by)
        instance = await queryset.select_related(relations).get(pk=self.pk)
        self._orm.clear()
        self.update_from_dict(instance.model_dump())
        self.__setattr_fields__.clear()
        return self

delete() async

Removes the Model instance from the database.

Sends pre_delete and post_delete signals.

Sets model save status to False.

Note it does not delete the Model itself (python object). So you can delete and later save (since pk is deleted no conflict will arise) or update and the Model will be saved in database again.

:return: number of deleted rows (for some backends) :rtype: int

Source code in ormar/models/model.py
Python
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
async def delete(self) -> int:
    """
    Removes the Model instance from the database.

    Sends pre_delete and post_delete signals.

    Sets model save status to False.

    Note it does not delete the Model itself (python object).
    So you can delete and later save (since pk is deleted no conflict will arise)
    or update and the Model will be saved in database again.

    :return: number of deleted rows (for some backends)
    :rtype: int
    """
    await self._emit_signal("pre_delete", instance=self)
    expr = self.ormar_config.table.delete()
    expr = expr.where(self.pk_column == (getattr(self, self.ormar_config.pkname)))
    result = await self._execute_query(expr)
    self.set_save_status(False)
    await self._emit_signal("post_delete", instance=self)
    return result

load() async

Allow to refresh existing Models fields from database. Be careful as the related models can be overwritten by pk_only models in load. Does NOT refresh the related models fields if they were loaded before.

:raises NoMatch: If given pk is not found in database.

:return: reloaded Model :rtype: Model

Source code in ormar/models/model.py
Python
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
async def load(self: T) -> T:
    """
    Allow to refresh existing Models fields from database.
    Be careful as the related models can be overwritten by pk_only models in load.
    Does NOT refresh the related models fields if they were loaded before.

    :raises NoMatch: If given pk is not found in database.

    :return: reloaded Model
    :rtype: Model
    """
    expr = self.ormar_config.table.select().where(self.pk_column == self.pk)
    row = await self._execute_query(expr, is_select=True)
    if not row:  # pragma nocover
        raise NoMatch("Instance was deleted from database and cannot be refreshed")
    kwargs = dict(row)
    kwargs = self.translate_aliases_to_columns(kwargs)
    self.update_from_dict(kwargs)
    self.set_save_status(True)
    self.__setattr_fields__.clear()
    return self

load_all(follow=False, exclude=None, order_by=None) async

Allow to refresh existing Models fields from database. Performs refresh of the related models fields.

By default, loads only self and the directly related ones.

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

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

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

:param order_by: columns by which models should be sorted :type order_by: Union[list, str] :raises NoMatch: If given pk is not found in database.

:param exclude: related models to exclude :type exclude: Union[list, str, set, 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 :return: reloaded Model :rtype: Model

Source code in ormar/models/model.py
Python
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
409
410
411
412
413
414
async def load_all(
    self: T,
    follow: bool = False,
    exclude: Union[list, str, set, dict, None] = None,
    order_by: Union[list, str, None] = None,
) -> T:
    """
    Allow to refresh existing Models fields from database.
    Performs refresh of the related models fields.

    By default, loads only self and the directly related ones.

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

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

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

    :param order_by: columns by which models should be sorted
    :type order_by: Union[list, str]
    :raises NoMatch: If given pk is not found in database.

    :param exclude: related models to exclude
    :type exclude: Union[list, str, set, 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
    :return: reloaded Model
    :rtype: Model
    """
    relations = list(self.extract_related_names())
    if follow:
        relations = self._iterate_related_models()
    queryset = self.__class__.objects
    if exclude:
        queryset = queryset.exclude_fields(exclude)
    if order_by:
        queryset = queryset.order_by(order_by)
    instance = await queryset.select_related(relations).get(pk=self.pk)
    self._orm.clear()
    self.update_from_dict(instance.model_dump())
    self.__setattr_fields__.clear()
    return self

save() async

Performs a save of given Model instance. If primary key is already saved, db backend will throw integrity error.

Related models are saved by pk number, reverse relation and many to many fields are not saved - use corresponding relations methods.

If there are fields with server_default set and those fields are not already filled save will trigger also a second query to refreshed the fields populated server side.

Does not recognize if model was previously saved. If you want to perform update or insert depending on the pk fields presence use upsert.

Sends pre_save and post_save signals.

Sets model save status to True.

:return: saved Model :rtype: Model

Source code in ormar/models/model.py
Python
 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
async def save(self: T) -> T:
    """
    Performs a save of given Model instance.
    If primary key is already saved, db backend will throw integrity error.

    Related models are saved by pk number, reverse relation and many to many fields
    are not saved - use corresponding relations methods.

    If there are fields with server_default set and those fields
    are not already filled save will trigger also a second query
    to refreshed the fields populated server side.

    Does not recognize if model was previously saved.
    If you want to perform update or insert depending on the pk
    fields presence use upsert.

    Sends pre_save and post_save signals.

    Sets model save status to True.

    :return: saved Model
    :rtype: Model
    """
    await self._emit_signal("pre_save", instance=self)
    self_fields = self._extract_model_db_fields()

    if (
        not self.pk
        and self.ormar_config.model_fields[self.ormar_config.pkname].autoincrement
    ):
        self_fields.pop(self.ormar_config.pkname, None)
    self_fields = self.populate_default_values(self_fields)
    self.update_from_dict(
        {
            k: v
            for k, v in self_fields.items()
            if k not in self.extract_related_names()
        }
    )

    self_fields = self.translate_columns_to_aliases(self_fields)
    expr = self.ormar_config.table.insert()
    expr = expr.values(**self_fields)

    pkname = self.ormar_config.pkname
    pk_returned_from_insert = False
    pk = await self._execute_query(expr)
    if pk and isinstance(pk, self.pk_type()):
        setattr(self, pkname, pk)
        pk_returned_from_insert = True

    if self.pk is None:
        raise ModelPersistenceError(  # pragma: no cover
            f"Could not recover the generated primary key for "
            f"{self.__class__.__name__} after INSERT. This happens on "
            "backends that lack RETURNING support for server-side "
            "defaults on non-AUTO_INCREMENT primary keys — most notably "
            "Oracle MySQL, which does not implement RETURNING in any "
            "version. Use autoincrement=True, provide the primary key "
            "client-side, or switch to a RETURNING-capable backend "
            "(PostgreSQL, SQLite 3.35+, MariaDB 10.5+)."
        )

    self.set_save_status(True)
    # refresh server-side defaults — but skip the pk if the insert already
    # returned it, so save() stays a single round-trip when the pk is the
    # only server_default field.
    if any(
        field.server_default is not None
        for name, field in self.ormar_config.model_fields.items()
        if name not in self_fields
        and not (pk_returned_from_insert and name == pkname)
    ):
        await self.load()

    self.__setattr_fields__.clear()
    await self._emit_signal("post_save", instance=self)
    return self

Triggers a upsert method on all related models if the instances are not already saved. By default saves only the directly related ones.

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

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

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

:param relation_field: field with relation leading to this model :type relation_field: Optional[ForeignKeyField] :param previous_model: previous model from which method came :type previous_model: Model :param exclude: items to exclude during saving of relations :type exclude: Union[set, dict] :param relation_map: map of relations to follow :type relation_map: dict :param save_all: flag if all models should be saved or only not saved ones :type save_all: bool :param follow: flag to trigger deep save - by default only directly related models are saved with follow=True also related models of related models are saved :type follow: bool :param update_count: internal parameter for recursive calls - number of updated instances :type update_count: int :return: number of updated/saved models :rtype: int

Source code in ormar/models/model.py
Python
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
async def save_related(  # noqa: CCR001, CFQ002
    self,
    follow: bool = False,
    save_all: bool = False,
    relation_map: Optional[builtins.dict] = None,
    exclude: Union[set, builtins.dict, None] = None,
    update_count: int = 0,
    previous_model: Optional["Model"] = None,
    relation_field: Optional["ForeignKeyField"] = None,
) -> int:
    """
    Triggers a upsert method on all related models
    if the instances are not already saved.
    By default saves only the directly related ones.

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

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

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

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

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

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

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

        post_save = fields_to_visit - pre_save

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

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

    return update_count

update(_columns=None, **kwargs) async

Performs update of Model instance in the database. Fields can be updated before or you can pass them as kwargs.

Sends pre_update and post_update signals.

Sets model save status to True.

:param _columns: list of columns to update, if None all are updated :type _columns: list :raises ModelPersistenceError: If the pk column is not set

:param kwargs: list of fields to update as field=value pairs :type kwargs: Any :return: updated Model :rtype: Model

Source code in ormar/models/model.py
Python
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
async def update(self: T, _columns: Optional[list[str]] = None, **kwargs: Any) -> T:
    """
    Performs update of Model instance in the database.
    Fields can be updated before or you can pass them as kwargs.

    Sends pre_update and post_update signals.

    Sets model save status to True.

    :param _columns: list of columns to update, if None all are updated
    :type _columns: list
    :raises ModelPersistenceError: If the pk column is not set

    :param kwargs: list of fields to update as field=value pairs
    :type kwargs: Any
    :return: updated Model
    :rtype: Model
    """
    explicit_fields = self.__setattr_fields__ | kwargs.keys()
    values = self.populate_onupdate_value(
        dict(kwargs), explicit_fields=explicit_fields
    )
    if values:
        self.update_from_dict(values)

    if not self.pk:
        raise ModelPersistenceError(
            "You cannot update not saved model! Use save or upsert method."
        )

    await self._emit_signal("pre_update", instance=self, passed_args=kwargs)
    self_fields = self._extract_model_db_fields()
    self_fields.pop(self.get_column_name_from_alias(self.ormar_config.pkname))
    if _columns:
        self_fields = {
            k: v
            for k, v in self_fields.items()
            if k in _columns or k in self._onupdate_fields
        }
    if self_fields:
        self_fields = self.translate_columns_to_aliases(self_fields)
        expr = self.ormar_config.table.update().values(**self_fields)
        expr = expr.where(self.pk_column == getattr(self, self.ormar_config.pkname))
        await self._execute_query(expr)
    self.set_save_status(True)
    self.__setattr_fields__.clear()
    await self._emit_signal("post_update", instance=self)
    return self

upsert(**kwargs) async

Performs either a save or an update depending on the presence of the pk. If the pk field is filled it's an update, otherwise the save is performed. For save kwargs are ignored, used only in update if provided.

:param kwargs: list of fields to update :type kwargs: Any :return: saved Model :rtype: Model

Source code in ormar/models/model.py
Python
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
async def upsert(self: T, **kwargs: Any) -> T:
    """
    Performs either a save or an update depending on the presence of the pk.
    If the pk field is filled it's an update, otherwise the save is performed.
    For save kwargs are ignored, used only in update if provided.

    :param kwargs: list of fields to update
    :type kwargs: Any
    :return: saved Model
    :rtype: Model
    """

    force_save = kwargs.pop("__force_save__", False)
    if force_save:
        expr = self.ormar_config.table.select().where(self.pk_column == self.pk)
        row = await self._execute_query(expr, is_select=True)
        if not row:
            return await self.save()
        return await self.update(**kwargs)

    if not self.pk:
        return await self.save()
    return await self.update(**kwargs)

ModelRow

Bases: NewBaseModel

Source code in ormar/models/model_row.py
Python
 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
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
class ModelRow(NewBaseModel):
    @classmethod
    def from_row(  # noqa: CFQ002
        cls,
        row: ResultProxy,
        source_model: type["Model"],
        select_related: Optional[list] = None,
        related_models: Any = None,
        related_field: Optional["ForeignKeyField"] = None,
        excludable: Optional[ExcludableItems] = None,
        current_relation_str: str = "",
        proxy_source_model: Optional[type["Model"]] = None,
        used_prefixes: Optional[list[str]] = None,
        plan_cache: Optional[PlanCache] = None,
    ) -> Optional["Model"]:
        """
        Model method to convert raw sql row from database into ormar.Model instance.
        Traverses nested models if they were specified in select_related for query.

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

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

        if select_related:
            related_models = group_related_list(select_related)

        if related_field:
            table_prefix = cls._process_table_prefix(
                source_model=source_model,
                current_relation_str=current_relation_str,
                related_field=related_field,
                used_prefixes=used_prefixes,
            )

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

        instance: Optional["Model"] = None
        if item.get(plan.pk_field_name, None) is not None:
            instance = cast(
                "Model",
                cls._construct_with_excluded(plan.excluded_field_names, **item),
            )
            instance.set_save_status(True)
        return instance

    @classmethod
    def _process_table_prefix(
        cls,
        source_model: type["Model"],
        current_relation_str: str,
        related_field: "ForeignKeyField",
        used_prefixes: list[str],
    ) -> str:
        """

        :param source_model: model on which relation was defined
        :type source_model: type[Model]
        :param current_relation_str: current relation string
        :type current_relation_str: str
        :param related_field: field with relation declaration
        :type related_field: "ForeignKeyField"
        :param used_prefixes: list of already extracted prefixes
        :type used_prefixes: list[str]
        :return: table_prefix to use
        :rtype: str
        """
        if related_field.is_multi:
            previous_model = related_field.through
        else:
            previous_model = related_field.owner
        table_prefix = cls.ormar_config.alias_manager.resolve_relation_alias(
            from_model=previous_model, relation_name=related_field.name
        )
        if not table_prefix or table_prefix in used_prefixes:
            manager = cls.ormar_config.alias_manager
            table_prefix = manager.resolve_relation_alias_after_complex(
                source_model=source_model,
                relation_str=current_relation_str,
                relation_field=related_field,
            )
        used_prefixes.append(table_prefix)
        return table_prefix

    @classmethod
    def _populate_nested_models_from_row(  # noqa: CFQ002
        cls,
        item: dict,
        row: ResultProxy,
        source_model: type["Model"],
        related_models: Any,
        excludable: ExcludableItems,
        table_prefix: str,
        used_prefixes: list[str],
        current_relation_str: Optional[str] = None,
        proxy_source_model: Optional[type["Model"]] = None,
        plan_cache: Optional[PlanCache] = None,
    ) -> dict:
        """
        Traverses structure of related models and populates the nested models
        from the database row.
        Related models can be a list if only directly related models are to be
        populated, converted to dict if related models also have their own related
        models to be populated.

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

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

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

            relation_str, remainder = cls._process_remainder_and_relation_string(
                related_models=related_models,
                current_relation_str=current_relation_str,
                related=related,
            )
            child = model_cls.from_row(
                row,
                related_models=remainder,
                related_field=field,
                excludable=excludable,
                current_relation_str=relation_str,
                source_model=source_model,
                proxy_source_model=proxy_source_model,
                used_prefixes=used_prefixes,
                plan_cache=plan_cache,
            )
            item[model_cls.get_column_name_from_alias(related)] = child
            if (
                field.is_multi
                and child
                and not model_excludable.is_excluded(field.through.get_name())
            ):
                cls._populate_through_instance(
                    row=row,
                    item=item,
                    related=related,
                    excludable=excludable,
                    child=child,
                    proxy_source_model=proxy_source_model,
                    plan_cache=plan_cache,
                )

        return item

    @staticmethod
    def _process_remainder_and_relation_string(
        related_models: Union[dict, list],
        current_relation_str: Optional[str],
        related: str,
    ) -> tuple[str, Optional[Union[dict, list]]]:
        """
        Process remainder models and relation string

        :param related_models: list or dict of related models
        :type related_models: Union[dict, list]
        :param current_relation_str: current relation string
        :type current_relation_str: Optional[str]
        :param related: name of the relation
        :type related: str
        """
        relation_str = (
            "__".join([current_relation_str, related])
            if current_relation_str
            else related
        )

        remainder = None
        if isinstance(related_models, dict) and related_models[related]:
            remainder = related_models[related]
        return relation_str, remainder

    @classmethod
    def _populate_through_instance(  # noqa: CFQ002
        cls,
        row: ResultProxy,
        item: dict,
        related: str,
        excludable: ExcludableItems,
        child: "Model",
        proxy_source_model: Optional[type["Model"]],
        plan_cache: Optional[PlanCache] = None,
    ) -> None:
        """
        Populates the through model on reverse side of current query.
        Normally it's child class, unless the query is from queryset.

        :param row: row from db result
        :type row: ResultProxy
        :param item: parent item dict
        :type item: dict
        :param related: current relation name
        :type related: str
        :param excludable: structure of fields to include and exclude
        :type excludable: ExcludableItems
        :param child: child item of parent
        :type child: "Model"
        :param proxy_source_model: source model from which querysetproxy is constructed
        :type proxy_source_model: type["Model"]
        :param plan_cache: optional per-queryset plan cache
        :type plan_cache: Optional[PlanCache]
        """
        through_name = cls.ormar_config.model_fields[related].through.get_name()
        through_child = cls._create_through_instance(
            row=row,
            related=related,
            through_name=through_name,
            excludable=excludable,
            plan_cache=plan_cache,
        )

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

    @classmethod
    def _create_through_instance(
        cls,
        row: ResultProxy,
        through_name: str,
        related: str,
        excludable: ExcludableItems,
        plan_cache: Optional[PlanCache] = None,
    ) -> "ModelRow":
        """
        Initialize the through model from db row.
        Excluded all relation fields and other exclude/include set in excludable.

        :param row: loaded row from database
        :type row: sqlalchemy.engine.ResultProxy
        :param through_name: name of the through field
        :type through_name: str
        :param related: name of the relation
        :type related: str
        :param excludable: structure of fields to include and exclude
        :type excludable: ExcludableItems
        :param plan_cache: optional per-queryset plan cache
        :type plan_cache: Optional[PlanCache]
        :return: initialized through model without relation
        :rtype: "ModelRow"
        """
        model_cls = cls.ormar_config.model_fields[through_name].to
        table_prefix = cls.ormar_config.alias_manager.resolve_relation_alias(
            from_model=cls, relation_name=related
        )
        # remove relations on through field — must happen before the plan is
        # built so the plan reflects the through-model's full exclude set
        model_excludable = excludable.get(model_cls=model_cls, alias=table_prefix)
        model_excludable.set_values(
            value=model_cls.extract_related_names(), slot="exclude"
        )
        plan = model_cls.get_or_build_row_plan(table_prefix, excludable, plan_cache)
        child_dict = model_cls.apply_row_plan(plan, row, {})
        child = model_cls._construct_with_excluded(  # type: ignore
            plan.excluded_field_names, **child_dict
        )
        return child

    @classmethod
    def build_row_extraction_plan(
        cls,
        table_prefix: str,
        excludable: ExcludableItems,
    ) -> RowExtractionPlan:
        """
        Compute the per-row extraction plan for a ``(cls, table_prefix,
        excludable)`` triple — the work that previously ran inside
        ``extract_prefixed_table_columns`` for every row.

        :param table_prefix: prefix of the table from AliasManager
        :type table_prefix: str
        :param excludable: structure of fields to include and exclude
        :type excludable: ExcludableItems
        :return: cacheable plan for fast per-row extraction
        :rtype: RowExtractionPlan
        """
        selected_columns = set(
            cls.own_table_columns(
                model=cls, excludable=excludable, alias=table_prefix, use_alias=False
            )
        )
        column_prefix = table_prefix + "_" if table_prefix else ""
        column_pairs = cls._get_table_column_pairs(cls)
        mappings = tuple(
            (f"{column_prefix}{col_name}", field_name)
            for col_name, field_name in column_pairs
            if field_name in selected_columns
        )
        excluded = frozenset(
            cls.get_names_to_exclude(excludable=excludable, alias=table_prefix)
        )
        return RowExtractionPlan(
            column_mappings=mappings,
            excluded_field_names=excluded,
            pk_field_name=cls.ormar_config.pkname,
        )

    @classmethod
    def get_or_build_row_plan(
        cls,
        table_prefix: str,
        excludable: ExcludableItems,
        plan_cache: Optional[PlanCache],
    ) -> RowExtractionPlan:
        """
        Return a cached plan for the given key, or build and cache one. When
        ``plan_cache`` is ``None`` (legacy / external caller) the plan is
        built fresh on every call so behavior matches the pre-cache shape.

        :param table_prefix: prefix of the table from AliasManager
        :type table_prefix: str
        :param excludable: structure of fields to include and exclude
        :type excludable: ExcludableItems
        :param plan_cache: per-queryset cache keyed by
            ``(cls, table_prefix, id(excludable))``; ``None`` to bypass
        :type plan_cache: Optional[PlanCache]
        :return: extraction plan for this row position
        :rtype: RowExtractionPlan
        """
        if plan_cache is None:
            return cls.build_row_extraction_plan(table_prefix, excludable)
        key = (cls, table_prefix, id(excludable))
        plan = plan_cache.get(key)
        if plan is None:
            plan = cls.build_row_extraction_plan(table_prefix, excludable)
            plan_cache[key] = plan
        return plan

    @staticmethod
    def apply_row_plan(
        plan: RowExtractionPlan,
        row: ResultProxy,
        item: dict,
    ) -> dict:
        """
        Populate ``item`` from ``row`` using ``plan.column_mappings``. Skips
        keys already present so a partially populated dict (e.g. from
        ``_populate_nested_models_from_row``) is not overwritten.

        :param plan: precomputed extraction plan
        :type plan: RowExtractionPlan
        :param row: raw result row from the database
        :type row: sqlalchemy.engine.result.ResultProxy
        :param item: dict to populate in place
        :type item: dict
        :return: ``item`` (returned for chaining symmetry with the legacy API)
        :rtype: dict
        """
        for prefixed_name, field_name in plan.column_mappings:
            if field_name not in item:
                item[field_name] = row[prefixed_name]
        return item

apply_row_plan(plan, row, item) staticmethod

Populate item from row using plan.column_mappings. Skips keys already present so a partially populated dict (e.g. from _populate_nested_models_from_row) is not overwritten.

:param plan: precomputed extraction plan :type plan: RowExtractionPlan :param row: raw result row from the database :type row: sqlalchemy.engine.result.ResultProxy :param item: dict to populate in place :type item: dict :return: item (returned for chaining symmetry with the legacy API) :rtype: dict

Source code in ormar/models/model_row.py
Python
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
@staticmethod
def apply_row_plan(
    plan: RowExtractionPlan,
    row: ResultProxy,
    item: dict,
) -> dict:
    """
    Populate ``item`` from ``row`` using ``plan.column_mappings``. Skips
    keys already present so a partially populated dict (e.g. from
    ``_populate_nested_models_from_row``) is not overwritten.

    :param plan: precomputed extraction plan
    :type plan: RowExtractionPlan
    :param row: raw result row from the database
    :type row: sqlalchemy.engine.result.ResultProxy
    :param item: dict to populate in place
    :type item: dict
    :return: ``item`` (returned for chaining symmetry with the legacy API)
    :rtype: dict
    """
    for prefixed_name, field_name in plan.column_mappings:
        if field_name not in item:
            item[field_name] = row[prefixed_name]
    return item

build_row_extraction_plan(table_prefix, excludable) classmethod

Compute the per-row extraction plan for a (cls, table_prefix, excludable) triple — the work that previously ran inside extract_prefixed_table_columns for every row.

:param table_prefix: prefix of the table from AliasManager :type table_prefix: str :param excludable: structure of fields to include and exclude :type excludable: ExcludableItems :return: cacheable plan for fast per-row extraction :rtype: RowExtractionPlan

Source code in ormar/models/model_row.py
Python
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
409
410
@classmethod
def build_row_extraction_plan(
    cls,
    table_prefix: str,
    excludable: ExcludableItems,
) -> RowExtractionPlan:
    """
    Compute the per-row extraction plan for a ``(cls, table_prefix,
    excludable)`` triple — the work that previously ran inside
    ``extract_prefixed_table_columns`` for every row.

    :param table_prefix: prefix of the table from AliasManager
    :type table_prefix: str
    :param excludable: structure of fields to include and exclude
    :type excludable: ExcludableItems
    :return: cacheable plan for fast per-row extraction
    :rtype: RowExtractionPlan
    """
    selected_columns = set(
        cls.own_table_columns(
            model=cls, excludable=excludable, alias=table_prefix, use_alias=False
        )
    )
    column_prefix = table_prefix + "_" if table_prefix else ""
    column_pairs = cls._get_table_column_pairs(cls)
    mappings = tuple(
        (f"{column_prefix}{col_name}", field_name)
        for col_name, field_name in column_pairs
        if field_name in selected_columns
    )
    excluded = frozenset(
        cls.get_names_to_exclude(excludable=excludable, alias=table_prefix)
    )
    return RowExtractionPlan(
        column_mappings=mappings,
        excluded_field_names=excluded,
        pk_field_name=cls.ormar_config.pkname,
    )

from_row(row, source_model, select_related=None, related_models=None, related_field=None, excludable=None, current_relation_str='', proxy_source_model=None, used_prefixes=None, plan_cache=None) classmethod

Model method to convert raw sql row from database into ormar.Model instance. Traverses nested models if they were specified in select_related for query.

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

:param used_prefixes: list of already extracted prefixes :type used_prefixes: list[str] :param proxy_source_model: source model from which querysetproxy is constructed :type proxy_source_model: Optional[type["ModelRow"]] :param excludable: structure of fields to include and exclude :type excludable: ExcludableItems :param current_relation_str: name of the relation field :type current_relation_str: str :param source_model: model on which relation was defined :type source_model: type[Model] :param row: raw result row from the database :type row: ResultProxy :param select_related: list of names of related models fetched from database :type select_related: list :param related_models: list or dict of related models :type related_models: Union[list, dict] :param related_field: field with relation declaration :type related_field: ForeignKeyField :return: returns model if model is populated from database :rtype: Optional[Model]

Source code in ormar/models/model_row.py
Python
 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
@classmethod
def from_row(  # noqa: CFQ002
    cls,
    row: ResultProxy,
    source_model: type["Model"],
    select_related: Optional[list] = None,
    related_models: Any = None,
    related_field: Optional["ForeignKeyField"] = None,
    excludable: Optional[ExcludableItems] = None,
    current_relation_str: str = "",
    proxy_source_model: Optional[type["Model"]] = None,
    used_prefixes: Optional[list[str]] = None,
    plan_cache: Optional[PlanCache] = None,
) -> Optional["Model"]:
    """
    Model method to convert raw sql row from database into ormar.Model instance.
    Traverses nested models if they were specified in select_related for query.

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

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

    if select_related:
        related_models = group_related_list(select_related)

    if related_field:
        table_prefix = cls._process_table_prefix(
            source_model=source_model,
            current_relation_str=current_relation_str,
            related_field=related_field,
            used_prefixes=used_prefixes,
        )

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

    instance: Optional["Model"] = None
    if item.get(plan.pk_field_name, None) is not None:
        instance = cast(
            "Model",
            cls._construct_with_excluded(plan.excluded_field_names, **item),
        )
        instance.set_save_status(True)
    return instance

get_or_build_row_plan(table_prefix, excludable, plan_cache) classmethod

Return a cached plan for the given key, or build and cache one. When plan_cache is None (legacy / external caller) the plan is built fresh on every call so behavior matches the pre-cache shape.

:param table_prefix: prefix of the table from AliasManager :type table_prefix: str :param excludable: structure of fields to include and exclude :type excludable: ExcludableItems :param plan_cache: per-queryset cache keyed by (cls, table_prefix, id(excludable)); None to bypass :type plan_cache: Optional[PlanCache] :return: extraction plan for this row position :rtype: RowExtractionPlan

Source code in ormar/models/model_row.py
Python
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
@classmethod
def get_or_build_row_plan(
    cls,
    table_prefix: str,
    excludable: ExcludableItems,
    plan_cache: Optional[PlanCache],
) -> RowExtractionPlan:
    """
    Return a cached plan for the given key, or build and cache one. When
    ``plan_cache`` is ``None`` (legacy / external caller) the plan is
    built fresh on every call so behavior matches the pre-cache shape.

    :param table_prefix: prefix of the table from AliasManager
    :type table_prefix: str
    :param excludable: structure of fields to include and exclude
    :type excludable: ExcludableItems
    :param plan_cache: per-queryset cache keyed by
        ``(cls, table_prefix, id(excludable))``; ``None`` to bypass
    :type plan_cache: Optional[PlanCache]
    :return: extraction plan for this row position
    :rtype: RowExtractionPlan
    """
    if plan_cache is None:
        return cls.build_row_extraction_plan(table_prefix, excludable)
    key = (cls, table_prefix, id(excludable))
    plan = plan_cache.get(key)
    if plan is None:
        plan = cls.build_row_extraction_plan(table_prefix, excludable)
        plan_cache[key] = plan
    return plan

NewBaseModel

Bases: BaseModel, ModelTableProxy

Main base class of ormar Model. Inherits from pydantic BaseModel and has all mixins combined in ModelTableProxy. Constructed with ModelMetaclass which in turn also inherits pydantic metaclass.

Abstracts away all internals and helper functions, so final Model class has only the logic concerned with database connection and data persistence.

Source code in ormar/models/newbasemodel.py
Python
  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
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass):
    """
    Main base class of ormar Model.
    Inherits from pydantic BaseModel and has all mixins combined in ModelTableProxy.
    Constructed with ModelMetaclass which in turn also inherits pydantic metaclass.

    Abstracts away all internals and helper functions, so final Model class has only
    the logic concerned with database connection and data persistence.
    """

    __slots__ = (
        "_orm_id",
        "_orm_saved",
        "_orm",
        "_pk_column",
        "__pk_only__",
        "__ormar_excludable__",
        "__cached_hash__",
        "__pydantic_extra__",
        "__pydantic_fields_set__",
        "__setattr_fields__",
    )

    if TYPE_CHECKING:  # pragma no cover
        pk: Any
        __relation_map__: Optional[list[str]]
        __cached_hash__: Optional[int]
        __setattr_fields__: set[str]
        _orm_relationship_manager: AliasManager
        _orm: RelationsManager
        _orm_id: int
        _orm_saved: bool
        _related_names: Optional[set]
        _through_names: Optional[set]
        _related_names_hash: str
        _quick_access_fields: set
        _json_fields: set
        _bytes_fields: set
        _onupdate_fields: set
        _pydantic_field_names: Optional[frozenset[str]]
        _extra_is_ignore: Optional[bool]
        _allowed_kwarg_names: Optional[frozenset[str]]
        _relation_field_names: Optional[frozenset[str]]
        ormar_config: OrmarConfig

    # noinspection PyMissingConstructor
    def __init__(self, *args: Any, **kwargs: Any) -> None:  # type: ignore
        """
        Initializer that creates a new ormar Model that is also pydantic Model at the
        same time.

        Passed keyword arguments can be only field names and their corresponding values
        as those will be passed to pydantic validation that will complain if extra
        params are passed.

        If relations are defined each relation is expanded and children models are also
        initialized and validated. Relation from both sides is registered so you can
        access related models from both sides.

        Json fields are automatically loaded/dumped if needed.

        Models marked as abstract=True in internal OrmarConfig cannot be initialized.

        :raises ModelError: if abstract model is initialized, model has ForwardRefs
         that has not been updated or unknown field is passed
        :param args: ignored args
        :type args: Any
        :param kwargs: keyword arguments - all fields values and some special params
        :type kwargs: Any
        """
        self._verify_model_can_be_initialized()
        self._initialize_internal_attributes()

        object.__setattr__(self, "__pk_only__", False)
        object.__setattr__(self, "__ormar_excludable__", None)

        new_kwargs, through_tmp_dict = self._process_kwargs(kwargs)

        new_kwargs = self.serialize_nested_models_json_fields(new_kwargs)
        self.__pydantic_validator__.validate_python(
            new_kwargs,
            self_instance=self,  # type: ignore
        )
        self._register_related_models(new_kwargs, through_tmp_dict)

    @classmethod
    def _internal_construct(
        cls,
        _pk_only: bool = False,
        _excluded: Optional[set[str]] = None,
        **kwargs: Any,
    ) -> "NewBaseModel":
        """
        Internal-only factory for constructing model instances with pk_only or
        excluded support. Not reachable from user-supplied kwargs or JSON
        deserialization.

        :param _pk_only: if True, skip validation and set only pk field
        :type _pk_only: bool
        :param _excluded: set of field names to explicitly set to None
        :type _excluded: Optional[set[str]]
        :param kwargs: field values for the model
        :type kwargs: Any
        :return: constructed model instance
        :rtype: NewBaseModel
        """
        instance = cls.__new__(cls)
        instance._verify_model_can_be_initialized()
        instance._initialize_internal_attributes()
        object.__setattr__(instance, "__pk_only__", _pk_only)
        object.__setattr__(instance, "__ormar_excludable__", None)

        new_kwargs, through_tmp_dict = instance._process_kwargs(kwargs)

        if _excluded:
            for field_to_nullify in _excluded:
                new_kwargs[field_to_nullify] = None

        if not _pk_only:
            new_kwargs = instance.serialize_nested_models_json_fields(new_kwargs)
            instance.__pydantic_validator__.validate_python(
                new_kwargs,
                self_instance=instance,  # type: ignore
            )
        else:
            fields_set = {instance.ormar_config.pkname}
            object.__setattr__(instance, "__dict__", new_kwargs)
            object.__setattr__(instance, "__pydantic_fields_set__", fields_set)

        instance._register_related_models(new_kwargs, through_tmp_dict)
        return instance

    def _register_related_models(
        self, new_kwargs: dict[str, Any], through_tmp_dict: dict[str, Any]
    ) -> None:
        """
        Adds back through fields and registers related models after initialization.

        :param new_kwargs: processed keyword arguments with field values
        :type new_kwargs: dict[str, Any]
        :param through_tmp_dict: through model fields extracted during processing
        :type through_tmp_dict: dict[str, Any]
        """
        new_kwargs.update(through_tmp_dict)
        model_fields = object.__getattribute__(self, "ormar_config").model_fields
        for related in self.extract_related_names().union(self.extract_through_names()):
            model_fields[related].expand_relationship(
                new_kwargs.get(related), self, to_register=True
            )

    @classmethod
    def _construct_with_excluded(
        cls, excluded: AbstractSet[str], **kwargs: Any
    ) -> typing_extensions.Self:
        """
        Constructs model instance and nullifies excluded fields post-construction.
        Used when loading partial results from the database.

        :param excluded: collection of field names to nullify after construction
        :type excluded: AbstractSet[str]
        :param kwargs: field values for the model
        :type kwargs: Any
        :return: constructed model instance
        :rtype: Self
        """
        instance = cls(**kwargs)
        for field_to_nullify in excluded:
            instance.__dict__[field_to_nullify] = None
        return instance

    def __setattr__(self, name: str, value: Any) -> None:  # noqa CCR001
        """
        Overwrites setattr in pydantic parent as otherwise descriptors are not called.

        :param name: name of the attribute to set
        :type name: str
        :param value: value of the attribute to set
        :type value: Any
        :return: None
        :rtype: None
        """
        prev_hash = hash(self)

        if hasattr(self, name):
            object.__setattr__(self, name, value)
        else:
            # let pydantic handle errors for unknown fields
            super().__setattr__(name, value)

        if self._onupdate_fields and name in self.ormar_config.model_fields:
            self.__setattr_fields__.add(name)

        # In this case, the hash could have changed, so update it
        if name == self.ormar_config.pkname or self.pk is None:
            object.__setattr__(self, "__cached_hash__", None)
            new_hash = hash(self)

            if prev_hash != new_hash:
                self._update_relation_cache(prev_hash, new_hash)

    def __getattr__(self, item: str) -> Any:
        """
        Used for private attributes of pydantic v2.

        :param item: name of attribute
        :type item: str
        :return: Any
        :rtype: Any
        """
        # TODO: Check __pydantic_extra__
        if item == "__pydantic_extra__":
            return None
        return super().__getattr__(item)  # type: ignore

    def __getstate__(self) -> dict[Any, Any]:
        state = super().__getstate__()
        self_dict = self.model_dump()
        state["__dict__"].update(**self_dict)
        return state

    def __setstate__(self, state: dict[Any, Any]) -> None:
        relations = {
            k: v
            for k, v in state["__dict__"].items()
            if k in self.extract_related_names()
        }
        basic_state = {
            k: v
            for k, v in state["__dict__"].items()
            if k not in self.extract_related_names()
        }
        state["__dict__"] = basic_state
        super().__setstate__(state)
        self._initialize_internal_attributes()
        for name, value in relations.items():
            setattr(self, name, value)

    def _update_relation_cache(self, prev_hash: int, new_hash: int) -> None:
        """
        Update all relation proxy caches with different hash if we have changed

        :param prev_hash: The previous hash to update
        :type prev_hash: int
        :param new_hash: The hash to update to
        :type new_hash: int
        """

        def _update_cache(relations: list[Relation], recurse: bool = True) -> None:
            for relation in relations:
                # Read ``related_models`` directly (rather than calling
                # ``relation.get()``) so an un-materialized reverse/m2m
                # proxy stays un-materialized — there is nothing in an
                # empty proxy to migrate hashes for.
                relation_proxy = relation.related_models

                if hasattr(relation_proxy, "update_cache"):
                    relation_proxy.update_cache(prev_hash, new_hash)  # type: ignore
                elif recurse and hasattr(relation_proxy, "_orm"):
                    _update_cache(
                        relation_proxy._orm._relations.values(),  # type: ignore
                        recurse=False,
                    )

        _update_cache(list(self._orm._relations.values()))

    def _internal_set(self, name: str, value: Any) -> None:
        """
        Delegates call to pydantic.

        :param name: name of param
        :type name: str
        :param value: value to set
        :type value: Any
        """
        super().__setattr__(name, value)

    def _verify_model_can_be_initialized(self) -> None:
        """
        Raises exception if model is abstract or has ForwardRefs in relation fields.

        :return: None
        :rtype: None
        """
        if self.ormar_config.abstract:
            raise ModelError(f"You cannot initialize abstract model {self.get_name()}")
        if self.ormar_config.requires_ref_update:
            raise ModelError(
                f"Model {self.get_name()} has not updated "
                f"ForwardRefs. \nBefore using the model you "
                f"need to call update_forward_refs()."
            )

    def _process_kwargs(self, kwargs: dict) -> tuple[dict, dict]:  # noqa: CCR001
        """
        Initializes nested models.

        Removes property_fields

        Checks if field is in the model fields or pydantic fields.

        Extracts through models from kwargs into temporary dict.

        :param kwargs: passed to init keyword arguments
        :type kwargs: dict
        :return: modified kwargs
        :rtype: tuple[dict, dict]
        """
        cls = type(self)
        config = cls.ormar_config
        model_fields = config.model_fields

        pydantic_fields = cls._pydantic_field_names
        if pydantic_fields is None:
            pydantic_fields = frozenset(cls.model_fields.keys())
            cls._pydantic_field_names = pydantic_fields

        # remove property fields
        for prop_field in config.property_fields:
            kwargs.pop(prop_field, None)

        if "pk" in kwargs:
            kwargs[config.pkname] = kwargs.pop("pk")

        # extract through fields
        through_tmp_dict = {
            field_name: kwargs.pop(field_name, None)
            for field_name in self.extract_through_names()
        }

        extra_is_ignore = cls._extra_is_ignore
        if extra_is_ignore is None:
            extra_is_ignore = config.extra == Extra.ignore
            cls._extra_is_ignore = extra_is_ignore

        if extra_is_ignore:
            allowed = cls._allowed_kwarg_names
            if allowed is None:
                allowed = frozenset(model_fields.keys()) | pydantic_fields
                cls._allowed_kwarg_names = allowed
            kwargs = {k: v for k, v in kwargs.items() if k in allowed}

        json_fields = cls._json_fields
        bytes_fields = cls._bytes_fields

        # ``relation_field_names`` is the disjoint set of fields that need
        # ``expand_relationship``; everything else can skip that call. Cached
        # on the class on first access — same pattern as ``_pydantic_field_names``.
        relation_field_names = getattr(cls, "_relation_field_names", None)
        if relation_field_names is None:
            relation_field_names = frozenset(
                name for name, f in model_fields.items() if f.is_relation
            )
            cls._relation_field_names = relation_field_names  # type: ignore[attr-defined]

        has_json = bool(json_fields)
        has_bytes = bool(bytes_fields)
        has_relations = bool(relation_field_names)

        # Validate unknown kwargs up front so the dispatch loop doesn't need
        # a per-iteration check. ``extra=ignore`` already filtered above, so
        # in that branch no unknowns can remain.
        if not extra_is_ignore:
            for k in kwargs:
                if k not in model_fields and k not in pydantic_fields:
                    try:
                        model_fields[k]
                    except KeyError as e:
                        raise ModelError(
                            f"Unknown field '{e.args[0]}' for model "
                            f"{self.get_name(lower=False)}"
                        )

        if not has_json and not has_bytes and not has_relations:
            # Fast path — plain model with no relations/json/bytes. The
            # validation pass above is the only per-key cost; the value
            # copy is a single C-level dict construction.
            return dict(kwargs), through_tmp_dict

        new_kwargs: dict[str, Any] = {}
        for k, v in kwargs.items():
            if k in relation_field_names:
                v = model_fields[k].expand_relationship(v, self, to_register=False)
            if has_json and k in json_fields:
                v = encode_json(v)
            if has_bytes and k in bytes_fields and v is not None:
                v = decode_bytes(
                    value=v,
                    represent_as_string=model_fields[k].represent_as_base64_str,
                )
            new_kwargs[k] = v
        return new_kwargs, through_tmp_dict

    def _initialize_internal_attributes(self) -> None:
        """
        Initializes internal attributes during __init__()
        :rtype: None
        """
        # object.__setattr__(self, "_orm_id", uuid.uuid4().hex)
        object.__setattr__(self, "_orm_saved", False)
        object.__setattr__(self, "_pk_column", None)
        object.__setattr__(self, "__setattr_fields__", set())
        object.__setattr__(
            self,
            "_orm",
            RelationsManager(
                related_fields=self.extract_related_fields(), owner=cast("Model", self)
            ),
        )

    def __eq__(self, other: object) -> bool:
        """
        Compares other model to this model. when == is called.
        :param other: other model to compare
        :type other: object
        :return: result of comparison
        :rtype: bool
        """
        if isinstance(other, NewBaseModel):
            return self.__same__(other)
        return super().__eq__(other)  # pragma no cover

    def __hash__(self) -> int:
        cached = getattr(self, "__cached_hash__", None)
        if cached is not None:
            return cached

        pk = self.pk
        cls = type(self)
        if pk is not None:
            # ``type(self)`` hashes by identity in CPython, so ``hash((pk, cls))``
            # is uniqueness-equivalent to the original ``str(pk) + cls.__name__``
            # without two string allocations per call. This is the hot path —
            # everything that goes through ``_relation_cache`` is keyed on
            # saved-pk Models.
            ret = hash((pk, cls))
        else:
            # Unsaved models can hold list/dict values in ``__dict__`` (json
            # fields, reverse-relation slots), so we still ``str(vals)`` to
            # keep the result hashable. Cold path; not perf-critical.
            related = self.extract_related_names()
            vals = {k: v for k, v in self.__dict__.items() if k not in related}
            ret = hash((str(vals), cls))

        object.__setattr__(self, "__cached_hash__", ret)
        return ret

    def __same__(self, other: "NewBaseModel") -> bool:
        """
        Used by __eq__, compares other model to this model.

        Saved models (both with pk) compare directly by ``(pk, type)`` to
        skip the hash-cache fill on the *other* side. Unsaved/mixed states
        fall through to the original hash-equality semantics.

        :param other: model to compare to
        :type other: NewBaseModel
        :return: result of comparison
        :rtype: bool
        """
        if type(self) is not type(other):
            return False  # pragma: no cover
        self_pk = self.pk
        other_pk = other.pk
        if self_pk is not None and other_pk is not None:
            return self_pk == other_pk
        if (self_pk is None) != (other_pk is None):
            return False
        else:
            return hash(self) == other.__hash__()

    @classmethod
    def get_name(cls, lower: bool = True) -> str:
        """
        Returns name of the Model class, by default lowercase.

        :param lower: flag if name should be set to lowercase
        :type lower: bool
        :return: name of the model
        :rtype: str
        """
        if lower:
            try:
                return cls._lower_name  # type: ignore[attr-defined]
            except AttributeError:
                cls._lower_name = cls.__name__.lower()  # type: ignore[attr-defined]
                return cls._lower_name  # type: ignore[attr-defined]
        return cls.__name__

    @property
    def pk_column(self) -> sqlalchemy.Column:
        """
        Retrieves primary key sqlalchemy column from models OrmarConfig.table.
        Each model has to have primary key.
        Only one primary key column is allowed.

        :return: primary key sqlalchemy column
        :rtype: sqlalchemy.Column
        """
        if object.__getattribute__(self, "_pk_column") is not None:
            return object.__getattribute__(self, "_pk_column")
        pk_columns = self.ormar_config.table.primary_key.columns.values()
        pk_col = pk_columns[0]
        object.__setattr__(self, "_pk_column", pk_col)
        return pk_col

    @property
    def saved(self) -> bool:
        """Saved status of the model. Changed by setattr and loading from db"""
        return self._orm_saved

    @property
    def signals(self) -> "SignalEmitter":
        """Exposes signals from model OrmarConfig"""
        return self.ormar_config.signals

    @classmethod
    def pk_type(cls) -> Any:
        """Shortcut to models primary key field type"""
        return cls.ormar_config.model_fields[cls.ormar_config.pkname].__type__

    @classmethod
    def db_backend_name(cls) -> str:
        """Shortcut to database dialect,
        cause some dialect require different treatment"""
        return cls.ormar_config.database.dialect.name

    def remove(self, parent: "Model", name: str) -> None:
        """Removes child from relation with given name in RelationshipManager"""
        self._orm.remove_parent(self, parent, name)

    def set_save_status(self, status: bool) -> None:
        """Sets value of the save status"""
        object.__setattr__(self, "_orm_saved", status)

    @classmethod
    def update_forward_refs(cls, **localns: Any) -> None:
        """
        Processes fields that are ForwardRef and need to be evaluated into actual
        models.

        Expands relationships, register relation in alias manager and substitutes
        sqlalchemy columns with new ones with proper column type (null before).

        Populates OrmarConfig table of the Model which is left empty before.

        Sets self_reference flag on models that links to themselves.

        Calls the pydantic method to evaluate pydantic fields.

        :param localns: local namespace
        :type localns: Any
        :return: None
        :rtype: None
        """
        globalns = sys.modules[cls.__module__].__dict__.copy()
        globalns.setdefault(cls.__name__, cls)
        fields_to_check = cls.ormar_config.model_fields.copy()
        for field in fields_to_check.values():
            if field.has_unresolved_forward_refs():
                field = cast(ForeignKeyField, field)
                field.evaluate_forward_ref(globalns=globalns, localns=localns)
                field.set_self_reference_flag()
                if field.is_multi and not field.through:
                    field = cast(ormar.ManyToManyField, field)
                    field.create_default_through_model()
                expand_reverse_relationship(model_field=field)
                register_relation_in_alias_manager(field=field)
                update_column_definition(model=cls, field=field)
        populate_config_sqlalchemy_table_if_required(config=cls.ormar_config)
        # super().update_forward_refs(**localns)
        cls.model_rebuild(
            force=True,
            _types_namespace={
                field.to.__name__: field.to
                for field in fields_to_check.values()
                if field.is_relation
            },
        )
        cls.ormar_config.requires_ref_update = False

    @staticmethod
    def _extract_nested_models_from_list(  # noqa: CFQ002
        relation_map: builtins.dict,
        models: MutableSequence,
        include: Union[set, builtins.dict, None],
        exclude: Union[set, builtins.dict, None],
        exclude_primary_keys: bool,
        exclude_through_models: bool,
        flatten_map: Optional[FlattenMap] = None,
    ) -> list:
        """
        Converts list of models into list of dictionaries.

        :param models: list of models
        :type models: list
        :param include: fields to include
        :type include: Union[set, dict, None]
        :param exclude: fields to exclude
        :type exclude: Union[set, dict, None]
        :param flatten_map: FlattenMap of relations to render as pk values
        :type flatten_map: Optional[FlattenMap]
        :return: list of models converted to dictionaries
        :rtype: list[dict]
        """
        result = []
        for model in models:
            try:
                model_dict = model.model_dump(
                    relation_map=relation_map,
                    include=include,
                    exclude=exclude,
                    exclude_primary_keys=exclude_primary_keys,
                    exclude_through_models=exclude_through_models,
                    flatten_fields=flatten_map,
                )
                if not exclude_through_models:
                    model.populate_through_models(
                        model=model,
                        model_dict=model_dict,
                        include=include,
                        exclude=exclude,
                        relation_map=relation_map,
                    )
                result.append(model_dict)
            except ReferenceError:  # pragma no cover
                continue
        return result

    @staticmethod
    def populate_through_models(
        model: "Model",
        model_dict: builtins.dict,
        include: Union[set, builtins.dict],
        exclude: Union[set, builtins.dict],
        relation_map: builtins.dict,
    ) -> None:
        """
        Populates through models with values from dict representation.

        :param model: model to populate through models
        :type model: Model
        :param model_dict: dict representation of the model
        :type model_dict: dict
        :param include: fields to include
        :type include: dict
        :param exclude: fields to exclude
        :type exclude: dict
        :param relation_map: map of relations to follow to avoid circular refs
        :type relation_map: dict
        :return: None
        :rtype: None
        """

        models_to_populate = filter_not_excluded_fields(
            fields=model.extract_through_names(),
            include=normalize_to_dict(include),
            exclude=normalize_to_dict(exclude),
        )
        through_fields_to_populate = [
            model.ormar_config.model_fields[through_model]
            for through_model in models_to_populate
            if model.ormar_config.model_fields[through_model].related_name
            not in relation_map
        ]
        for through_field in through_fields_to_populate:
            through_instance = getattr(model, through_field.name)
            if through_instance:
                model_dict[through_field.name] = through_instance.model_dump()

    @staticmethod
    def _resolve_field_descent(  # noqa: CFQ002
        field: str,
        nested_model: Any,
        flatten_map: Optional[FlattenMap],
        exclude_list: bool,
        relation_map: builtins.dict,
        include: Optional[builtins.dict],
        exclude: Optional[builtins.dict],
        dict_instance: builtins.dict,
    ) -> Optional[NestedDescent]:
        """
        Decide how to dump a related field. When the field is flattened, write
        its primary-key representation directly into ``dict_instance`` and
        return ``None`` so the caller skips further descent. When the field is
        an excluded list, also return ``None``. Otherwise return a
        ``NestedDescent`` carrying the per-field locals the caller threads
        into the recursive dump.

        :param field: relation field name being processed
        :type field: str
        :param nested_model: value of the relation on the current instance
        :type nested_model: Any
        :param flatten_map: current flatten directive scope, or ``None``
        :type flatten_map: Optional[FlattenMap]
        :param exclude_list: whether to skip list-valued relations entirely
        :type exclude_list: bool
        :param relation_map: relation traversal map for the current scope
        :type relation_map: dict
        :param include: include selector for the current scope
        :type include: Optional[dict]
        :param exclude: exclude selector for the current scope
        :type exclude: Optional[dict]
        :param dict_instance: dict being built for the current model
        :type dict_instance: dict
        :return: per-field locals, or ``None`` if the field was handled inline
            (flattened or skipped)
        :rtype: Optional[NestedDescent]
        """
        is_sequence = isinstance(nested_model, MutableSequence)
        if is_sequence and exclude_list:
            return None
        if flatten_map is not None and flatten_map.is_field_flattened(field):
            if is_sequence:
                dict_instance[field] = [m.pk for m in nested_model]
            else:
                dict_instance[field] = (
                    nested_model.pk if nested_model is not None else None
                )
            return None
        return NestedDescent(
            flatten_map=flatten_map.descend(field) if flatten_map is not None else None,
            relation_map=cast(
                builtins.dict, skip_ellipsis(relation_map, field, default=dict())
            ),
            include=convert_all(skip_ellipsis(include, field)),
            exclude=convert_all(skip_ellipsis(exclude, field)),
        )

    def _extract_nested_models(  # noqa: CCR001, CFQ002
        self,
        relation_map: builtins.dict,
        dict_instance: builtins.dict,
        include: Optional[builtins.dict],
        exclude: Optional[builtins.dict],
        exclude_primary_keys: bool,
        exclude_through_models: bool,
        exclude_list: bool,
        flatten_map: Optional[FlattenMap] = None,
    ) -> builtins.dict:
        """
        Traverse nested models and converts them into dictionaries.
        Calls itself recursively if needed.

        :param relation_map: map of the relations to follow to avoid circular deps
        :type relation_map: dict
        :param dict_instance: current instance dict
        :type dict_instance: dict
        :param include: fields to include
        :type include: Optional[dict]
        :param exclude: fields to exclude
        :type exclude: Optional[dict]
        :param exclude_list: whether to exclude list-valued relations
        :type exclude_list: bool
        :param flatten_map: FlattenMap directing relations to render as pk values
        :type flatten_map: Optional[FlattenMap]
        :return: current model dict with child models converted to dictionaries
        :rtype: dict
        """
        fields = filter_not_excluded_fields(
            fields=self.extract_related_names(), include=include, exclude=exclude
        )

        for field in fields:
            if not relation_map or field not in relation_map:
                continue
            try:
                nested_model = getattr(self, field)
                descent = self._resolve_field_descent(
                    field=field,
                    nested_model=nested_model,
                    flatten_map=flatten_map,
                    exclude_list=exclude_list,
                    relation_map=relation_map,
                    include=include,
                    exclude=exclude,
                    dict_instance=dict_instance,
                )
                if descent is None:
                    continue
                if isinstance(nested_model, MutableSequence):
                    dict_instance[field] = self._extract_nested_models_from_list(
                        relation_map=descent.relation_map,
                        models=nested_model,
                        include=descent.include,
                        exclude=descent.exclude,
                        exclude_primary_keys=exclude_primary_keys,
                        exclude_through_models=exclude_through_models,
                        flatten_map=descent.flatten_map,
                    )
                elif nested_model is not None:
                    model_dict = nested_model.model_dump(
                        relation_map=descent.relation_map,
                        include=descent.include,
                        exclude=descent.exclude,
                        exclude_primary_keys=exclude_primary_keys,
                        exclude_through_models=exclude_through_models,
                        flatten_fields=descent.flatten_map,
                    )
                    if not exclude_through_models:
                        nested_model.populate_through_models(
                            model=nested_model,
                            model_dict=model_dict,
                            include=descent.include,
                            exclude=descent.exclude,
                            relation_map=descent.relation_map,
                        )
                    dict_instance[field] = model_dict
                else:
                    dict_instance[field] = None
            except ReferenceError:  # pragma: no cover
                dict_instance[field] = None
        return dict_instance

    @typing_extensions.deprecated(
        "The `dict` method is deprecated; use `model_dump` instead.",
        category=OrmarDeprecatedSince020,
    )
    def dict(  # type: ignore # noqa A003
        self,
        *,
        include: Union[set, builtins.dict, None] = None,
        exclude: Union[set, builtins.dict, None] = None,
        by_alias: bool = False,
        exclude_unset: bool = False,
        exclude_defaults: bool = False,
        exclude_none: bool = False,
        exclude_primary_keys: bool = False,
        exclude_through_models: bool = False,
        exclude_list: bool = False,
        relation_map: Optional[builtins.dict] = None,
    ) -> "DictStrAny":  # noqa: A003 # pragma: no cover
        warnings.warn(
            "The `dict` method is deprecated; use `model_dump` instead.",
            DeprecationWarning,
        )
        return self.model_dump(
            include=include,
            exclude=exclude,
            by_alias=by_alias,
            exclude_unset=exclude_unset,
            exclude_defaults=exclude_defaults,
            exclude_none=exclude_none,
            exclude_primary_keys=exclude_primary_keys,
            exclude_through_models=exclude_through_models,
            exclude_list=exclude_list,
            relation_map=relation_map,
        )

    def model_dump(  # type: ignore # noqa A003, CFQ002
        self,
        *,
        mode: Union[Literal["json", "python"], str] = "python",
        include: Union[set, builtins.dict, None] = None,
        exclude: Union[set, builtins.dict, None] = None,
        by_alias: bool = False,
        exclude_unset: bool = False,
        exclude_defaults: bool = False,
        exclude_none: bool = False,
        exclude_primary_keys: bool = False,
        exclude_through_models: bool = False,
        exclude_list: bool = False,
        relation_map: Optional[builtins.dict] = None,
        flatten_fields: Union[
            set, list, str, tuple, builtins.dict, "FieldAccessor", FlattenMap, None
        ] = None,
        flatten_all: bool = False,
        round_trip: bool = False,
        warnings: bool = True,
    ) -> "DictStrAny":  # noqa: A003
        """

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

        Nested models are also parsed to dictionaries.

        Additionally, fields decorated with @property_field are also added.

        :param exclude_through_models: flag to exclude through models from dict
        :type exclude_through_models: bool
        :param exclude_primary_keys: flag to exclude primary keys from dict
        :type exclude_primary_keys: bool
        :param include: fields to include
        :type include: Union[set, dict, None]
        :param exclude: fields to exclude
        :type exclude: Union[set, dict, None]
        :param by_alias: flag to get values by alias - passed to pydantic
        :type by_alias: bool
        :param exclude_unset: flag to exclude not set values - passed to pydantic
        :type exclude_unset: bool
        :param exclude_defaults: flag to exclude default values - passed to pydantic
        :type exclude_defaults: bool
        :param exclude_none: flag to exclude None values - passed to pydantic
        :type exclude_none: bool
        :param exclude_list: flag to exclude lists of nested values models from dict
        :type exclude_list: bool
        :param relation_map: map of the relations to follow to avoid circular deps
        :type relation_map: dict
        :param flatten_fields: relations to render as their primary-key value.
            Accepts dunder-strings, list/set/tuple, nested dict (with Ellipsis),
            or ``FieldAccessor`` / list of accessors. Resolves to a nested-dict
            spec used during traversal.
        :type flatten_fields: Union[set, list, str, tuple, dict, FieldAccessor, None]
        :param flatten_all: if True every nested relation is collapsed to its pk
        :type flatten_all: bool
        :param mode: The mode in which `to_python` should run.
            If mode is 'json', the dictionary will only contain JSON serializable types.
            If mode is 'python', the dictionary may contain any Python objects.
        :type mode: str
        :param round_trip: flag to enable serialization round-trip support
        :type round_trip: bool
        :param warnings: flag to log warnings for invalid fields
        :type warnings: bool
        :return:
        :rtype:
        """
        flatten_map = self._resolve_flatten_map(
            flatten_fields=flatten_fields, flatten_all=flatten_all
        )
        if exclude_primary_keys and flatten_map:
            raise QueryDefinitionError(
                "flatten_fields / flatten_all cannot be combined with "
                "exclude_primary_keys=True: flattening renders primary keys, "
                "excluding removes them."
            )
        include_dict = normalize_to_dict(include)
        exclude_dict = normalize_to_dict(exclude)
        if flatten_map is not None:
            flatten_map.check_vs_selector(include_dict, "include")
            flatten_map.check_vs_selector(exclude_dict, "exclude")

        pydantic_exclude = self._update_excluded_with_related(exclude)
        pydantic_exclude = self._update_excluded_with_pks_and_through(
            exclude=pydantic_exclude,
            exclude_primary_keys=exclude_primary_keys,
            exclude_through_models=exclude_through_models,
        )
        dict_instance = super().model_dump(
            mode=mode,
            include=include,
            exclude=pydantic_exclude,
            by_alias=by_alias,
            exclude_defaults=exclude_defaults,
            exclude_unset=exclude_unset,
            exclude_none=exclude_none,
            round_trip=round_trip,
            warnings=False,
        )

        dict_instance = {
            k: self._convert_bytes_to_str(column_name=k, value=v)
            for k, v in dict_instance.items()
        }

        relation_map = (
            relation_map
            if relation_map is not None
            else translate_list_to_dict(self._iterate_related_models())
        )
        pk_only = getattr(self, "__pk_only__", False)
        if relation_map and not pk_only:
            dict_instance = self._extract_nested_models(
                relation_map=relation_map,
                dict_instance=dict_instance,
                include=include_dict,
                exclude=exclude_dict,
                exclude_primary_keys=exclude_primary_keys,
                exclude_through_models=exclude_through_models,
                exclude_list=exclude_list,
                flatten_map=flatten_map,
            )

        return dict_instance

    def _resolve_flatten_map(
        self,
        flatten_fields: Union[
            set, list, str, tuple, builtins.dict, "FieldAccessor", FlattenMap, None
        ],
        flatten_all: bool,
    ) -> Optional[FlattenMap]:
        """
        Produce a :class:`FlattenMap` from the various input forms accepted by
        ``model_dump``. Resolves dunder strings / lists / sets / nested dicts /
        ``FieldAccessor`` chains; an already-resolved ``FlattenMap`` (passed
        during recursion) is returned as-is.

        :param flatten_fields: user spec, nested dict, or FlattenMap from recursion
        :type flatten_fields: Union[set, list, str, tuple, dict, FieldAccessor,
            FlattenMap, None]
        :param flatten_all: if True every relation flattens regardless of spec
        :type flatten_all: bool
        :return: resolved FlattenMap, or None when nothing should flatten
        :rtype: Optional[FlattenMap]
        """
        if flatten_all:
            return FlattenMap(flatten_all=True)

        if flatten_fields is None:
            excludable = getattr(self, "__ormar_excludable__", None)
            return excludable.flatten_map() if excludable is not None else None

        if isinstance(flatten_fields, FlattenMap):
            return flatten_fields

        if isinstance(flatten_fields, builtins.dict):
            return FlattenMap(data=flatten_fields)

        excludable = ExcludableItems()
        excludable.build(
            items=extract_access_chains(flatten_fields),
            model_cls=cast(type["Model"], type(self)),
            slot="flatten",
        )
        return excludable.flatten_map()

    @typing_extensions.deprecated(
        "The `json` method is deprecated; use `model_dump_json` instead.",
        category=OrmarDeprecatedSince020,
    )
    def json(  # type: ignore # noqa A003
        self,
        *,
        include: Union[set, builtins.dict, None] = None,
        exclude: Union[set, builtins.dict, None] = None,
        by_alias: bool = False,
        exclude_unset: bool = False,
        exclude_defaults: bool = False,
        exclude_none: bool = False,
        exclude_primary_keys: bool = False,
        exclude_through_models: bool = False,
        **dumps_kwargs: Any,
    ) -> str:  # pragma: no cover
        warnings.warn(
            "The `json` method is deprecated; use `model_dump_json` instead.",
            DeprecationWarning,
        )
        return self.model_dump_json(
            include=include,
            exclude=exclude,
            by_alias=by_alias,
            exclude_unset=exclude_unset,
            exclude_defaults=exclude_defaults,
            exclude_none=exclude_none,
            exclude_primary_keys=exclude_primary_keys,
            exclude_through_models=exclude_through_models,
            **dumps_kwargs,
        )

    def model_dump_json(  # type: ignore # noqa A003
        self,
        *,
        include: Union[set, builtins.dict, None] = None,
        exclude: Union[set, builtins.dict, None] = None,
        by_alias: bool = False,
        exclude_unset: bool = False,
        exclude_defaults: bool = False,
        exclude_none: bool = False,
        exclude_primary_keys: bool = False,
        exclude_through_models: bool = False,
        flatten_fields: Union[
            set, list, str, tuple, builtins.dict, "FieldAccessor", FlattenMap, None
        ] = None,
        flatten_all: bool = False,
        **dumps_kwargs: Any,
    ) -> str:
        """
        Generate a JSON representation of the model, `include` and `exclude`
        arguments as per `dict()`.

        `encoder` is an optional function to supply as `default` to json.dumps(),
        other arguments as per `json.dumps()`.
        """
        data = self.model_dump(
            include=include,
            exclude=exclude,
            by_alias=by_alias,
            exclude_unset=exclude_unset,
            exclude_defaults=exclude_defaults,
            exclude_none=exclude_none,
            exclude_primary_keys=exclude_primary_keys,
            exclude_through_models=exclude_through_models,
            flatten_fields=flatten_fields,
            flatten_all=flatten_all,
        )
        return self.__pydantic_serializer__.to_json(data, warnings=False).decode()

    @classmethod
    @typing_extensions.deprecated(
        "The `construct` method is deprecated; use `model_construct` instead.",
        category=OrmarDeprecatedSince020,
    )
    def construct(
        cls: type["T"], _fields_set: Union[set[str], None] = None, **values: Any
    ) -> "T":  # pragma: no cover
        warnings.warn(
            "The `construct` method is deprecated; use `model_construct` instead.",
            DeprecationWarning,
        )
        return cls.model_construct(_fields_set=_fields_set, **values)

    @classmethod
    def model_construct(
        cls: type["T"], _fields_set: Optional["SetStr"] = None, **values: Any
    ) -> "T":
        own_values = {
            k: v for k, v in values.items() if k not in cls.extract_related_names()
        }
        model = cls.__new__(cls)
        fields_values: dict[str, Any] = {}
        for name, field in cls.model_fields.items():
            if name in own_values:
                fields_values[name] = own_values[name]
            elif not field.is_required():
                fields_values[name] = field.get_default()
        fields_values.update(own_values)

        if _fields_set is None:
            _fields_set = set(values.keys())

        extra_allowed = cls.model_config.get("extra") == "allow"
        if not extra_allowed:
            fields_values.update(values)
        object.__setattr__(model, "__dict__", fields_values)
        model._initialize_internal_attributes()
        cls._construct_relations(model=model, values=values)
        object.__setattr__(model, "__pydantic_fields_set__", _fields_set)
        return cls._pydantic_model_construct_finalizer(
            model=model, extra_allowed=extra_allowed, values=values
        )

    @classmethod
    def _pydantic_model_construct_finalizer(
        cls: type["T"], model: "T", extra_allowed: bool, **values: Any
    ) -> "T":
        """
        Recreate pydantic model_construct logic here as we do not call super method.
        """
        _extra: Union[builtins.dict[str, Any], None] = None
        if extra_allowed:  # pragma: no cover
            _extra = {}
            for k, v in values.items():
                _extra[k] = v

        if not cls.__pydantic_root_model__:
            object.__setattr__(model, "__pydantic_extra__", _extra)

        if cls.__pydantic_post_init__:  # pragma: no cover
            model.model_post_init(None)
        elif not cls.__pydantic_root_model__:
            # Note: if there are any private attributes,
            # cls.__pydantic_post_init__ would exist
            # Since it doesn't, that means that `__pydantic_private__`
            # should be set to None
            object.__setattr__(model, "__pydantic_private__", None)

        return model

    @classmethod
    def _construct_relations(cls: type["T"], model: "T", values: builtins.dict) -> None:
        present_relations = [
            relation for relation in cls.extract_related_names() if relation in values
        ]
        for relation in present_relations:
            value_to_set = values[relation]
            if not isinstance(value_to_set, list):
                value_to_set = [value_to_set]
            relation_field = cls.ormar_config.model_fields[relation]
            relation_value = [
                relation_field.expand_relationship(x, model, to_register=False)
                for x in value_to_set
                if x is not None
            ]

            for child in relation_value:
                model._orm.add(
                    parent=cast("Model", child),
                    child=cast("Model", model),
                    field=cast("ForeignKeyField", relation_field),
                )

    def update_from_dict(self, value_dict: builtins.dict) -> "NewBaseModel":
        """
        Updates self with values of fields passed in the dictionary.

        :param value_dict: dictionary of fields names and values
        :type value_dict: dict
        :return: self
        :rtype: NewBaseModel
        """
        for key, value in value_dict.items():
            setattr(self, key, value)
        return self

    def _convert_bytes_to_str(
        self, column_name: str, value: Any
    ) -> Union[str, builtins.dict]:
        """
        Converts value to str from bytes for represent_as_base64_str columns.

        :param column_name: name of the field
        :type column_name: str
        :param value: value fo the field
        :type value: Any
        :return: converted value if needed, else original value
        :rtype: Any
        """
        if column_name not in self._bytes_fields:
            return value
        field = self.ormar_config.model_fields[column_name]
        if (
            value is not None
            and not isinstance(value, str)
            and field.represent_as_base64_str
        ):
            return base64.b64encode(value).decode()
        return value

    def _extract_own_model_fields(self) -> builtins.dict:
        """
        Returns a dictionary with field names and values for fields that are not
        relations fields (ForeignKey, ManyToMany etc.)

        :return: dictionary of fields names and values.
        :rtype: dict
        """
        related_names = self.extract_related_names()
        self_fields = {k: v for k, v in self.__dict__.items() if k not in related_names}
        return self_fields

    def _extract_model_db_fields(self) -> builtins.dict:
        """
        Returns a dictionary with field names and values for fields that are stored in
        current model's table.

        That includes own non-relational fields ang foreign key fields.

        :return: dictionary of fields names and values.
        :rtype: dict
        """
        self_fields = self._extract_own_model_fields()
        self_fields = {
            k: v
            for k, v in self_fields.items()
            if self.get_column_alias(k) in self.ormar_config.table.columns
        }
        for field in self._extract_db_related_names():
            relation_field = self.ormar_config.model_fields[field]
            target_pk_name = relation_field.to.ormar_config.pkname
            target_field = getattr(self, field)
            self_fields[field] = getattr(target_field, target_pk_name, None)
            if not relation_field.nullable and not self_fields[field]:
                raise ModelPersistenceError(
                    f"You cannot save {relation_field.to.get_name()} "
                    f"model without pk set!"
                )
        return self_fields

pk_column property

Retrieves primary key sqlalchemy column from models OrmarConfig.table. Each model has to have primary key. Only one primary key column is allowed.

:return: primary key sqlalchemy column :rtype: sqlalchemy.Column

saved property

Saved status of the model. Changed by setattr and loading from db

signals property

Exposes signals from model OrmarConfig

__eq__(other)

Compares other model to this model. when == is called. :param other: other model to compare :type other: object :return: result of comparison :rtype: bool

Source code in ormar/models/newbasemodel.py
Python
487
488
489
490
491
492
493
494
495
496
497
def __eq__(self, other: object) -> bool:
    """
    Compares other model to this model. when == is called.
    :param other: other model to compare
    :type other: object
    :return: result of comparison
    :rtype: bool
    """
    if isinstance(other, NewBaseModel):
        return self.__same__(other)
    return super().__eq__(other)  # pragma no cover

__getattr__(item)

Used for private attributes of pydantic v2.

:param item: name of attribute :type item: str :return: Any :rtype: Any

Source code in ormar/models/newbasemodel.py
Python
278
279
280
281
282
283
284
285
286
287
288
289
290
def __getattr__(self, item: str) -> Any:
    """
    Used for private attributes of pydantic v2.

    :param item: name of attribute
    :type item: str
    :return: Any
    :rtype: Any
    """
    # TODO: Check __pydantic_extra__
    if item == "__pydantic_extra__":
        return None
    return super().__getattr__(item)  # type: ignore

__init__(*args, **kwargs)

Initializer that creates a new ormar Model that is also pydantic Model at the same time.

Passed keyword arguments can be only field names and their corresponding values as those will be passed to pydantic validation that will complain if extra params are passed.

If relations are defined each relation is expanded and children models are also initialized and validated. Relation from both sides is registered so you can access related models from both sides.

Json fields are automatically loaded/dumped if needed.

Models marked as abstract=True in internal OrmarConfig cannot be initialized.

:raises ModelError: if abstract model is initialized, model has ForwardRefs that has not been updated or unknown field is passed :param args: ignored args :type args: Any :param kwargs: keyword arguments - all fields values and some special params :type kwargs: Any

Source code in ormar/models/newbasemodel.py
Python
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
def __init__(self, *args: Any, **kwargs: Any) -> None:  # type: ignore
    """
    Initializer that creates a new ormar Model that is also pydantic Model at the
    same time.

    Passed keyword arguments can be only field names and their corresponding values
    as those will be passed to pydantic validation that will complain if extra
    params are passed.

    If relations are defined each relation is expanded and children models are also
    initialized and validated. Relation from both sides is registered so you can
    access related models from both sides.

    Json fields are automatically loaded/dumped if needed.

    Models marked as abstract=True in internal OrmarConfig cannot be initialized.

    :raises ModelError: if abstract model is initialized, model has ForwardRefs
     that has not been updated or unknown field is passed
    :param args: ignored args
    :type args: Any
    :param kwargs: keyword arguments - all fields values and some special params
    :type kwargs: Any
    """
    self._verify_model_can_be_initialized()
    self._initialize_internal_attributes()

    object.__setattr__(self, "__pk_only__", False)
    object.__setattr__(self, "__ormar_excludable__", None)

    new_kwargs, through_tmp_dict = self._process_kwargs(kwargs)

    new_kwargs = self.serialize_nested_models_json_fields(new_kwargs)
    self.__pydantic_validator__.validate_python(
        new_kwargs,
        self_instance=self,  # type: ignore
    )
    self._register_related_models(new_kwargs, through_tmp_dict)

__same__(other)

Used by eq, compares other model to this model.

Saved models (both with pk) compare directly by (pk, type) to skip the hash-cache fill on the other side. Unsaved/mixed states fall through to the original hash-equality semantics.

:param other: model to compare to :type other: NewBaseModel :return: result of comparison :rtype: bool

Source code in ormar/models/newbasemodel.py
Python
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
def __same__(self, other: "NewBaseModel") -> bool:
    """
    Used by __eq__, compares other model to this model.

    Saved models (both with pk) compare directly by ``(pk, type)`` to
    skip the hash-cache fill on the *other* side. Unsaved/mixed states
    fall through to the original hash-equality semantics.

    :param other: model to compare to
    :type other: NewBaseModel
    :return: result of comparison
    :rtype: bool
    """
    if type(self) is not type(other):
        return False  # pragma: no cover
    self_pk = self.pk
    other_pk = other.pk
    if self_pk is not None and other_pk is not None:
        return self_pk == other_pk
    if (self_pk is None) != (other_pk is None):
        return False
    else:
        return hash(self) == other.__hash__()

__setattr__(name, value)

Overwrites setattr in pydantic parent as otherwise descriptors are not called.

:param name: name of the attribute to set :type name: str :param value: value of the attribute to set :type value: Any :return: None :rtype: None

Source code in ormar/models/newbasemodel.py
Python
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
def __setattr__(self, name: str, value: Any) -> None:  # noqa CCR001
    """
    Overwrites setattr in pydantic parent as otherwise descriptors are not called.

    :param name: name of the attribute to set
    :type name: str
    :param value: value of the attribute to set
    :type value: Any
    :return: None
    :rtype: None
    """
    prev_hash = hash(self)

    if hasattr(self, name):
        object.__setattr__(self, name, value)
    else:
        # let pydantic handle errors for unknown fields
        super().__setattr__(name, value)

    if self._onupdate_fields and name in self.ormar_config.model_fields:
        self.__setattr_fields__.add(name)

    # In this case, the hash could have changed, so update it
    if name == self.ormar_config.pkname or self.pk is None:
        object.__setattr__(self, "__cached_hash__", None)
        new_hash = hash(self)

        if prev_hash != new_hash:
            self._update_relation_cache(prev_hash, new_hash)

db_backend_name() classmethod

Shortcut to database dialect, cause some dialect require different treatment

Source code in ormar/models/newbasemodel.py
Python
598
599
600
601
602
@classmethod
def db_backend_name(cls) -> str:
    """Shortcut to database dialect,
    cause some dialect require different treatment"""
    return cls.ormar_config.database.dialect.name

get_name(lower=True) classmethod

Returns name of the Model class, by default lowercase.

:param lower: flag if name should be set to lowercase :type lower: bool :return: name of the model :rtype: str

Source code in ormar/models/newbasemodel.py
Python
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
@classmethod
def get_name(cls, lower: bool = True) -> str:
    """
    Returns name of the Model class, by default lowercase.

    :param lower: flag if name should be set to lowercase
    :type lower: bool
    :return: name of the model
    :rtype: str
    """
    if lower:
        try:
            return cls._lower_name  # type: ignore[attr-defined]
        except AttributeError:
            cls._lower_name = cls.__name__.lower()  # type: ignore[attr-defined]
            return cls._lower_name  # type: ignore[attr-defined]
    return cls.__name__

model_dump(*, mode='python', include=None, exclude=None, by_alias=False, exclude_unset=False, exclude_defaults=False, exclude_none=False, exclude_primary_keys=False, exclude_through_models=False, exclude_list=False, relation_map=None, flatten_fields=None, flatten_all=False, round_trip=False, warnings=True)

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

Nested models are also parsed to dictionaries.

Additionally, fields decorated with @property_field are also added.

:param exclude_through_models: flag to exclude through models from dict :type exclude_through_models: bool :param exclude_primary_keys: flag to exclude primary keys from dict :type exclude_primary_keys: bool :param include: fields to include :type include: Union[set, dict, None] :param exclude: fields to exclude :type exclude: Union[set, dict, None] :param by_alias: flag to get values by alias - passed to pydantic :type by_alias: bool :param exclude_unset: flag to exclude not set values - passed to pydantic :type exclude_unset: bool :param exclude_defaults: flag to exclude default values - passed to pydantic :type exclude_defaults: bool :param exclude_none: flag to exclude None values - passed to pydantic :type exclude_none: bool :param exclude_list: flag to exclude lists of nested values models from dict :type exclude_list: bool :param relation_map: map of the relations to follow to avoid circular deps :type relation_map: dict :param flatten_fields: relations to render as their primary-key value. Accepts dunder-strings, list/set/tuple, nested dict (with Ellipsis), or FieldAccessor / list of accessors. Resolves to a nested-dict spec used during traversal. :type flatten_fields: Union[set, list, str, tuple, dict, FieldAccessor, None] :param flatten_all: if True every nested relation is collapsed to its pk :type flatten_all: bool :param mode: The mode in which to_python should run. If mode is 'json', the dictionary will only contain JSON serializable types. If mode is 'python', the dictionary may contain any Python objects. :type mode: str :param round_trip: flag to enable serialization round-trip support :type round_trip: bool :param warnings: flag to log warnings for invalid fields :type warnings: bool :return: :rtype:

Source code in ormar/models/newbasemodel.py
Python
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
def model_dump(  # type: ignore # noqa A003, CFQ002
    self,
    *,
    mode: Union[Literal["json", "python"], str] = "python",
    include: Union[set, builtins.dict, None] = None,
    exclude: Union[set, builtins.dict, None] = None,
    by_alias: bool = False,
    exclude_unset: bool = False,
    exclude_defaults: bool = False,
    exclude_none: bool = False,
    exclude_primary_keys: bool = False,
    exclude_through_models: bool = False,
    exclude_list: bool = False,
    relation_map: Optional[builtins.dict] = None,
    flatten_fields: Union[
        set, list, str, tuple, builtins.dict, "FieldAccessor", FlattenMap, None
    ] = None,
    flatten_all: bool = False,
    round_trip: bool = False,
    warnings: bool = True,
) -> "DictStrAny":  # noqa: A003
    """

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

    Nested models are also parsed to dictionaries.

    Additionally, fields decorated with @property_field are also added.

    :param exclude_through_models: flag to exclude through models from dict
    :type exclude_through_models: bool
    :param exclude_primary_keys: flag to exclude primary keys from dict
    :type exclude_primary_keys: bool
    :param include: fields to include
    :type include: Union[set, dict, None]
    :param exclude: fields to exclude
    :type exclude: Union[set, dict, None]
    :param by_alias: flag to get values by alias - passed to pydantic
    :type by_alias: bool
    :param exclude_unset: flag to exclude not set values - passed to pydantic
    :type exclude_unset: bool
    :param exclude_defaults: flag to exclude default values - passed to pydantic
    :type exclude_defaults: bool
    :param exclude_none: flag to exclude None values - passed to pydantic
    :type exclude_none: bool
    :param exclude_list: flag to exclude lists of nested values models from dict
    :type exclude_list: bool
    :param relation_map: map of the relations to follow to avoid circular deps
    :type relation_map: dict
    :param flatten_fields: relations to render as their primary-key value.
        Accepts dunder-strings, list/set/tuple, nested dict (with Ellipsis),
        or ``FieldAccessor`` / list of accessors. Resolves to a nested-dict
        spec used during traversal.
    :type flatten_fields: Union[set, list, str, tuple, dict, FieldAccessor, None]
    :param flatten_all: if True every nested relation is collapsed to its pk
    :type flatten_all: bool
    :param mode: The mode in which `to_python` should run.
        If mode is 'json', the dictionary will only contain JSON serializable types.
        If mode is 'python', the dictionary may contain any Python objects.
    :type mode: str
    :param round_trip: flag to enable serialization round-trip support
    :type round_trip: bool
    :param warnings: flag to log warnings for invalid fields
    :type warnings: bool
    :return:
    :rtype:
    """
    flatten_map = self._resolve_flatten_map(
        flatten_fields=flatten_fields, flatten_all=flatten_all
    )
    if exclude_primary_keys and flatten_map:
        raise QueryDefinitionError(
            "flatten_fields / flatten_all cannot be combined with "
            "exclude_primary_keys=True: flattening renders primary keys, "
            "excluding removes them."
        )
    include_dict = normalize_to_dict(include)
    exclude_dict = normalize_to_dict(exclude)
    if flatten_map is not None:
        flatten_map.check_vs_selector(include_dict, "include")
        flatten_map.check_vs_selector(exclude_dict, "exclude")

    pydantic_exclude = self._update_excluded_with_related(exclude)
    pydantic_exclude = self._update_excluded_with_pks_and_through(
        exclude=pydantic_exclude,
        exclude_primary_keys=exclude_primary_keys,
        exclude_through_models=exclude_through_models,
    )
    dict_instance = super().model_dump(
        mode=mode,
        include=include,
        exclude=pydantic_exclude,
        by_alias=by_alias,
        exclude_defaults=exclude_defaults,
        exclude_unset=exclude_unset,
        exclude_none=exclude_none,
        round_trip=round_trip,
        warnings=False,
    )

    dict_instance = {
        k: self._convert_bytes_to_str(column_name=k, value=v)
        for k, v in dict_instance.items()
    }

    relation_map = (
        relation_map
        if relation_map is not None
        else translate_list_to_dict(self._iterate_related_models())
    )
    pk_only = getattr(self, "__pk_only__", False)
    if relation_map and not pk_only:
        dict_instance = self._extract_nested_models(
            relation_map=relation_map,
            dict_instance=dict_instance,
            include=include_dict,
            exclude=exclude_dict,
            exclude_primary_keys=exclude_primary_keys,
            exclude_through_models=exclude_through_models,
            exclude_list=exclude_list,
            flatten_map=flatten_map,
        )

    return dict_instance

model_dump_json(*, include=None, exclude=None, by_alias=False, exclude_unset=False, exclude_defaults=False, exclude_none=False, exclude_primary_keys=False, exclude_through_models=False, flatten_fields=None, flatten_all=False, **dumps_kwargs)

Generate a JSON representation of the model, include and exclude arguments as per dict().

encoder is an optional function to supply as default to json.dumps(), other arguments as per json.dumps().

Source code in ormar/models/newbasemodel.py
Python
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
def model_dump_json(  # type: ignore # noqa A003
    self,
    *,
    include: Union[set, builtins.dict, None] = None,
    exclude: Union[set, builtins.dict, None] = None,
    by_alias: bool = False,
    exclude_unset: bool = False,
    exclude_defaults: bool = False,
    exclude_none: bool = False,
    exclude_primary_keys: bool = False,
    exclude_through_models: bool = False,
    flatten_fields: Union[
        set, list, str, tuple, builtins.dict, "FieldAccessor", FlattenMap, None
    ] = None,
    flatten_all: bool = False,
    **dumps_kwargs: Any,
) -> str:
    """
    Generate a JSON representation of the model, `include` and `exclude`
    arguments as per `dict()`.

    `encoder` is an optional function to supply as `default` to json.dumps(),
    other arguments as per `json.dumps()`.
    """
    data = self.model_dump(
        include=include,
        exclude=exclude,
        by_alias=by_alias,
        exclude_unset=exclude_unset,
        exclude_defaults=exclude_defaults,
        exclude_none=exclude_none,
        exclude_primary_keys=exclude_primary_keys,
        exclude_through_models=exclude_through_models,
        flatten_fields=flatten_fields,
        flatten_all=flatten_all,
    )
    return self.__pydantic_serializer__.to_json(data, warnings=False).decode()

pk_type() classmethod

Shortcut to models primary key field type

Source code in ormar/models/newbasemodel.py
Python
593
594
595
596
@classmethod
def pk_type(cls) -> Any:
    """Shortcut to models primary key field type"""
    return cls.ormar_config.model_fields[cls.ormar_config.pkname].__type__

populate_through_models(model, model_dict, include, exclude, relation_map) staticmethod

Populates through models with values from dict representation.

:param model: model to populate through models :type model: Model :param model_dict: dict representation of the model :type model_dict: dict :param include: fields to include :type include: dict :param exclude: fields to exclude :type exclude: dict :param relation_map: map of relations to follow to avoid circular refs :type relation_map: dict :return: None :rtype: None

Source code in ormar/models/newbasemodel.py
Python
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
@staticmethod
def populate_through_models(
    model: "Model",
    model_dict: builtins.dict,
    include: Union[set, builtins.dict],
    exclude: Union[set, builtins.dict],
    relation_map: builtins.dict,
) -> None:
    """
    Populates through models with values from dict representation.

    :param model: model to populate through models
    :type model: Model
    :param model_dict: dict representation of the model
    :type model_dict: dict
    :param include: fields to include
    :type include: dict
    :param exclude: fields to exclude
    :type exclude: dict
    :param relation_map: map of relations to follow to avoid circular refs
    :type relation_map: dict
    :return: None
    :rtype: None
    """

    models_to_populate = filter_not_excluded_fields(
        fields=model.extract_through_names(),
        include=normalize_to_dict(include),
        exclude=normalize_to_dict(exclude),
    )
    through_fields_to_populate = [
        model.ormar_config.model_fields[through_model]
        for through_model in models_to_populate
        if model.ormar_config.model_fields[through_model].related_name
        not in relation_map
    ]
    for through_field in through_fields_to_populate:
        through_instance = getattr(model, through_field.name)
        if through_instance:
            model_dict[through_field.name] = through_instance.model_dump()

remove(parent, name)

Removes child from relation with given name in RelationshipManager

Source code in ormar/models/newbasemodel.py
Python
604
605
606
def remove(self, parent: "Model", name: str) -> None:
    """Removes child from relation with given name in RelationshipManager"""
    self._orm.remove_parent(self, parent, name)

set_save_status(status)

Sets value of the save status

Source code in ormar/models/newbasemodel.py
Python
608
609
610
def set_save_status(self, status: bool) -> None:
    """Sets value of the save status"""
    object.__setattr__(self, "_orm_saved", status)

update_forward_refs(**localns) classmethod

Processes fields that are ForwardRef and need to be evaluated into actual models.

Expands relationships, register relation in alias manager and substitutes sqlalchemy columns with new ones with proper column type (null before).

Populates OrmarConfig table of the Model which is left empty before.

Sets self_reference flag on models that links to themselves.

Calls the pydantic method to evaluate pydantic fields.

:param localns: local namespace :type localns: Any :return: None :rtype: None

Source code in ormar/models/newbasemodel.py
Python
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
@classmethod
def update_forward_refs(cls, **localns: Any) -> None:
    """
    Processes fields that are ForwardRef and need to be evaluated into actual
    models.

    Expands relationships, register relation in alias manager and substitutes
    sqlalchemy columns with new ones with proper column type (null before).

    Populates OrmarConfig table of the Model which is left empty before.

    Sets self_reference flag on models that links to themselves.

    Calls the pydantic method to evaluate pydantic fields.

    :param localns: local namespace
    :type localns: Any
    :return: None
    :rtype: None
    """
    globalns = sys.modules[cls.__module__].__dict__.copy()
    globalns.setdefault(cls.__name__, cls)
    fields_to_check = cls.ormar_config.model_fields.copy()
    for field in fields_to_check.values():
        if field.has_unresolved_forward_refs():
            field = cast(ForeignKeyField, field)
            field.evaluate_forward_ref(globalns=globalns, localns=localns)
            field.set_self_reference_flag()
            if field.is_multi and not field.through:
                field = cast(ormar.ManyToManyField, field)
                field.create_default_through_model()
            expand_reverse_relationship(model_field=field)
            register_relation_in_alias_manager(field=field)
            update_column_definition(model=cls, field=field)
    populate_config_sqlalchemy_table_if_required(config=cls.ormar_config)
    # super().update_forward_refs(**localns)
    cls.model_rebuild(
        force=True,
        _types_namespace={
            field.to.__name__: field.to
            for field in fields_to_check.values()
            if field.is_relation
        },
    )
    cls.ormar_config.requires_ref_update = False

update_from_dict(value_dict)

Updates self with values of fields passed in the dictionary.

:param value_dict: dictionary of fields names and values :type value_dict: dict :return: self :rtype: NewBaseModel

Source code in ormar/models/newbasemodel.py
Python
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
def update_from_dict(self, value_dict: builtins.dict) -> "NewBaseModel":
    """
    Updates self with values of fields passed in the dictionary.

    :param value_dict: dictionary of fields names and values
    :type value_dict: dict
    :return: self
    :rtype: NewBaseModel
    """
    for key, value in value_dict.items():
        setattr(self, key, value)
    return self