mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-02-25 17:23:11 -05:00
inject known standardized units upon unit creation
This commit is contained in:
@@ -1,17 +1,114 @@
|
|||||||
from pydantic import UUID4
|
from collections.abc import Iterable
|
||||||
|
|
||||||
|
from pydantic import UUID4, BaseModel
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
|
|
||||||
from mealie.db.models.recipe.ingredient import IngredientUnitModel
|
from mealie.db.models.recipe.ingredient import IngredientUnitModel
|
||||||
from mealie.schema.recipe.recipe_ingredient import IngredientUnit
|
from mealie.lang.providers import get_locale_context
|
||||||
|
from mealie.schema.recipe.recipe_ingredient import IngredientUnit, StandardizedUnitType
|
||||||
|
|
||||||
from .repository_generic import GroupRepositoryGeneric
|
from .repository_generic import GroupRepositoryGeneric
|
||||||
|
|
||||||
|
|
||||||
class RepositoryUnit(GroupRepositoryGeneric[IngredientUnit, IngredientUnitModel]):
|
class RepositoryUnit(GroupRepositoryGeneric[IngredientUnit, IngredientUnitModel]):
|
||||||
|
_standardized_unit_map: dict[str, str] | None = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def standardized_unit_map(self) -> dict[str, str]:
|
||||||
|
"""A map of potential known units to its standardized name in our seed data"""
|
||||||
|
|
||||||
|
if self._standardized_unit_map is None:
|
||||||
|
from .seed.seeders import IngredientUnitsSeeder
|
||||||
|
|
||||||
|
ctx = get_locale_context()
|
||||||
|
if ctx:
|
||||||
|
locale = ctx[1].key
|
||||||
|
else:
|
||||||
|
locale = None
|
||||||
|
|
||||||
|
self._standardized_unit_map = {}
|
||||||
|
locale_file = IngredientUnitsSeeder.get_file(locale=locale)
|
||||||
|
for unit_key, unit in IngredientUnitsSeeder.load_file(locale_file).items():
|
||||||
|
for prop in ["name", "plural_name", "abbreviation"]:
|
||||||
|
val = unit.get(prop)
|
||||||
|
if val and isinstance(val, str):
|
||||||
|
self._standardized_unit_map[val.strip().lower()] = unit_key
|
||||||
|
|
||||||
|
return self._standardized_unit_map
|
||||||
|
|
||||||
def _get_unit(self, id: UUID4) -> IngredientUnitModel:
|
def _get_unit(self, id: UUID4) -> IngredientUnitModel:
|
||||||
stmt = select(self.model).filter_by(**self._filter_builder(**{"id": id}))
|
stmt = select(self.model).filter_by(**self._filter_builder(**{"id": id}))
|
||||||
return self.session.execute(stmt).scalars().one()
|
return self.session.execute(stmt).scalars().one()
|
||||||
|
|
||||||
|
def _add_standardized_unit(self, data: BaseModel | dict) -> dict:
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
data = data.model_dump()
|
||||||
|
|
||||||
|
for prop in ["name", "plural_name", "abbreviation", "plural_abbreviation"]:
|
||||||
|
val = data.get(prop)
|
||||||
|
if not (val and isinstance(val, str)):
|
||||||
|
continue
|
||||||
|
|
||||||
|
standardized_unit_key = self.standardized_unit_map.get(val.strip().lower())
|
||||||
|
if not standardized_unit_key:
|
||||||
|
continue
|
||||||
|
|
||||||
|
match standardized_unit_key:
|
||||||
|
case "teaspoon":
|
||||||
|
data["standard_quantity"] = 1 / 6
|
||||||
|
data["standard_unit"] = StandardizedUnitType.FLUID_OUNCE
|
||||||
|
case "tablespoon":
|
||||||
|
data["standard_quantity"] = 1 / 2
|
||||||
|
data["standard_unit"] = StandardizedUnitType.FLUID_OUNCE
|
||||||
|
case "cup":
|
||||||
|
data["standard_quantity"] = 1
|
||||||
|
data["standard_unit"] = StandardizedUnitType.CUP
|
||||||
|
case "fluid-ounce":
|
||||||
|
data["standard_quantity"] = 1
|
||||||
|
data["standard_unit"] = StandardizedUnitType.FLUID_OUNCE
|
||||||
|
case "pint":
|
||||||
|
data["standard_quantity"] = 2
|
||||||
|
data["standard_unit"] = StandardizedUnitType.CUP
|
||||||
|
case "quart":
|
||||||
|
data["standard_quantity"] = 4
|
||||||
|
data["standard_unit"] = StandardizedUnitType.CUP
|
||||||
|
case "gallon":
|
||||||
|
data["standard_quantity"] = 16
|
||||||
|
data["standard_unit"] = StandardizedUnitType.CUP
|
||||||
|
case "milliliter":
|
||||||
|
data["standard_quantity"] = 1
|
||||||
|
data["standard_unit"] = StandardizedUnitType.MILLILITER
|
||||||
|
case "liter":
|
||||||
|
data["standard_quantity"] = 1
|
||||||
|
data["standard_unit"] = StandardizedUnitType.LITER
|
||||||
|
case "pound":
|
||||||
|
data["standard_quantity"] = 1
|
||||||
|
data["standard_unit"] = StandardizedUnitType.POUND
|
||||||
|
case "ounce":
|
||||||
|
data["standard_quantity"] = 1
|
||||||
|
data["standard_unit"] = StandardizedUnitType.OUNCE
|
||||||
|
case "gram":
|
||||||
|
data["standard_quantity"] = 1
|
||||||
|
data["standard_unit"] = StandardizedUnitType.GRAM
|
||||||
|
case "kilogram":
|
||||||
|
data["standard_quantity"] = 1
|
||||||
|
data["standard_unit"] = StandardizedUnitType.KILOGRAM
|
||||||
|
case "milligram":
|
||||||
|
data["standard_quantity"] = 1 / 1000
|
||||||
|
data["standard_unit"] = StandardizedUnitType.GRAM
|
||||||
|
case _:
|
||||||
|
continue
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def create(self, data: IngredientUnit | dict) -> IngredientUnit:
|
||||||
|
data = self._add_standardized_unit(data)
|
||||||
|
return super().create(data)
|
||||||
|
|
||||||
|
def create_many(self, data: Iterable[IngredientUnit | dict]) -> list[IngredientUnit]:
|
||||||
|
data = [self._add_standardized_unit(i) for i in data]
|
||||||
|
return super().create_many(data)
|
||||||
|
|
||||||
def merge(self, from_unit: UUID4, to_unit: UUID4) -> IngredientUnit | None:
|
def merge(self, from_unit: UUID4, to_unit: UUID4) -> IngredientUnit | None:
|
||||||
from_model = self._get_unit(from_unit)
|
from_model = self._get_unit(from_unit)
|
||||||
to_model = self._get_unit(to_unit)
|
to_model = self._get_unit(to_unit)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import enum
|
import enum
|
||||||
|
from enum import StrEnum
|
||||||
from fractions import Fraction
|
from fractions import Fraction
|
||||||
from typing import ClassVar
|
from typing import ClassVar
|
||||||
from uuid import UUID, uuid4
|
from uuid import UUID, uuid4
|
||||||
@@ -34,6 +35,28 @@ def display_fraction(fraction: Fraction):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class StandardizedUnitType(StrEnum):
|
||||||
|
"""
|
||||||
|
An arbitrary list of standardized units supported by unit conversions.
|
||||||
|
The backend doesn't really care what standardized unit you use, as long as it's recognized,
|
||||||
|
but defining them here keeps it consistant with the frontend.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Imperial
|
||||||
|
FLUID_OUNCE = "fluid ounce"
|
||||||
|
CUP = "cup"
|
||||||
|
|
||||||
|
OUNCE = "ounce"
|
||||||
|
POUND = "pound"
|
||||||
|
|
||||||
|
# Metric
|
||||||
|
MILLILITER = "milliliter"
|
||||||
|
LITER = "liter"
|
||||||
|
|
||||||
|
GRAM = "gram"
|
||||||
|
KILOGRAM = "kilogram"
|
||||||
|
|
||||||
|
|
||||||
class UnitFoodBase(MealieModel):
|
class UnitFoodBase(MealieModel):
|
||||||
id: UUID4 | None = None
|
id: UUID4 | None = None
|
||||||
name: str
|
name: str
|
||||||
|
|||||||
Reference in New Issue
Block a user