mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-10-30 17:53:31 -04:00 
			
		
		
		
	fix performance issues on /api/foods (#2163)
* fix performance issues on /api/foods * fix comment * actually apply query-options
This commit is contained in:
		| @@ -1,5 +1,7 @@ | |||||||
| from pydantic import UUID4 | from pydantic import UUID4 | ||||||
| from sqlalchemy import select | 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.db.models.recipe.ingredient import IngredientFoodModel | ||||||
| from mealie.schema.recipe.recipe_ingredient import IngredientFood | 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": |     def by_group(self, group_id: UUID4) -> "RepositoryFood": | ||||||
|         return super().by_group(group_id) |         return super().by_group(group_id) | ||||||
|  |  | ||||||
|  |     def paging_query_options(self) -> list[LoaderOption]: | ||||||
|  |         return [ | ||||||
|  |             joinedload(IngredientFoodModel.extras), | ||||||
|  |             joinedload(IngredientFoodModel.label), | ||||||
|  |         ] | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ from typing import Any, Generic, TypeVar | |||||||
| from fastapi import HTTPException | from fastapi import HTTPException | ||||||
| from pydantic import UUID4, BaseModel | from pydantic import UUID4, BaseModel | ||||||
| from sqlalchemy import Select, delete, func, select | from sqlalchemy import Select, delete, func, select | ||||||
|  | from sqlalchemy.orm.interfaces import LoaderOption | ||||||
| from sqlalchemy.orm.session import Session | from sqlalchemy.orm.session import Session | ||||||
| from sqlalchemy.sql import sqltypes | from sqlalchemy.sql import sqltypes | ||||||
|  |  | ||||||
| @@ -284,6 +285,10 @@ class RepositoryGeneric(Generic[Schema, Model]): | |||||||
|             q = self._query().filter(attribute_name == attr_match) |             q = self._query().filter(attribute_name == attr_match) | ||||||
|             return [eff_schema.from_orm(x) for x in self.session.execute(q).scalars().all()] |             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]: |     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 |         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 |         eff_schema = override or self.schema | ||||||
|  |  | ||||||
|         q = self._query() |         q = self._query().options(*self.paging_query_options()) | ||||||
|  |  | ||||||
|         fltr = self._filter_builder() |         fltr = self._filter_builder() | ||||||
|         q = q.filter_by(**fltr) |         q = q.filter_by(**fltr) | ||||||
|         q, count, total_pages = self.add_pagination_to_query(q, pagination) |         q, count, total_pages = self.add_pagination_to_query(q, pagination) | ||||||
|  |  | ||||||
|         try: |         try: | ||||||
|             data = self.session.execute(q).scalars().all() |             data = self.session.execute(q).unique().scalars().all() | ||||||
|         except Exception as e: |         except Exception as e: | ||||||
|             self._log_exception(e) |             self._log_exception(e) | ||||||
|             self.session.rollback() |             self.session.rollback() | ||||||
|             raise e |             raise e | ||||||
|  |  | ||||||
|         return PaginationBase( |         return PaginationBase( | ||||||
|             page=pagination.page, |             page=pagination.page, | ||||||
|             per_page=pagination.per_page, |             per_page=pagination.per_page, | ||||||
|   | |||||||
| @@ -2,12 +2,12 @@ from __future__ import annotations | |||||||
|  |  | ||||||
| import datetime | import datetime | ||||||
| import enum | import enum | ||||||
|  | from typing import Any | ||||||
| from uuid import UUID, uuid4 | from uuid import UUID, uuid4 | ||||||
|  |  | ||||||
| from pydantic import UUID4, Field, validator | from pydantic import UUID4, Field, validator | ||||||
| from pydantic.utils import GetterDict | from pydantic.utils import GetterDict | ||||||
|  |  | ||||||
| from mealie.db.models.recipe.ingredient import IngredientFoodModel |  | ||||||
| from mealie.schema._mealie import MealieModel | from mealie.schema._mealie import MealieModel | ||||||
| from mealie.schema._mealie.types import NoneFloat | from mealie.schema._mealie.types import NoneFloat | ||||||
| from mealie.schema.response.pagination import PaginationBase | from mealie.schema.response.pagination import PaginationBase | ||||||
| @@ -37,14 +37,19 @@ class IngredientFood(CreateIngredientFood): | |||||||
|     update_at: datetime.datetime | None |     update_at: datetime.datetime | None | ||||||
|  |  | ||||||
|     class Config: |     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 |                 # Keep all other fields as they are | ||||||
|         def getter_dict(cls, name_orm: IngredientFoodModel): |                 else: | ||||||
|             return { |                     return super().get(key, default) | ||||||
|                 **GetterDict(name_orm), |  | ||||||
|                 "extras": {x.key_name: x.value for x in name_orm.extras}, |         orm_mode = True | ||||||
|             } |         getter_dict = _FoodGetter | ||||||
|  |  | ||||||
|  |  | ||||||
| class IngredientFoodPagination(PaginationBase): | class IngredientFoodPagination(PaginationBase): | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user