From 9d35b0923a05bcc2b99521ccfef3e5db6f4c7622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren?= Date: Wed, 22 Feb 2023 05:01:27 +0100 Subject: [PATCH] fix performance issues on /api/foods (#2163) * fix performance issues on /api/foods * fix comment * actually apply query-options --- mealie/repos/repository_foods.py | 8 ++++++++ mealie/repos/repository_generic.py | 10 +++++++--- mealie/schema/recipe/recipe_ingredient.py | 21 +++++++++++++-------- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/mealie/repos/repository_foods.py b/mealie/repos/repository_foods.py index 54770858d46d..8d364c058aaf 100644 --- a/mealie/repos/repository_foods.py +++ b/mealie/repos/repository_foods.py @@ -1,5 +1,7 @@ from pydantic import UUID4 from sqlalchemy import select +from sqlalchemy.orm import joinedload +from sqlalchemy.orm.interfaces import LoaderOption from mealie.db.models.recipe.ingredient import IngredientFoodModel from mealie.schema.recipe.recipe_ingredient import IngredientFood @@ -29,3 +31,9 @@ class RepositoryFood(RepositoryGeneric[IngredientFood, IngredientFoodModel]): def by_group(self, group_id: UUID4) -> "RepositoryFood": return super().by_group(group_id) + + def paging_query_options(self) -> list[LoaderOption]: + return [ + joinedload(IngredientFoodModel.extras), + joinedload(IngredientFoodModel.label), + ] diff --git a/mealie/repos/repository_generic.py b/mealie/repos/repository_generic.py index 27995cbc122d..910366ad3bf8 100644 --- a/mealie/repos/repository_generic.py +++ b/mealie/repos/repository_generic.py @@ -7,6 +7,7 @@ from typing import Any, Generic, TypeVar from fastapi import HTTPException from pydantic import UUID4, BaseModel from sqlalchemy import Select, delete, func, select +from sqlalchemy.orm.interfaces import LoaderOption from sqlalchemy.orm.session import Session from sqlalchemy.sql import sqltypes @@ -284,6 +285,10 @@ class RepositoryGeneric(Generic[Schema, Model]): q = self._query().filter(attribute_name == attr_match) return [eff_schema.from_orm(x) for x in self.session.execute(q).scalars().all()] + def paging_query_options(self) -> list[LoaderOption]: + # Override this in subclasses to specify joinedloads or similar for page_all + return [] + def page_all(self, pagination: PaginationQuery, override=None) -> PaginationBase[Schema]: """ pagination is a method to interact with the filtered database table and return a paginated result @@ -296,19 +301,18 @@ class RepositoryGeneric(Generic[Schema, Model]): """ eff_schema = override or self.schema - q = self._query() + q = self._query().options(*self.paging_query_options()) fltr = self._filter_builder() q = q.filter_by(**fltr) q, count, total_pages = self.add_pagination_to_query(q, pagination) try: - data = self.session.execute(q).scalars().all() + data = self.session.execute(q).unique().scalars().all() except Exception as e: self._log_exception(e) self.session.rollback() raise e - return PaginationBase( page=pagination.page, per_page=pagination.per_page, diff --git a/mealie/schema/recipe/recipe_ingredient.py b/mealie/schema/recipe/recipe_ingredient.py index afd3ba2e1eab..cfd4240fb416 100644 --- a/mealie/schema/recipe/recipe_ingredient.py +++ b/mealie/schema/recipe/recipe_ingredient.py @@ -2,12 +2,12 @@ from __future__ import annotations import datetime import enum +from typing import Any from uuid import UUID, uuid4 from pydantic import UUID4, Field, validator from pydantic.utils import GetterDict -from mealie.db.models.recipe.ingredient import IngredientFoodModel from mealie.schema._mealie import MealieModel from mealie.schema._mealie.types import NoneFloat from mealie.schema.response.pagination import PaginationBase @@ -37,14 +37,19 @@ class IngredientFood(CreateIngredientFood): update_at: datetime.datetime | None class Config: - orm_mode = True + class _FoodGetter(GetterDict): + def get(self, key: Any, default: Any = None) -> Any: + # Transform extras into key-value dict + if key == "extras": + value = super().get(key, default) + return {x.key_name: x.value for x in value} - @classmethod - def getter_dict(cls, name_orm: IngredientFoodModel): - return { - **GetterDict(name_orm), - "extras": {x.key_name: x.value for x in name_orm.extras}, - } + # Keep all other fields as they are + else: + return super().get(key, default) + + orm_mode = True + getter_dict = _FoodGetter class IngredientFoodPagination(PaginationBase):