From f4d5fa3e98eaad3958ac14954d343040effe18e1 Mon Sep 17 00:00:00 2001 From: Friendly-Banana <69007475+Friendly-Banana@users.noreply.github.com> Date: Sun, 25 Aug 2024 21:17:14 +0200 Subject: [PATCH 1/3] fix typos --- src/menu_parser.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/menu_parser.py b/src/menu_parser.py index 67c27e98a..fa962a987 100644 --- a/src/menu_parser.py +++ b/src/menu_parser.py @@ -94,7 +94,7 @@ class SelfServiceBasePriceType(Enum): SAUSAGE = (0.5, 0.5, 0.5) MEAT = (1.0, 1.0, 1.0) FISH = (1.5, 1.5, 1.5) - PIZZA_VEGIE = (4.0, 4.5, 5.0) + PIZZA_VEGGIE = (4.0, 4.5, 5.0) PIZZA_MEAT = (4.5, 5.0, 5.5) def __init__(self, p1, p2, p3): @@ -238,12 +238,12 @@ def __get_price(canteen: Canteen, dish: Tuple[str, str, str, str, str], dish_nam if dish[4] == "0": base_price_type = StudentenwerkMenuParser.SelfServiceBasePriceType.PIZZA_MEAT else: - base_price_type = StudentenwerkMenuParser.SelfServiceBasePriceType.PIZZA_VEGIE + base_price_type = StudentenwerkMenuParser.SelfServiceBasePriceType.PIZZA_VEGGIE return StudentenwerkMenuParser.__get_self_service_prices(base_price_type, price_per_unit_type) - base_url: str = "http://www.studierendenwerk-muenchen-oberbayern.de/mensa/speiseplan/speiseplan_{url_id}_-de.html" + base_url: str = "https://www.studierendenwerk-muenchen-oberbayern.de/mensa/speiseplan/speiseplan_{url_id}_-de.html" base_url_with_date: str = ( - "http://www.studierendenwerk-muenchen-oberbayern.de/mensa/speiseplan/speiseplan_{date}_{url_id}_-de.html" + "https://www.studierendenwerk-muenchen-oberbayern.de/mensa/speiseplan/speiseplan_{date}_{url_id}_-de.html" ) def parse(self, canteen: Canteen) -> Optional[Dict[datetime.date, Menu]]: @@ -328,7 +328,7 @@ def __parse_dishes(menu_html: html.Element, canteen: Canteen) -> List[Dish]: "//li[contains(@class, 'c-menu-dish-list__item u-clearfix " "clearfix js-menu__list-item')]/@data-essen-typ", ) - dish_markers_meetless: List[str] = menu_html.xpath( + dish_markers_meatless: List[str] = menu_html.xpath( "//li[contains(@class, 'c-menu-dish-list__item u-clearfix " "clearfix js-menu__list-item')]/@data-essen-fleischlos", ) @@ -341,7 +341,7 @@ def __parse_dishes(menu_html: html.Element, canteen: Canteen) -> List[Dish]: dish_markers_additional, dish_markers_allergen, dish_markers_type, - dish_markers_meetless, + dish_markers_meatless, ) for ( dish_name, @@ -349,14 +349,14 @@ def __parse_dishes(menu_html: html.Element, canteen: Canteen) -> List[Dish]: dish_marker_additional, dish_marker_allergen, dish_marker_type, - dish_marker_meetless, + dish_marker_meatless, ) in dishes_tup: dishes_dict[dish_name] = ( dish_type, dish_marker_additional, dish_marker_allergen, dish_marker_type, - dish_marker_meetless, + dish_marker_meatless, ) # create Dish objects with correct prices; if prices is not available, -1 is used instead @@ -405,7 +405,7 @@ class DishType(Enum): VEGETARIAN = auto() VEGAN = auto() - # if an label is a subclass of another label, + # if a label is a subclass of another label, _label_lookup: Dict[str, Set[Label]] = { "a": {Label.GLUTEN}, "aW": {Label.WHEAT}, @@ -953,13 +953,13 @@ def get_menus(self, text: str, year: int, week_number: int) -> Optional[Dict[dat dishes = [] if soup.name not in ["", "Feiertag"]: dishes.append(soup) - # https://regex101.com/r/MDFu1Z/1 # prepare dish type dish_type = "" if len(dish_types) > 1: dish_type = dish_types[1] + # https://regex101.com/r/MDFu1Z/1 for dish_str in re.split(r"(\n{2,}|(? Date: Sun, 25 Aug 2024 22:35:01 +0200 Subject: [PATCH 2/3] replace insecure http links --- README.md | 4 ++-- src/entities.py | 6 +++--- src/menu_parser.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index cb5d9b379..f4595cd18 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Actions Status](https://github.com/TUM-Dev/eat-api/workflows/CI%2FCD/badge.svg)](https://github.com/TUM-Dev/eat-api/actions) -Simple static API for the canteens of the [Studierendenwerk München Oberbayern](http://www.studierendenwerk-muenchen-oberbayern.de) as well as some other canteens. By now, the following canteens are supported: +Simple static API for the canteens of the [Studierendenwerk München Oberbayern](https://www.studierendenwerk-muenchen-oberbayern.de) as well as some other canteens. By now, the following canteens are supported: | Name | API-key | Address | |:-------------------------------|:-------------------------------|:----------------------------------------------------------------------------------------------------------------------------| @@ -111,7 +111,7 @@ The `canteens.json` and `label.json` are generated from the `Canteen` and `Label - Parser for [OpenMensa](https://openmensa.org) ([GitHub](https://github.com/openmensa/openmensa)) - [Wilhelm Gastronomie im FMI Gebäude der TUM Garching](https://openmensa.org/c/773) - [Konradhofer Catering - Betriebskantine IPP](https://openmensa.org/c/774) -- [Hunger | TUM.sexy](http://tum.sexy/hunger/) ([Github](https://github.com/mammuth/TUM.sexy)) +- [Hunger | TUM.sexy](https://tum.sexy/hunger/) ([Github](https://github.com/mammuth/TUM.sexy)) - `FMeat.php` SDK ([GitHub](https://github.com/jpbernius/fmeat.php)) - [Telegram](https://telegram.org/) bot for [Channel t.me/lunchgfz](https://t.me/lunchgfz) ([GitLab](https://gitlab.com/raabf/lunchgfz-telegram)) - UWP-TUM-Campus-App ([Github](https://github.com/COM8/UWP-TUM-Campus-App)) diff --git a/src/entities.py b/src/entities.py index b541e1ae5..6f9809b15 100644 --- a/src/entities.py +++ b/src/entities.py @@ -52,7 +52,7 @@ def to_json_obj(self): return {"base_price": self.base_price, "price_per_unit": self.price_per_unit, "unit": self.unit} def __hash__(self) -> int: - # http://stackoverflow.com/questions/4005318/how-to-implement-a-good-hash-function-in-python + # https://stackoverflow.com/questions/4005318/how-to-implement-a-good-hash-function-in-python return (hash(self.base_price) << 1) ^ hash(self.price_per_unit) ^ hash(self.unit) @@ -97,7 +97,7 @@ def to_json_obj(self): } def __hash__(self) -> int: - # http://stackoverflow.com/questions/4005318/how-to-implement-a-good-hash-function-in-python + # https://stackoverflow.com/questions/4005318/how-to-implement-a-good-hash-function-in-python return hash(self.students) ^ hash(self.staff) ^ hash(self.guests) @@ -568,7 +568,7 @@ def to_json_obj(self): } def __hash__(self) -> int: - # http://stackoverflow.com/questions/4005318/how-to-implement-a-good-hash-function-in-python + # https://stackoverflow.com/questions/4005318/how-to-implement-a-good-hash-function-in-python return (hash(self.name) << 1) ^ hash(self.prices) ^ hash(frozenset(self.labels)) ^ hash(self.dish_type) diff --git a/src/menu_parser.py b/src/menu_parser.py index fa962a987..19b9b9322 100644 --- a/src/menu_parser.py +++ b/src/menu_parser.py @@ -573,7 +573,7 @@ def __get_label_str_and_price(self, column_index: int, line: str) -> Optional[Tu class IPPBistroMenuParser(MenuParser): canteens = {Canteen.IPP_BISTRO} - url = "http://konradhof-catering.com/ipp/" + url = "https://konradhof-catering.com/ipp/" split_days_regex: Pattern[str] = re.compile( r"(Tagessuppe siehe Aushang|Aushang|Aschermittwoch|Feiertag|Geschlossen)", re.IGNORECASE, From fd77d3ae0489362e0fedd183912b51a58ac3215b Mon Sep 17 00:00:00 2001 From: Friendly-Banana <69007475+Friendly-Banana@users.noreply.github.com> Date: Sun, 25 Aug 2024 23:16:05 +0200 Subject: [PATCH 3/3] introduce dish types --- src/entities.py | 59 +++++++++++++++++++-- src/enum_json_creator.py | 7 ++- src/menu_parser.py | 105 ++++++++++++++++++++++++++----------- src/test/test_openmensa.py | 42 +++++++++------ src/utils/json_util.py | 2 +- 5 files changed, 162 insertions(+), 53 deletions(-) diff --git a/src/entities.py b/src/entities.py index 6f9809b15..0ace41baf 100644 --- a/src/entities.py +++ b/src/entities.py @@ -528,18 +528,71 @@ def to_api_representation(self) -> Dict[str, object]: } +class DishType(ApiRepresentable, Enum): + def __init__(self, text: Dict[Language, str]): + self.text = text + + @staticmethod + def custom(text: str): + class CUSTOM: + def __init__(self, text: str): + self.text = {Language.DE: text, Language.EN: text} + + def to_json_obj(self): + return { + "name": "CUSTOM", + "text": json_util.dict_to_json_dict(self.text), + } + + return CUSTOM(text) + + CUSTOM = {Language.DE: "", Language.EN: ""} + + PASTA = {Language.DE: "Pasta", Language.EN: "Pasta"} + PIZZA = {Language.DE: "Pizza", Language.EN: "Pizza"} + GRILLED = {Language.DE: "Grill", Language.EN: "Grilled"} + WOK = {Language.DE: "Wok", Language.EN: "Wok"} + STUDITOPF = {Language.DE: "Studitopf", Language.EN: "Studitopf"} + MEAT = {Language.DE: "Fleisch", Language.EN: "Meat"} + VEGETARIAN = {Language.DE: "Vegetarisch / fleischlos", Language.EN: "Vegetarian"} + VEGAN = {Language.DE: "Vegan", Language.EN: "Vegan"} + SOUP = {Language.DE: "Tagessupe", Language.EN: "Soup of the day"} + DAILY_DISH = {Language.DE: "Tagesgericht", Language.EN: "Daily dish"} + DESSERT = {Language.DE: "Dessert (Glas)", Language.EN: "Dessert (glass)"} + SIDE_DISH = {Language.DE: "Beilagen", Language.EN: "Side dishes"} + SALAD = {Language.DE: "Salat", Language.EN: "Salad"} + FRUITS = {Language.DE: "Obst", Language.EN: "Fruits"} + FISH = {Language.DE: "Fisch", Language.EN: "Fish"} + SWEET_DISH = {Language.DE: "Süßspeise", Language.EN: "Sweet dish"} + TRADITIONAL = {Language.DE: "Traditionelle Küche", Language.EN: "Traditional cuisine"} + INTERNATIONAL = {Language.DE: "Internationale Küche", Language.EN: "International cuisine"} + SPECIAL = {Language.DE: "Special", Language.EN: "Special"} + + def to_json_obj(self): + return { + "name": self.name, + "text": json_util.dict_to_json_dict(self.text), + } + + def to_api_representation(self) -> Dict[str, object]: + return { + "enum_name": self.name, + "text": json_util.dict_to_json_dict(self.text), + } + + class Dish: name: str prices: Prices labels: Set[Label] - dish_type: str + dish_type: DishType def __init__( self, name: str, prices: Prices, labels: Set[Label], - dish_type: str, + dish_type: DishType, ): self.name = name self.prices = prices @@ -564,7 +617,7 @@ def to_json_obj(self): "name": self.name, "prices": self.prices.to_json_obj(), "labels": sorted(map(lambda l: l.name, self.labels)), - "dish_type": self.dish_type, + "dish_type": self.dish_type.to_json_obj(), } def __hash__(self) -> int: diff --git a/src/enum_json_creator.py b/src/enum_json_creator.py index 8f9dc13ef..d8d0e8a65 100644 --- a/src/enum_json_creator.py +++ b/src/enum_json_creator.py @@ -27,6 +27,11 @@ def write_enum_as_api_representation_to_file(base_dir: str, filename: str, enum_ base_directory = sys.argv[1] if not os.path.exists(base_directory): raise FileNotFoundError(f"There is no such directory '{base_directory}'.") - enum_types = {entities.Canteen: "canteens.json", entities.Label: "labels.json", entities.Language: "languages.json"} + enum_types = { + entities.Canteen: "canteens.json", + entities.Label: "labels.json", + entities.Language: "languages.json", + entities.DishType: "dish_types.json", + } for key, value in enum_types.items(): write_enum_as_api_representation_to_file(base_directory, value, key) diff --git a/src/menu_parser.py b/src/menu_parser.py index 19b9b9322..967460081 100644 --- a/src/menu_parser.py +++ b/src/menu_parser.py @@ -7,15 +7,15 @@ import tempfile import unicodedata from abc import ABC, abstractmethod -from enum import Enum, auto +from enum import Enum from subprocess import call # nosec: all the inputs is fully defined -from typing import Dict, List, Optional, Pattern, Set, Tuple +from typing import Dict, List, Optional, Pattern, Set, Tuple, Union from warnings import warn import requests # type: ignore from lxml import html # nosec: https://github.com/TUM-Dev/eat-api/issues/19 -from entities import Canteen, Dish, Label, Menu, Price, Prices, Week +from entities import Canteen, Dish, DishType, Label, Menu, Price, Prices, Week from utils import util @@ -29,6 +29,7 @@ class MenuParser(ABC): """ canteens: Set[Canteen] + _dish_type_lookup: Dict[str, DishType] _label_lookup: Dict[str, Set[Label]] # we use datetime %u, so we go from 1-7 weekday_positions: Dict[str, int] = {"mon": 1, "tue": 2, "wed": 3, "thu": 4, "fri": 5, "sat": 6, "sun": 7} @@ -58,6 +59,13 @@ def _parse_label(cls, labels_str: str) -> Set[Label]: Label.add_supertype_labels(labels) return labels + @classmethod + def _parse_dish_type(cls, dish_type_str: str) -> Union[str, DishType]: + if dish_type_str not in cls._dish_type_lookup: + warn(f"Unknown dish type: {dish_type_str}") + return DishType.custom(dish_type_str) + return cls._dish_type_lookup.get(dish_type_str, dish_type_str) + class StudentenwerkMenuParser(MenuParser): # Canteens: @@ -112,6 +120,24 @@ def __init__(self, students: float, staff: float, guests: float): self.guests = guests self.unit = "100g" + _dish_type_lookup = { + "Pizza": DishType.PIZZA, + "Pasta": DishType.PASTA, + "Grill": DishType.GRILLED, + "Wok": DishType.WOK, + "Studitopf": DishType.STUDITOPF, + "Vegetarisch/fleischlos": DishType.VEGETARIAN, + "Vegetarisch / fleischlos": DishType.VEGETARIAN, + "Tagessupe": DishType.SOUP, # this typo is in the original data + "Dessert (Glas)": DishType.DESSERT, + "Fleisch": DishType.MEAT, + "Fisch": DishType.FISH, + "Süßspeise": DishType.SWEET_DISH, + "Vegan": DishType.VEGAN, + "Beilagen": DishType.SIDE_DISH, + "Aktion": DishType.SPECIAL, + } + _label_lookup: Dict[str, Set[Label]] = { "GQB": {Label.BAVARIA}, "MSC": {Label.MSC}, @@ -362,6 +388,7 @@ def __parse_dishes(menu_html: html.Element, canteen: Canteen) -> List[Dish]: # create Dish objects with correct prices; if prices is not available, -1 is used instead dishes: List[Dish] = [] for name in dishes_dict: + dish_type = StudentenwerkMenuParser._parse_dish_type(dishes_dict[name][0]) # parse labels labels = set() labels |= StudentenwerkMenuParser._parse_label(dishes_dict[name][1]) @@ -370,7 +397,7 @@ def __parse_dishes(menu_html: html.Element, canteen: Canteen) -> List[Dish]: StudentenwerkMenuParser.__add_diet(labels, dishes_dict[name][4]) # do not price side dishes prices: Prices - if dishes_dict[name][0] == "Beilagen": + if dish_type == DishType.SIDE_DISH: # set classic prices without any base price prices = StudentenwerkMenuParser.__get_self_service_prices( StudentenwerkMenuParser.SelfServiceBasePriceType.VEGETARIAN_SOUP_STEW, @@ -379,7 +406,7 @@ def __parse_dishes(menu_html: html.Element, canteen: Canteen) -> List[Dish]: else: # find prices prices = StudentenwerkMenuParser.__get_price(canteen, dishes_dict[name], name) - dishes.append(Dish(name, prices, labels, dishes_dict[name][0])) + dishes.append(Dish(name, prices, labels, dish_type)) return dishes @staticmethod @@ -398,12 +425,7 @@ class FMIBistroMenuParser(MenuParser): url = "https://www.wilhelm-gastronomie.de/.cm4all/mediadb/Speiseplan_Garching_KW{calendar_week}_{year}.pdf" canteens = {Canteen.FMI_BISTRO} - - class DishType(Enum): - SOUP = auto() - MEAT = auto() - VEGETARIAN = auto() - VEGAN = auto() + dish_types = DishType.SOUP, DishType.MEAT, DishType.VEGETARIAN, DishType.VEGAN # if a label is a subclass of another label, _label_lookup: Dict[str, Set[Label]] = { @@ -472,7 +494,7 @@ def get_menus(self, text: str, year: int, calendar_week: int) -> Dict[datetime.d for date in Week.get_non_weekend_days_for_calendar_week(year, calendar_week): dishes = [] dish_title_parts = [] - dish_type_iterator = iter(FMIBistroMenuParser.DishType) + dish_type_iterator = iter(self.dish_types) for line in lines: if "€" not in line: @@ -497,7 +519,7 @@ def get_menus(self, text: str, year: int, calendar_week: int) -> Dict[datetime.d # merge title lines and replace subsequent whitespaces with single " " dish_title = re.sub(r"\s+", " ", " ".join(dish_title_parts)) - dishes += [Dish(dish_title, dish_prices, labels, str(dish_type))] + dishes += [Dish(dish_title, dish_prices, labels, dish_type)] dish_title_parts = [] if dishes: @@ -717,7 +739,7 @@ def get_menus(self, text, year, week_number): lines_weekdays = {"mon": "", "tue": "", "wed": "", "thu": "", "fri": ""} # it must be lines[3:] instead of lines[2:] or else the menus would start with "Preis ab 0,90€" (from the - # soups) instead of the first menu, if there is a day where the bistro is closed. + # soups) instead of the first menu, if there is a day when the bistro is closed. for line in lines[soup_line_index + 3 :]: # noqa: E203 lines_weekdays["mon"] += " " + line[pos_mon:pos_tue].replace("\n", " ") lines_weekdays["tue"] += " " + line[pos_tue:pos_wed].replace("\n", " ") @@ -726,7 +748,7 @@ def get_menus(self, text, year, week_number): lines_weekdays["fri"] += " " + line[pos_fri:].replace("\n", " ") for key in lines_weekdays: - # Appends `?€` to „Überraschungsmenü“ if it do not have a price. The second '€' is a separator for the + # Appends `?€` to „Überraschungsmenü“ if it does not have a price. The second '€' is a separator for the # later split # pylint:disable=E4702 lines_weekdays[key] = self.surprise_without_price_regex.sub(r"\g<1>?€ € \g<2>", lines_weekdays[key]) @@ -740,12 +762,12 @@ def get_menus(self, text, year, week_number): # create dish types # since we have the same dish types every day we can use them if there are 4 dishes available if len(dish_names_price) == 4: - dish_types = ["Veggie", "Traditionelle Küche", "Internationale Küche", "Specials"] + dish_types = [DishType.VEGETARIAN, DishType.TRADITIONAL, DishType.INTERNATIONAL, DishType.SPECIAL] else: - dish_types = ["Tagesgericht"] * len(dish_names_price) + dish_types = [DishType.DAILY_DISH] * len(dish_names_price) # create labels - # all dishes have the same ingridients + # all dishes have the same ingredients # TODO: switch to new label and Canteen enum # labels = IngredientsOld("ipp-bistro") # labels.parse_labels("Mi,Gl,Sf,Sl,Ei,Se,4") @@ -839,7 +861,7 @@ def parse_dish(self, dish_str): dish_price = Prices(Price(float(match[0].replace("€", "").replace(",", ".").strip()))) dish_str = re.sub(self.price_regex, "", dish_str) - return Dish(dish_str, dish_price, labels, "Tagesgericht") + return Dish(dish_str, dish_price, labels, DishType.DAILY_DISH) def parse(self, canteen: Canteen) -> Optional[Dict[datetime.date, Menu]]: page = requests.get(self.startPageurl, timeout=10.0) @@ -886,7 +908,7 @@ def get_menus(self, text: str, year: int, week_number: int) -> Optional[Dict[dat lines = text.splitlines() # get dish types - # its the line before the first "***..." line + # it's the line before the first "***..." line dish_types_line = "" last_non_empty_line = -1 for i, line in enumerate(lines): @@ -947,9 +969,9 @@ def get_menus(self, text: str, year: int, week_number: int) -> Optional[Dict[dat soup_str = soup_str.replace("-\n", "").strip().replace("\n", " ") soup = self.parse_dish(soup_str) if len(dish_types) > 0: - soup.dish_type = dish_types[0] + soup.dish_type = DishType.custom(dish_types[0]) else: - soup.dish_type = "Suppe" + soup.dish_type = DishType.SOUP dishes = [] if soup.name not in ["", "Feiertag"]: dishes.append(soup) @@ -957,13 +979,13 @@ def get_menus(self, text: str, year: int, week_number: int) -> Optional[Dict[dat # prepare dish type dish_type = "" if len(dish_types) > 1: - dish_type = dish_types[1] + dish_type = DishType.custom(dish_types[1]) # https://regex101.com/r/MDFu1Z/1 for dish_str in re.split(r"(\n{2,}|(? Optional[Dict[dat class StraubingMensaMenuParser(MenuParser): url = "https://www.stwno.de/infomax/daten-extern/csv/HS-SR/{calendar_week}.csv" canteens = {Canteen.MENSA_STRAUBING} + _dish_type_lookup = { + "Suppe": DishType.SOUP, + "HG1": DishType.DAILY_DISH, + "HG2": DishType.DAILY_DISH, + "B1": DishType.SIDE_DISH, + "B2": DishType.SIDE_DISH, + "B3": DishType.SIDE_DISH, + "N1": DishType.DESSERT, + "N2": DishType.DESSERT, + } _label_lookup: Dict[str, Set[Label]] = { "1": {Label.DYESTUFF}, @@ -1083,13 +1115,13 @@ def parse_menu(self, rows: List[List[str]]) -> Dict[datetime.date, Menu]: return menus def parse_dish(self, data: List[str]) -> Dish: - labels: List[Label] = [] + labels: Set[Label] = set() title = data[3] bracket = title.rfind("(") # find bracket that encloses labels if bracket != -1: - labels.extend(self._parse_label(title[bracket:].replace("(", "").replace(")", ""))) + labels.union(self._parse_label(title[bracket:].replace("(", "").replace(")", ""))) title = title[:bracket].strip() # prices are given as string with , instead of . as separator @@ -1098,15 +1130,15 @@ def parse_dish(self, data: List[str]) -> Dish: Price(float(data[7].replace(",", "."))), Price(float(data[8].replace(",", "."))), ) - dish_type = data[2] + dish_type = self._parse_dish_type(data[2]) marks = data[4] - labels.extend(self._marks_to_labels(marks)) + labels.union(self._marks_to_labels(marks)) return Dish(title, prices, labels, dish_type) @classmethod - def _marks_to_labels(cls, marks: str) -> List[Label]: + def _marks_to_labels(cls, marks: str) -> Set[Label]: mark_to_label = { "VG": [Label.VEGAN, Label.VEGETARIAN], "V": [Label.VEGETARIAN], @@ -1119,9 +1151,9 @@ def _marks_to_labels(cls, marks: str) -> List[Label]: "W": [Label.WILD_MEAT], } - labels = [] + labels: Set[Label] = set() for mark in marks.split(","): - labels.extend(mark_to_label.get(mark, [])) + labels.union(mark_to_label.get(mark, [])) return labels @@ -1130,6 +1162,15 @@ class MensaBildungscampusHeilbronnParser(MenuParser): base_url = "https://openmensa.org/api/v2/canteens/277" canteens = {Canteen.MENSA_BILDUNGSCAMPUS_HEILBRONN} + _dish_type_lookup = { + "Suppentopf": DishType.SOUP, + "Tierisch": DishType.MEAT, + "Dessert": DishType.DESSERT, + "Dessert vegan": DishType.DESSERT, + "Vegan": DishType.VEGAN, + "Vegetarisch": DishType.VEGETARIAN, + } + def parse(self, canteen: Canteen) -> Optional[Dict[datetime.date, Menu]]: menus = {} @@ -1142,7 +1183,7 @@ def mutate(element): Price(0, element["prices"]["others"], "Portion"), ), set(), - element["category"], + self._parse_dish_type(element["category"]), ) for date in self.__get_available_dates(): diff --git a/src/test/test_openmensa.py b/src/test/test_openmensa.py index 67a897645..3f439df22 100644 --- a/src/test/test_openmensa.py +++ b/src/test/test_openmensa.py @@ -4,7 +4,7 @@ from pyopenmensa.feed import LazyBuilder from src import openmensa -from src.entities import Dish, Label, Menu, Price, Prices, Week +from src.entities import Dish, DishType, Label, Menu, Price, Prices, Week class OpenMensaTest(TestCase): @@ -15,7 +15,7 @@ def test_should_add_dish_to_canteen(self): "Gulasch vom Schwein", Prices(Price(1.9)), {Label.PORK, Label.GLUTEN, Label.BARLEY, Label.WHEAT, Label.GARLIC, Label.MILK}, - "Tagesgericht", + DishType.DAILY_DISH, ) openmensa.addDishToCanteen(dish, dateobj, canteen) @@ -33,60 +33,70 @@ def test_should_add_week_to_canteen(self): "Pochiertes Lachsfilet mit Dillsoße dazu Minze-Reis", Prices(Price(6.5)), {Label.CELERY, Label.MILK}, - "Tagesgericht", + DishType.DAILY_DISH, ) - dish1_mon2 = Dish("Dampfkartoffeln mit Zucchinigemüse", Prices(Price(3.6)), {Label.CELERY}, "Tagesgericht") + dish1_mon2 = Dish("Dampfkartoffeln mit Zucchinigemüse", Prices(Price(3.6)), {Label.CELERY}, DishType.DAILY_DISH) dish2_mon2 = Dish( "Valess-Schnitzel mit Tomaten-Couscous", Prices(Price(4.3)), {Label.CELERY, Label.GLUTEN, Label.CHICKEN_EGGS, Label.MILK}, - "Tagesgericht", + DishType.DAILY_DISH, ) dish3_mon2 = Dish( "Kasslerpfanne mit frischen Champignons und Spätzle", Prices(Price(4.9)), {Label.CELERY, Label.MILK}, - "Tagesgericht", + DishType.DAILY_DISH, + ) + dish1_tue2 = Dish( + "Gemüsereispfanne mit geräuchertem Tofu", + Prices(Price(3.6)), + {Label.CELERY}, + DishType.DAILY_DISH, ) - dish1_tue2 = Dish("Gemüsereispfanne mit geräuchertem Tofu", Prices(Price(3.6)), {Label.CELERY}, "Tagesgericht") dish2_tue2 = Dish( "Schweineschnitzel in Karottenpanade mit Rosmarin- Risoleekartoffeln", Prices(Price(5.3)), {Label.CELERY, Label.GLUTEN, Label.CHICKEN_EGGS}, - "Tagesgericht", + DishType.DAILY_DISH, + ) + dish1_wed2 = Dish( + "Spaghetti al Pomodoro", + Prices(Price(3.6)), + {Label.CELERY, Label.GLUTEN}, + DishType.DAILY_DISH, ) - dish1_wed2 = Dish("Spaghetti al Pomodoro", Prices(Price(3.6)), {Label.CELERY, Label.GLUTEN}, "Tagesgericht") dish2_wed2 = Dish( "Krustenbraten vom Schwein mit Kartoffelknödel und Krautsalat", Prices(Price(5.3)), {Label.CELERY, Label.GLUTEN}, - "Tagesgericht", + DishType.DAILY_DISH, ) dish1_thu2 = Dish( "Red-Thaicurrysuppe mit Gemüse und Kokosmilch", Prices(Price(2.9)), {Label.CELERY}, - "Tagesgericht", + DishType.DAILY_DISH, ) dish2_thu2 = Dish( "Senf-Eier mit Salzkartoffeln", Prices(Price(3.8)), {Label.CELERY, Label.MUSTARD, Label.MILK}, - "Tagesgericht", + DishType.DAILY_DISH, ) dish3_thu2 = Dish( "Putengyros mit Zaziki und Tomatenreis", Prices(Price(5.3)), {Label.CELERY, Label.MILK}, - "Tagesgericht", + DishType.DAILY_DISH, ) - dish1_fri2 = Dish("Spiralnudeln mit Ratatouillegemüse", Prices(Price(3.6)), {Label.GLUTEN}, "Tagesgericht") - dish2_fri2 = Dish("Milchreis mit warmen Sauerkirschen", Prices(Price(3)), {Label.MILK}, "Tagesgericht") + dish1_fri2 = Dish("Spiralnudeln mit Ratatouillegemüse", Prices(Price(3.6)), {Label.GLUTEN}, DishType.DAILY_DISH) + dish2_fri2 = Dish("Milchreis mit warmen Sauerkirschen", Prices(Price(3)), {Label.MILK}, DishType.DAILY_DISH) dish3_fri2 = Dish( "Lasagne aus Seelachs und Blattspinat", Prices(Price(5.3)), {Label.CELERY, Label.GLUTEN, Label.MILK}, - "Tagesgericht", + DishType.DAILY_DISH, ) menu_mon2 = Menu(date_mon2, [dish_aktion2, dish1_mon2, dish2_mon2, dish3_mon2]) menu_tue2 = Menu(date_tue2, [dish_aktion2, dish1_tue2, dish2_tue2]) diff --git a/src/utils/json_util.py b/src/utils/json_util.py index efdcb2388..634c36e66 100644 --- a/src/utils/json_util.py +++ b/src/utils/json_util.py @@ -13,7 +13,7 @@ def default(self, o: Any) -> Any: def order_json_objects(obj): """ - Recusively orders all elemts in a Json object. + Recursively orders all elements in a Json object. Source: https://stackoverflow.com/questions/25851183/how-to-compare-two-json-objects-with-the-same-elements-in-a-different-order-equa """