Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Changelog
0.13.5
------
- Sample Starlette integration
- Relational fields are now lazily constructed via properties instead of in the constructor,
this results in a significant overhead reduction for Model instantiation with many relationships.

0.13.4
------
Expand All @@ -27,7 +29,7 @@ Changelog

0.13.2
------
* Security fixes for ``«model».save()`` & ``«model».dete()``:
* Security fixes for ``«model».save()`` & ``«model».delete()``:

This is now fully parametrized, and these operations are no longer susceptible to escaping issues.

Expand Down
Binary file modified docs/ORM_Perf.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 3 additions & 4 deletions tortoise/backends/base/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,7 @@ async def _prefetch_reverse_relation(
self._field_to_db(instance._meta.pk, instance.pk, instance)
for instance in instance_list
} # type: Set[Any]
backward_relation_manager = getattr(self.model, field)
relation_field = backward_relation_manager.relation_field
relation_field = self.model._meta.fields_map[field].relation_field

related_object_list = await related_query.filter(
**{"{}__in".format(relation_field): list(instance_id_set)}
Expand Down Expand Up @@ -301,9 +300,9 @@ def _make_prefetch_queries(self) -> None:
self._prefetch_queries[field] = related_query

async def _do_prefetch(self, instance_id_list: list, field: str, related_query) -> list:
if isinstance(getattr(self.model, field), fields.BackwardFKRelation):
if field in self.model._meta.backward_fk_fields:
return await self._prefetch_reverse_relation(instance_id_list, field, related_query)
if isinstance(getattr(self.model, field), fields.ManyToManyField):
if field in self.model._meta.m2m_fields:
return await self._prefetch_m2m_relation(instance_id_list, field, related_query)
return await self._prefetch_direct_relation(instance_id_list, field, related_query)

Expand Down
65 changes: 42 additions & 23 deletions tortoise/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,22 @@ def _fk_getter(self, _key):
return getattr(self, _key, None)


def _rfk_getter(self, _key, ftype, frelfield):
val = getattr(self, _key, None)
if val is None:
val = RelationQueryContainer(ftype, frelfield, self)
setattr(self, _key, val)
return val


def _m2m_getter(self, _key, field_object):
val = getattr(self, _key, None)
if val is None:
val = ManyToManyRelationManager(field_object.type, self, field_object)
setattr(self, _key, val)
return val


class MetaInfo:
__slots__ = (
"abstract",
Expand Down Expand Up @@ -168,6 +184,32 @@ def finalise_fields(self) -> None:
),
)

# Create lazy reverse FK fields on model.
for key in self.backward_fk_fields:
_key = "_{}".format(key)
field_object = self.fields_map[key] # type: fields.BackwardFKRelation # type: ignore
setattr(
self._model,
key,
property(
partial(
_rfk_getter,
_key=_key,
ftype=field_object.type,
frelfield=field_object.relation_field,
)
),
)

# Create lazy M2M fields on model.
for key in self.m2m_fields:
_key = "_{}".format(key)
setattr(
self._model,
key,
property(partial(_m2m_getter, _key=_key, field_object=self.fields_map[key])),
)

def _generate_filters(self) -> None:
get_overridden_filter_func = self.db.executor_class.get_overridden_filter_func
for key, filter_info in self._filters.items():
Expand Down Expand Up @@ -311,7 +353,6 @@ def __init__(self, *args, **kwargs) -> None:
# self._meta is a very common attribute lookup, lets cache it.
meta = self._meta
self._saved_in_db = meta.pk_attr in kwargs and meta.pk.generated
self._init_lazy_fkm2m()

# Assign values and do type conversions
passed_fields = {*kwargs.keys()}
Expand All @@ -330,7 +371,6 @@ def __init__(self, *args, **kwargs) -> None:
def _init_from_db(cls, **kwargs) -> MODEL_TYPE:
self = cls.__new__(cls)
self._saved_in_db = True
self._init_lazy_fkm2m()

meta = self._meta

Expand All @@ -341,27 +381,6 @@ def _init_from_db(cls, **kwargs) -> MODEL_TYPE:

return self

def _init_lazy_fkm2m(self) -> None:
meta = self._meta
# Create lazy fk/m2m objects
for key in meta.backward_fk_fields:
field_object = meta.fields_map[key]
setattr(
self,
key,
RelationQueryContainer(
field_object.type, field_object.relation_field, self # type: ignore
),
)

for key in meta.m2m_fields:
field_object = meta.fields_map[key]
setattr(
self,
key,
ManyToManyRelationManager(field_object.type, self, field_object), # type: ignore
)

def _set_field_values(self, values_map: Dict[str, Any]) -> Set[str]:
"""
Sets values for fields honoring type transformations and
Expand Down