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 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
|
||||
|
||||
|
||||
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:
|
||||
stmt = select(self.model).filter_by(**self._filter_builder(**{"id": id}))
|
||||
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:
|
||||
from_model = self._get_unit(from_unit)
|
||||
to_model = self._get_unit(to_unit)
|
||||
|
||||
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import enum
|
||||
from enum import StrEnum
|
||||
from fractions import Fraction
|
||||
from typing import ClassVar
|
||||
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):
|
||||
id: UUID4 | None = None
|
||||
name: str
|
||||
|
||||
Reference in New Issue
Block a user