recipes: more complete units.yaml, support parsing of conversions

This commit is contained in:
Emi Vasilek 2023-11-06 03:31:52 +01:00
parent e950977e05
commit a2cbbb2068

View file

@ -10,10 +10,59 @@ class Context:
self.units = Units() self.units = Units()
self.ingredients = Ingredients(self) 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: class Unit:
def __init__(self, name: str) -> None: def load(self, units: List["Unit"], dct: Dict[str, Any]) -> None:
self.name = name 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: class Units:
@ -22,9 +71,10 @@ class Units:
def load(self, lst: List[Any]) -> List[str]: def load(self, lst: List[Any]) -> List[str]:
assert_list(lst) assert_list(lst)
for unit in lst: for unitdct in lst:
assert_type(unit, "", str) unit = Unit()
self.units.append(Unit(unit)) unit.load(self.units, unitdct)
self.units.append(unit)
return [] return []
def get(self, name: str) -> Optional[Unit]: def get(self, name: str) -> Optional[Unit]:
@ -40,7 +90,7 @@ class Ingredient:
def load(self, dct: Dict[str, Any]) -> List[str]: def load(self, dct: Dict[str, Any]) -> List[str]:
issues = [] issues = []
issues += assert_dict(dct, ["name"], ["wdid", "pricedb", "aliases"]) assert_dict(dct, ["name"], ["wdid", "pricedb", "aliases", "conversions"])
assert_type(dct, "name", str) assert_type(dct, "name", str)
self.name = dct["name"] self.name = dct["name"]
@ -63,6 +113,13 @@ class Ingredient:
assert_type(elem, "", str) assert_type(elem, "", str)
self.aliases.append(elem) 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 return issues
@ -108,7 +165,7 @@ class PriceDB:
def load(self, dct: Dict[str, Any]) -> List[str]: def load(self, dct: Dict[str, Any]) -> List[str]:
issues = [] issues = []
issues += assert_dict(dct, ["price"], ["amount", "unit"]) assert_dict(dct, ["price"], ["amount", "unit"])
if isinstance(dct["price"], float): if isinstance(dct["price"], float):
self.price = dct["price"] self.price = dct["price"]
@ -146,7 +203,7 @@ class IngredientInstance:
def load(self, dct: Dict[str, Any]) -> List[str]: def load(self, dct: Dict[str, Any]) -> List[str]:
issues = [] issues = []
issues += assert_dict(dct, ["name"], ["amount", "unit", "note", "or"]) assert_dict(dct, ["name"], ["amount", "unit", "note", "or"])
assert_type(dct, "name", str) assert_type(dct, "name", str)
self.name = dct["name"] self.name = dct["name"]
@ -196,7 +253,7 @@ class RecipePart:
def load(self, dct: Dict[str, Any]) -> List[str]: def load(self, dct: Dict[str, Any]) -> List[str]:
issues = [] issues = []
issues += assert_dict(dct, ["title", "ingredients", "steps"], []) assert_dict(dct, ["title", "ingredients", "steps"], [])
assert_type(dct, "title", str) assert_type(dct, "title", str)
self.title = dct["title"] self.title = dct["title"]
@ -227,7 +284,7 @@ class Recipe:
def load(self, dct: Dict[str, Any]) -> List[str]: def load(self, dct: Dict[str, Any]) -> List[str]:
issues: List[str] = [] issues: List[str] = []
if "parts" in dct: if "parts" in dct:
assert_dict(dct, ["title"], []) assert_dict(dct, ["title"], ["parts"])
assert_type(dct, "title", str) assert_type(dct, "title", str)
self.title = dct["title"] self.title = dct["title"]
@ -246,17 +303,15 @@ class Recipe:
def assert_dict( def assert_dict(
dct: Dict[str, Any], required_keys: List[str], optional_keys: List[str] dct: Dict[str, Any], required_keys: List[str], optional_keys: List[str]
) -> List[str]: ) -> None:
issues = []
if not isinstance(dct, dict): if not isinstance(dct, dict):
raise RuntimeError(f"{dct} has to be a dict") raise RuntimeError(f"{dct} has to be a dict")
for reqkey in required_keys: for reqkey in required_keys:
if reqkey not in dct: 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] extraelems = [x for x in dct.keys() if x not in required_keys + optional_keys]
if len(extraelems) != 0: if len(extraelems) != 0:
issues.append(f"{extraelems} not allowed") raise RuntimeError(f"{extraelems} not allowed")
return issues
def assert_type(dct: Dict[str, Any], key: str, type: type) -> None: def assert_type(dct: Dict[str, Any], key: str, type: type) -> None: