mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-06-03 13:44:55 -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:
parent
666085b9ca
commit
9d35b0923a
@ -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):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user