Remove all sqlalchemy lazy-loading from app (#2260)

* Remove some implicit lazy-loads from user serialization

* implement full backup restore across different database versions

* rework all custom getter dicts to not leak lazy loads

* remove some occurances of lazy-loading

* remove a lot of lazy loading from recipes

* add more eager loading
remove loading options from repository
remove raiseload for checking

* fix failing test

* do not apply loader options for paging counts

* try using selectinload a bit more instead of joinedload

* linter fixes
This commit is contained in:
Sören
2023-03-24 17:27:26 +01:00
committed by GitHub
parent fae62ecb19
commit 4b426ddf2f
23 changed files with 351 additions and 142 deletions

View File

@@ -5,7 +5,8 @@ from uuid import UUID
from pydantic import UUID4, Field, validator
from pydantic.types import constr
from pydantic.utils import GetterDict
from sqlalchemy.orm import joinedload, selectinload
from sqlalchemy.orm.interfaces import LoaderOption
from mealie.core.config import get_app_dirs, get_app_settings
from mealie.db.models.users import User
@@ -15,6 +16,9 @@ from mealie.schema.group.group_preferences import ReadGroupPreferences
from mealie.schema.recipe import RecipeSummary
from mealie.schema.response.pagination import PaginationBase
from ...db.models.group import Group
from ...db.models.recipe import RecipeModel
from ..getter_dict import GroupGetterDict, UserGetterDict
from ..recipe import CategoryBase
DEFAULT_INTEGRATION_ID = "generic"
@@ -78,19 +82,8 @@ class UserBase(MealieModel):
can_organize: bool = False
class Config:
class _UserGetter(GetterDict):
def get(self, key: Any, default: Any = None) -> Any:
# Transform extras into key-value dict
if key == "group":
value = super().get(key, default)
return value.group.name
# Keep all other fields as they are
else:
return super().get(key, default)
orm_mode = True
getter_dict = _UserGetter
getter_dict = GroupGetterDict
schema_extra = {
"example": {
@@ -118,13 +111,11 @@ class UserOut(UserBase):
class Config:
orm_mode = True
@classmethod
def getter_dict(cls, ormModel: User):
return {
**GetterDict(ormModel),
"group": ormModel.group.name,
"favorite_recipes": [x.slug for x in ormModel.favorite_recipes],
}
getter_dict = UserGetterDict
@classmethod
def loader_options(cls) -> list[LoaderOption]:
return [joinedload(User.group), joinedload(User.favorite_recipes), joinedload(User.tokens)]
class UserPagination(PaginationBase):
@@ -136,13 +127,16 @@ class UserFavorites(UserBase):
class Config:
orm_mode = True
getter_dict = GroupGetterDict
@classmethod
def getter_dict(cls, ormModel: User):
return {
**GetterDict(ormModel),
"group": ormModel.group.name,
}
@classmethod
def loader_options(cls) -> list[LoaderOption]:
return [
joinedload(User.group),
selectinload(User.favorite_recipes).joinedload(RecipeModel.recipe_category),
selectinload(User.favorite_recipes).joinedload(RecipeModel.tags),
selectinload(User.favorite_recipes).joinedload(RecipeModel.tools),
]
class PrivateUser(UserOut):
@@ -175,6 +169,10 @@ class PrivateUser(UserOut):
def directory(self) -> Path:
return PrivateUser.get_directory(self.id)
@classmethod
def loader_options(cls) -> list[LoaderOption]:
return [joinedload(User.group), selectinload(User.favorite_recipes), joinedload(User.tokens)]
class UpdateGroup(GroupBase):
id: UUID4
@@ -211,6 +209,17 @@ class GroupInDB(UpdateGroup):
def exports(self) -> Path:
return GroupInDB.get_export_directory(self.id)
@classmethod
def loader_options(cls) -> list[LoaderOption]:
return [
joinedload(Group.categories),
joinedload(Group.webhooks),
joinedload(Group.preferences),
selectinload(Group.users).joinedload(User.group),
selectinload(Group.users).joinedload(User.favorite_recipes),
selectinload(Group.users).joinedload(User.tokens),
]
class GroupPagination(PaginationBase):
items: list[GroupInDB]