From a2cbbb20687dd79a8f6f0e9d8ff3eb88ac427190 Mon Sep 17 00:00:00 2001 From: Emi Vasilek Date: Mon, 6 Nov 2023 03:31:52 +0100 Subject: [PATCH] recipes: more complete units.yaml, support parsing of conversions --- recipes.py | 85 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 70 insertions(+), 15 deletions(-) diff --git a/recipes.py b/recipes.py index c037e30..3b00fbf 100644 --- a/recipes.py +++ b/recipes.py @@ -10,10 +10,59 @@ class Context: self.units = Units() self.ingredients = Ingredients(self) +class Conversion: + def load(self, units: List["Unit"], dct: Dict[str, Any]) -> None: + def find_unit(name: str) -> Optional[Unit]: + for unit in units: + if unit.name == name: + return unit + return None + + assert_dict(dct, ["from", "to", "ratio"], []) + + assert_type(dct, "from", str) + fromunit = find_unit(dct["from"]) + if fromunit is None: + raise RuntimeError(f"unit {dct['from']} doesn't exist") + self.fromunit = fromunit + + assert_type(dct, "to", str) + tounit = find_unit(dct["to"]) + if tounit is None: + raise RuntimeError(f"unit {tounit} doesn't exist") + self.tounit = tounit + + # TODO: or float + assert_type(dct, "ratio", int) + self.ratio = dct["ratio"] class Unit: - def __init__(self, name: str) -> None: - self.name = name + def load(self, units: List["Unit"], dct: Dict[str, Any]) -> None: + assert_dict(dct, ["name"], ["conversions", "aliases"]) + + assert_type(dct, "name", str) + self.name = dct["name"] + + unitsx = units[:] + unitsx.append(self) + + self.conversions: List[Conversion] = [] + if "conversions" in dct: + assert_list(dct["conversions"]) + for convdct in dct["conversions"]: + if "from" in dct["conversions"]: + raise RuntimeError(f"conversions in units.yaml cannot have a from field, it is automatically assigned from the unit name") + convdct["from"] = self.name + conversion = Conversion() + conversion.load(unitsx, convdct) + self.conversions.append(conversion) + + self.aliases: List[str] = [] + if "aliases" in dct: + assert_list(dct["aliases"]) + for alias in dct["aliases"]: + assert_type(alias, "", str) + self.aliases.append(alias) class Units: @@ -22,9 +71,10 @@ class Units: def load(self, lst: List[Any]) -> List[str]: assert_list(lst) - for unit in lst: - assert_type(unit, "", str) - self.units.append(Unit(unit)) + for unitdct in lst: + unit = Unit() + unit.load(self.units, unitdct) + self.units.append(unit) return [] def get(self, name: str) -> Optional[Unit]: @@ -40,7 +90,7 @@ class Ingredient: def load(self, dct: Dict[str, Any]) -> List[str]: issues = [] - issues += assert_dict(dct, ["name"], ["wdid", "pricedb", "aliases"]) + assert_dict(dct, ["name"], ["wdid", "pricedb", "aliases", "conversions"]) assert_type(dct, "name", str) self.name = dct["name"] @@ -63,6 +113,13 @@ class Ingredient: assert_type(elem, "", str) self.aliases.append(elem) + self.conversions = [] + if "conversions" in dct: + assert_list(dct["conversions"]) + for dct in dct["conversions"]: + conversion = Conversion() + conversion.load(self.ctx.units.units, dct) + self.conversions.append(conversion) return issues @@ -108,7 +165,7 @@ class PriceDB: def load(self, dct: Dict[str, Any]) -> List[str]: issues = [] - issues += assert_dict(dct, ["price"], ["amount", "unit"]) + assert_dict(dct, ["price"], ["amount", "unit"]) if isinstance(dct["price"], float): self.price = dct["price"] @@ -146,7 +203,7 @@ class IngredientInstance: def load(self, dct: Dict[str, Any]) -> List[str]: issues = [] - issues += assert_dict(dct, ["name"], ["amount", "unit", "note", "or"]) + assert_dict(dct, ["name"], ["amount", "unit", "note", "or"]) assert_type(dct, "name", str) self.name = dct["name"] @@ -196,7 +253,7 @@ class RecipePart: def load(self, dct: Dict[str, Any]) -> List[str]: issues = [] - issues += assert_dict(dct, ["title", "ingredients", "steps"], []) + assert_dict(dct, ["title", "ingredients", "steps"], []) assert_type(dct, "title", str) self.title = dct["title"] @@ -227,7 +284,7 @@ class Recipe: def load(self, dct: Dict[str, Any]) -> List[str]: issues: List[str] = [] if "parts" in dct: - assert_dict(dct, ["title"], []) + assert_dict(dct, ["title"], ["parts"]) assert_type(dct, "title", str) self.title = dct["title"] @@ -246,17 +303,15 @@ class Recipe: def assert_dict( dct: Dict[str, Any], required_keys: List[str], optional_keys: List[str] -) -> List[str]: - issues = [] +) -> None: if not isinstance(dct, dict): raise RuntimeError(f"{dct} has to be a dict") for reqkey in required_keys: if reqkey not in dct: - issues.append(f"{reqkey} is required") + raise RuntimeError(f"{reqkey} is required") extraelems = [x for x in dct.keys() if x not in required_keys + optional_keys] if len(extraelems) != 0: - issues.append(f"{extraelems} not allowed") - return issues + raise RuntimeError(f"{extraelems} not allowed") def assert_type(dct: Dict[str, Any], key: str, type: type) -> None: