From a8583c8e6901b8014e8ab21aa2c8a098377344f9 Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Sat, 9 Dec 2023 17:12:07 +0000 Subject: [PATCH 001/130] added backend translation support for plurals --- mealie/pkgs/i18n/json_provider.py | 26 ++++++++++++++++--- .../pkgs/i18n/test_locale_provider.py | 23 ++++++++++++++++ 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/mealie/pkgs/i18n/json_provider.py b/mealie/pkgs/i18n/json_provider.py index 34cc7710c458..5fbeb10c902d 100644 --- a/mealie/pkgs/i18n/json_provider.py +++ b/mealie/pkgs/i18n/json_provider.py @@ -1,6 +1,7 @@ import json from dataclasses import dataclass from pathlib import Path +from typing import cast @dataclass(slots=True) @@ -13,6 +14,22 @@ class JsonProvider: else: self.translations = path + def _parse_plurals(self, value: str, count: float): + # based off of: https://kazupon.github.io/vue-i18n/guide/pluralization.html + + values = [v.strip() for v in value.split("|")] + if len(values) == 1: + return value + elif len(values) == 2: + return values[0] if count == 1 else values[1] + elif len(values) == 3: + if count == 0: + return values[0] + else: + return values[1] if count == 1 else values[2] + else: + return values[0] + def t(self, key: str, default=None, **kwargs) -> str: keys = key.split(".") @@ -30,9 +47,12 @@ class JsonProvider: if i == last: for key, value in kwargs.items(): - if not value: + translation_value = cast(str, translation_value) + if value is None: value = "" - translation_value = translation_value.replace("{" + key + "}", value) - return translation_value + if key == "count": + translation_value = self._parse_plurals(translation_value, float(value)) + translation_value = translation_value.replace("{" + key + "}", str(value)) # type: ignore + return translation_value # type: ignore return default or key diff --git a/tests/unit_tests/pkgs/i18n/test_locale_provider.py b/tests/unit_tests/pkgs/i18n/test_locale_provider.py index 73100fb739a6..f4bf3a550521 100644 --- a/tests/unit_tests/pkgs/i18n/test_locale_provider.py +++ b/tests/unit_tests/pkgs/i18n/test_locale_provider.py @@ -9,6 +9,29 @@ def test_json_provider(): assert provider.t("test2", "DEFAULT") == "DEFAULT" +def test_json_provider_plural(): + provider = JsonProvider({"test": "test | tests"}) + assert provider.t("test", count=0) == "tests" + assert provider.t("test", count=0.5) == "tests" + assert provider.t("test", count=1) == "test" + assert provider.t("test", count=1.5) == "tests" + assert provider.t("test", count=2) == "tests" + + provider = JsonProvider({"test": "test 0 | test | tests"}) + assert provider.t("test", count=0) == "test 0" + assert provider.t("test", count=0.5) == "tests" + assert provider.t("test", count=1) == "test" + assert provider.t("test", count=1.5) == "tests" + assert provider.t("test", count=2) == "tests" + + provider = JsonProvider({"test": "zero tests | one test | {count} tests"}) + assert provider.t("test", count=0) == "zero tests" + assert provider.t("test", count=0.5) == "0.5 tests" + assert provider.t("test", count=1) == "one test" + assert provider.t("test", count=1.5) == "1.5 tests" + assert provider.t("test", count=2) == "2 tests" + + def test_json_provider_nested_keys(): nested_dict = { "root": { From 2cfc63b3026e69b4b654180f915b6f02f4809b61 Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Sat, 9 Dec 2023 17:19:06 +0000 Subject: [PATCH 002/130] added timedelta translations --- mealie/lang/messages/en-US.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/mealie/lang/messages/en-US.json b/mealie/lang/messages/en-US.json index a4990159d5d5..16bee3cc7743 100644 --- a/mealie/lang/messages/en-US.json +++ b/mealie/lang/messages/en-US.json @@ -31,5 +31,14 @@ "generic-updated-with-url": "{name} has been updated, {url}", "generic-duplicated": "{name} has been duplicated", "generic-deleted": "{name} has been deleted" + }, + "datetime": { + "year": "year|years", + "day": "day|days", + "hour": "hour|hours", + "minute": "minute|minutes", + "second": "second|seconds", + "millisecond": "millisecond|milliseconds", + "microsecond": "microsecond|microseconds" } } From 408df286fd0d8dbf883731ae0a3950d86066be60 Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Sat, 9 Dec 2023 17:19:19 +0000 Subject: [PATCH 003/130] added translator to scraper --- mealie/routes/groups/controller_migrations.py | 1 + mealie/routes/recipe/recipe_crud_routes.py | 6 +-- mealie/services/migrations/_migration_base.py | 13 +++++- mealie/services/scraper/cleaner.py | 40 ++++++++++--------- .../services/scraper/recipe_bulk_scraper.py | 8 +++- mealie/services/scraper/recipe_scraper.py | 6 ++- mealie/services/scraper/scraper.py | 5 ++- mealie/services/scraper/scraper_strategies.py | 4 +- 8 files changed, 52 insertions(+), 31 deletions(-) diff --git a/mealie/routes/groups/controller_migrations.py b/mealie/routes/groups/controller_migrations.py index 3beba1083f41..3ec1ca26eb5a 100644 --- a/mealie/routes/groups/controller_migrations.py +++ b/mealie/routes/groups/controller_migrations.py @@ -44,6 +44,7 @@ class GroupMigrationController(BaseUserController): "user_id": self.user.id, "group_id": self.group_id, "add_migration_tag": add_migration_tag, + "translator": self.translator, } table: dict[SupportedMigrations, type[BaseMigrator]] = { diff --git a/mealie/routes/recipe/recipe_crud_routes.py b/mealie/routes/recipe/recipe_crud_routes.py index bace402ebd2e..d479fe2f4ca7 100644 --- a/mealie/routes/recipe/recipe_crud_routes.py +++ b/mealie/routes/recipe/recipe_crud_routes.py @@ -167,7 +167,7 @@ class RecipeController(BaseRecipeController): async def parse_recipe_url(self, req: ScrapeRecipe): """Takes in a URL and attempts to scrape data and load it into the database""" try: - recipe, extras = await create_from_url(req.url) + recipe, extras = await create_from_url(req.url, self.translator) except ForceTimeoutException as e: raise HTTPException( status_code=408, detail=ErrorResponse.respond(message="Recipe Scraping Timed Out") @@ -196,7 +196,7 @@ class RecipeController(BaseRecipeController): @router.post("/create-url/bulk", status_code=202) def parse_recipe_url_bulk(self, bulk: CreateRecipeByUrlBulk, bg_tasks: BackgroundTasks): """Takes in a URL and attempts to scrape data and load it into the database""" - bulk_scraper = RecipeBulkScraperService(self.service, self.repos, self.group) + bulk_scraper = RecipeBulkScraperService(self.service, self.repos, self.group, self.translator) report_id = bulk_scraper.get_report_id() bg_tasks.add_task(bulk_scraper.scrape, bulk) @@ -211,7 +211,7 @@ class RecipeController(BaseRecipeController): async def test_parse_recipe_url(self, url: ScrapeRecipeTest): # Debugger should produce the same result as the scraper sees before cleaning try: - if scraped_data := await RecipeScraperPackage(url.url).scrape_url(): + if scraped_data := await RecipeScraperPackage(url.url, self.translator).scrape_url(): return scraped_data.schema.data except ForceTimeoutException as e: raise HTTPException( diff --git a/mealie/services/migrations/_migration_base.py b/mealie/services/migrations/_migration_base.py index 1268a38fb7cd..6cfa6fa3012c 100644 --- a/mealie/services/migrations/_migration_base.py +++ b/mealie/services/migrations/_migration_base.py @@ -6,6 +6,7 @@ from pydantic import UUID4 from mealie.core import root_logger from mealie.core.exceptions import UnexpectedNone +from mealie.lang.providers import Translator from mealie.repos.all_repositories import AllRepositories from mealie.schema.recipe import Recipe from mealie.schema.recipe.recipe_settings import RecipeSettings @@ -35,12 +36,20 @@ class BaseMigrator(BaseService): helpers: DatabaseMigrationHelpers def __init__( - self, archive: Path, db: AllRepositories, session, user_id: UUID4, group_id: UUID, add_migration_tag: bool + self, + archive: Path, + db: AllRepositories, + session, + user_id: UUID4, + group_id: UUID, + add_migration_tag: bool, + translator: Translator, ): self.archive = archive self.db = db self.session = session self.add_migration_tag = add_migration_tag + self.translator = translator user = db.users.get_one(user_id) if not user: @@ -225,6 +234,6 @@ class BaseMigrator(BaseService): with contextlib.suppress(KeyError): del recipe_dict["id"] - recipe_dict = cleaner.clean(recipe_dict, url=recipe_dict.get("org_url", None)) + recipe_dict = cleaner.clean(recipe_dict, self.translator, url=recipe_dict.get("org_url", None)) return Recipe(**recipe_dict) diff --git a/mealie/services/scraper/cleaner.py b/mealie/services/scraper/cleaner.py index f4bfc0ad7083..294b5c99b42f 100644 --- a/mealie/services/scraper/cleaner.py +++ b/mealie/services/scraper/cleaner.py @@ -10,6 +10,7 @@ from datetime import datetime, timedelta from slugify import slugify from mealie.core.root_logger import get_logger +from mealie.lang.providers import Translator logger = get_logger("recipe-scraper") @@ -32,7 +33,7 @@ MATCH_ERRONEOUS_WHITE_SPACE = re.compile(r"\n\s*\n") """ Matches multiple new lines and removes erroneous white space """ -def clean(recipe_data: dict, url=None) -> dict: +def clean(recipe_data: dict, translator: Translator, url=None) -> dict: """Main entrypoint to clean a recipe extracted from the web and format the data into an accectable format for the database @@ -45,9 +46,9 @@ def clean(recipe_data: dict, url=None) -> dict: recipe_data["description"] = clean_string(recipe_data.get("description", "")) # Times - recipe_data["prepTime"] = clean_time(recipe_data.get("prepTime")) - recipe_data["performTime"] = clean_time(recipe_data.get("performTime")) - recipe_data["totalTime"] = clean_time(recipe_data.get("totalTime")) + recipe_data["prepTime"] = clean_time(recipe_data.get("prepTime"), translator) + recipe_data["performTime"] = clean_time(recipe_data.get("performTime"), translator) + recipe_data["totalTime"] = clean_time(recipe_data.get("totalTime"), translator) recipe_data["recipeCategory"] = clean_categories(recipe_data.get("recipeCategory", [])) recipe_data["recipeYield"] = clean_yield(recipe_data.get("recipeYield")) recipe_data["recipeIngredient"] = clean_ingredients(recipe_data.get("recipeIngredient", [])) @@ -332,7 +333,7 @@ def clean_yield(yld: str | list[str] | None) -> str: return yld -def clean_time(time_entry: str | timedelta | None) -> None | str: +def clean_time(time_entry: str | timedelta | None, translator: Translator) -> None | str: """_summary_ Supported Structures: @@ -358,11 +359,11 @@ def clean_time(time_entry: str | timedelta | None) -> None | str: try: time_delta_instructionsect = parse_duration(time_entry) - return pretty_print_timedelta(time_delta_instructionsect) + return pretty_print_timedelta(time_delta_instructionsect, translator) except ValueError: return str(time_entry) case timedelta(): - return pretty_print_timedelta(time_entry) + return pretty_print_timedelta(time_entry, translator) case {"minValue": str(value)}: return clean_time(value) case [str(), *_]: @@ -371,7 +372,7 @@ def clean_time(time_entry: str | timedelta | None) -> None | str: # TODO: Not sure what to do here return str(time_entry) case _: - logger.warning("[SCRAPER] Unexpected type or structure for time_entrys") + logger.warning("[SCRAPER] Unexpected type or structure for time_entries") return None @@ -405,25 +406,25 @@ def parse_duration(iso_duration: str) -> timedelta: return timedelta(**times) -def pretty_print_timedelta(t: timedelta, max_components=None, max_decimal_places=2): +def pretty_print_timedelta(t: timedelta, translator: Translator, max_components=None, max_decimal_places=2): """ Print a pretty string for a timedelta. For example datetime.timedelta(days=2, seconds=17280) will be printed as '2 days 4 Hours 48 Minutes'. Setting max_components to e.g. 1 will change this to '2.2 days', where the number of decimal points can also be set. """ - time_scale_names_dict = { - timedelta(days=365): "year", - timedelta(days=1): "day", - timedelta(hours=1): "Hour", - timedelta(minutes=1): "Minute", - timedelta(seconds=1): "Second", - timedelta(microseconds=1000): "millisecond", - timedelta(microseconds=1): "microsecond", + time_scale_translation_keys_dict = { + timedelta(days=365): "datetime.year", + timedelta(days=1): "datetime.day", + timedelta(hours=1): "datetime.hour", + timedelta(minutes=1): "datetime.minute", + timedelta(seconds=1): "datetime.second", + timedelta(microseconds=1000): "datetime.millisecond", + timedelta(microseconds=1): "datetime.microsecond", } count = 0 out_list = [] - for scale, scale_name in time_scale_names_dict.items(): + for scale, scale_translation_key in time_scale_translation_keys_dict.items(): if t >= scale: count += 1 n = t / scale if count == max_components else int(t / scale) @@ -433,7 +434,8 @@ def pretty_print_timedelta(t: timedelta, max_components=None, max_decimal_places if n_txt[-2:] == ".0": n_txt = n_txt[:-2] - out_list.append(f"{n_txt} {scale_name}{'s' if n > 1 else ''}") + scale_value = translator.t(scale_translation_key, count=n) + out_list.append(f"{n_txt} {scale_value}") if out_list == []: return "none" diff --git a/mealie/services/scraper/recipe_bulk_scraper.py b/mealie/services/scraper/recipe_bulk_scraper.py index ed701ecc3886..bfd714941152 100644 --- a/mealie/services/scraper/recipe_bulk_scraper.py +++ b/mealie/services/scraper/recipe_bulk_scraper.py @@ -2,6 +2,7 @@ import asyncio from pydantic import UUID4 +from mealie.lang.providers import Translator from mealie.repos.repository_factory import AllRepositories from mealie.schema.recipe.recipe import CreateRecipeByUrlBulk, Recipe from mealie.schema.reports.reports import ( @@ -20,11 +21,14 @@ from mealie.services.scraper.scraper import create_from_url class RecipeBulkScraperService(BaseService): report_entries: list[ReportEntryCreate] - def __init__(self, service: RecipeService, repos: AllRepositories, group: GroupInDB) -> None: + def __init__( + self, service: RecipeService, repos: AllRepositories, group: GroupInDB, translator: Translator + ) -> None: self.service = service self.repos = repos self.group = group self.report_entries = [] + self.translator = translator super().__init__() @@ -81,7 +85,7 @@ class RecipeBulkScraperService(BaseService): async def _do(url: str) -> Recipe | None: async with sem: try: - recipe, _ = await create_from_url(url) + recipe, _ = await create_from_url(url, self.translator) return recipe except Exception as e: self.service.logger.error(f"failed to scrape url during bulk url import {url}") diff --git a/mealie/services/scraper/recipe_scraper.py b/mealie/services/scraper/recipe_scraper.py index a9faeeccb6fc..90a81c636025 100644 --- a/mealie/services/scraper/recipe_scraper.py +++ b/mealie/services/scraper/recipe_scraper.py @@ -1,3 +1,4 @@ +from mealie.lang.providers import Translator from mealie.schema.recipe.recipe import Recipe from mealie.services.scraper.scraped_extras import ScrapedExtras @@ -14,11 +15,12 @@ class RecipeScraper: # List of recipe scrapers. Note that order matters scrapers: list[type[ABCScraperStrategy]] - def __init__(self, scrapers: list[type[ABCScraperStrategy]] | None = None) -> None: + def __init__(self, translator: Translator, scrapers: list[type[ABCScraperStrategy]] | None = None) -> None: if scrapers is None: scrapers = DEFAULT_SCRAPER_STRATEGIES self.scrapers = scrapers + self.translator = translator async def scrape(self, url: str) -> tuple[Recipe, ScrapedExtras] | tuple[None, None]: """ @@ -26,7 +28,7 @@ class RecipeScraper: """ for scraper_type in self.scrapers: - scraper = scraper_type(url) + scraper = scraper_type(url, self.translator) result = await scraper.parse() if result is not None: diff --git a/mealie/services/scraper/scraper.py b/mealie/services/scraper/scraper.py index eb8d415b88d5..bcd240edd302 100644 --- a/mealie/services/scraper/scraper.py +++ b/mealie/services/scraper/scraper.py @@ -5,6 +5,7 @@ from fastapi import HTTPException, status from slugify import slugify from mealie.core.root_logger import get_logger +from mealie.lang.providers import Translator from mealie.pkgs import cache from mealie.schema.recipe import Recipe from mealie.services.recipe.recipe_data_service import RecipeDataService @@ -19,7 +20,7 @@ class ParserErrors(str, Enum): CONNECTION_ERROR = "CONNECTION_ERROR" -async def create_from_url(url: str) -> tuple[Recipe, ScrapedExtras | None]: +async def create_from_url(url: str, translator: Translator) -> tuple[Recipe, ScrapedExtras | None]: """Main entry point for generating a recipe from a URL. Pass in a URL and a Recipe object will be returned if successful. @@ -29,7 +30,7 @@ async def create_from_url(url: str) -> tuple[Recipe, ScrapedExtras | None]: Returns: Recipe: Recipe Object """ - scraper = RecipeScraper() + scraper = RecipeScraper(translator) new_recipe, extras = await scraper.scrape(url) if not new_recipe: diff --git a/mealie/services/scraper/scraper_strategies.py b/mealie/services/scraper/scraper_strategies.py index fd51498023a4..a995ba389204 100644 --- a/mealie/services/scraper/scraper_strategies.py +++ b/mealie/services/scraper/scraper_strategies.py @@ -11,6 +11,7 @@ from slugify import slugify from w3lib.html import get_base_url from mealie.core.root_logger import get_logger +from mealie.lang.providers import Translator from mealie.schema.recipe.recipe import Recipe, RecipeStep from mealie.services.scraper.scraped_extras import ScrapedExtras @@ -77,9 +78,10 @@ class ABCScraperStrategy(ABC): url: str - def __init__(self, url: str) -> None: + def __init__(self, url: str, translator: Translator) -> None: self.logger = get_logger() self.url = url + self.translator = translator @abstractmethod async def get_html(self, url: str) -> str: From 3a30b3216ee69c34bfa960ed9e160dc76675cee9 Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Sat, 9 Dec 2023 17:19:27 +0000 Subject: [PATCH 004/130] fixed tests --- .../scraper_tests/test_cleaner.py | 7 +++++-- .../scraper_tests/test_cleaner_parts.py | 21 +++++++++++-------- tests/unit_tests/test_recipe_parser.py | 6 ++++-- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/tests/unit_tests/services_tests/scraper_tests/test_cleaner.py b/tests/unit_tests/services_tests/scraper_tests/test_cleaner.py index 395b943016d2..7e7bf047fa61 100644 --- a/tests/unit_tests/services_tests/scraper_tests/test_cleaner.py +++ b/tests/unit_tests/services_tests/scraper_tests/test_cleaner.py @@ -4,6 +4,7 @@ from pathlib import Path import pytest +from mealie.lang.providers import local_provider from mealie.services.scraper import cleaner from mealie.services.scraper.scraper_strategies import RecipeScraperOpenGraph from tests import data as test_data @@ -37,15 +38,17 @@ test_cleaner_data = [ @pytest.mark.parametrize("json_file,num_steps", test_cleaner_data) def test_cleaner_clean(json_file: Path, num_steps): - recipe_data = cleaner.clean(json.loads(json_file.read_text())) + translator = local_provider() + recipe_data = cleaner.clean(json.loads(json_file.read_text()), translator) assert len(recipe_data["recipeInstructions"]) == num_steps def test_html_with_recipe_data(): path = test_data.html_healthy_pasta_bake_60759 url = "https://www.bbc.co.uk/food/recipes/healthy_pasta_bake_60759" + translator = local_provider() - open_graph_strategy = RecipeScraperOpenGraph(url) + open_graph_strategy = RecipeScraperOpenGraph(url, translator) recipe_data = open_graph_strategy.get_recipe_fields(path.read_text()) diff --git a/tests/unit_tests/services_tests/scraper_tests/test_cleaner_parts.py b/tests/unit_tests/services_tests/scraper_tests/test_cleaner_parts.py index 67cbf9b34869..5fedb073b7cc 100644 --- a/tests/unit_tests/services_tests/scraper_tests/test_cleaner_parts.py +++ b/tests/unit_tests/services_tests/scraper_tests/test_cleaner_parts.py @@ -4,6 +4,7 @@ from typing import Any import pytest +from mealie.lang.providers import local_provider from mealie.services.scraper import cleaner @@ -324,32 +325,32 @@ time_test_cases = ( CleanerCase( test_id="timedelta", input=timedelta(minutes=30), - expected="30 Minutes", + expected="30 minutes", ), CleanerCase( test_id="timedelta string (1)", input="PT2H30M", - expected="2 Hours 30 Minutes", + expected="2 hours 30 minutes", ), CleanerCase( test_id="timedelta string (2)", input="PT30M", - expected="30 Minutes", + expected="30 minutes", ), CleanerCase( test_id="timedelta string (3)", input="PT2H", - expected="2 Hours", + expected="2 hours", ), CleanerCase( test_id="timedelta string (4)", input="P1DT1H1M1S", - expected="1 day 1 Hour 1 Minute 1 Second", + expected="1 day 1 hour 1 minute 1 second", ), CleanerCase( test_id="timedelta string (4)", input="P1DT1H1M1.53S", - expected="1 day 1 Hour 1 Minute 1 Second", + expected="1 day 1 hour 1 minute 1 second", ), CleanerCase( test_id="timedelta string (5) invalid", @@ -366,7 +367,8 @@ time_test_cases = ( @pytest.mark.parametrize("case", time_test_cases, ids=(x.test_id for x in time_test_cases)) def test_cleaner_clean_time(case: CleanerCase): - result = cleaner.clean_time(case.input) + translator = local_provider() + result = cleaner.clean_time(case.input, translator) assert case.expected == result @@ -536,10 +538,11 @@ def test_cleaner_clean_nutrition(case: CleanerCase): @pytest.mark.parametrize( "t,max_components,max_decimal_places,expected", [ - (timedelta(days=2, seconds=17280), None, 2, "2 days 4 Hours 48 Minutes"), + (timedelta(days=2, seconds=17280), None, 2, "2 days 4 hours 48 minutes"), (timedelta(days=2, seconds=17280), 1, 2, "2.2 days"), (timedelta(days=365), None, 2, "1 year"), ], ) def test_pretty_print_timedelta(t, max_components, max_decimal_places, expected): - assert cleaner.pretty_print_timedelta(t, max_components, max_decimal_places) == expected + translator = local_provider() + assert cleaner.pretty_print_timedelta(t, translator, max_components, max_decimal_places) == expected diff --git a/tests/unit_tests/test_recipe_parser.py b/tests/unit_tests/test_recipe_parser.py index b583543a55f7..e7515d73d98c 100644 --- a/tests/unit_tests/test_recipe_parser.py +++ b/tests/unit_tests/test_recipe_parser.py @@ -1,5 +1,6 @@ import pytest +from mealie.lang.providers import local_provider from mealie.services.scraper import scraper from tests.utils.recipe_data import RecipeSiteTestCase, get_recipe_test_cases @@ -18,9 +19,10 @@ and then use this test case by removing the `@pytest.mark.skip` and than testing @pytest.mark.parametrize("recipe_test_data", test_cases) @pytest.mark.asyncio async def test_recipe_parser(recipe_test_data: RecipeSiteTestCase): - recipe, _ = await scraper.create_from_url(recipe_test_data.url) + translator = local_provider() + recipe, _ = await scraper.create_from_url(recipe_test_data.url, translator) assert recipe.slug == recipe_test_data.expected_slug - assert len(recipe.recipe_instructions) == recipe_test_data.num_steps + assert len(recipe.recipe_instructions or []) == recipe_test_data.num_steps assert len(recipe.recipe_ingredient) == recipe_test_data.num_ingredients assert recipe.org_url == recipe_test_data.url From 437f5c454f484f5f5fb097c204c17f0616b9e5d3 Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Sat, 9 Dec 2023 22:04:21 +0000 Subject: [PATCH 005/130] fixed missing translator --- mealie/services/scraper/scraper_strategies.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/mealie/services/scraper/scraper_strategies.py b/mealie/services/scraper/scraper_strategies.py index a995ba389204..2a9046b5843a 100644 --- a/mealie/services/scraper/scraper_strategies.py +++ b/mealie/services/scraper/scraper_strategies.py @@ -105,7 +105,9 @@ class RecipeScraperPackage(ABCScraperStrategy): return await safe_scrape_html(url) def clean_scraper(self, scraped_data: SchemaScraperFactory.SchemaScraper, url: str) -> tuple[Recipe, ScrapedExtras]: - def try_get_default(func_call: Callable | None, get_attr: str, default: Any, clean_func=None): + def try_get_default( + func_call: Callable | None, get_attr: str, default: Any, clean_func=None, **clean_func_kwargs + ): value = default if func_call: @@ -121,7 +123,7 @@ class RecipeScraperPackage(ABCScraperStrategy): self.logger.error(f"Error parsing recipe attribute '{get_attr}'") if clean_func: - value = clean_func(value) + value = clean_func(value, **clean_func_kwargs) return value @@ -141,9 +143,9 @@ class RecipeScraperPackage(ABCScraperStrategy): except TypeError: return [] - cook_time = try_get_default(None, "performTime", None, cleaner.clean_time) or try_get_default( - None, "cookTime", None, cleaner.clean_time - ) + cook_time = try_get_default( + None, "performTime", None, cleaner.clean_time, translator=self.translator + ) or try_get_default(None, "cookTime", None, cleaner.clean_time, translator=self.translator) extras = ScrapedExtras() @@ -160,8 +162,8 @@ class RecipeScraperPackage(ABCScraperStrategy): scraped_data.ingredients, "recipeIngredient", [""], cleaner.clean_ingredients ), recipe_instructions=get_instructions(), - total_time=try_get_default(None, "totalTime", None, cleaner.clean_time), - prep_time=try_get_default(None, "prepTime", None, cleaner.clean_time), + total_time=try_get_default(None, "totalTime", None, cleaner.clean_time, translator=self.translator), + prep_time=try_get_default(None, "prepTime", None, cleaner.clean_time, translator=self.translator), perform_time=cook_time, org_url=url, ) From 19e776a772d07d4aa96386c1ab2c4467070672d0 Mon Sep 17 00:00:00 2001 From: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com> Date: Wed, 31 Jan 2024 10:33:05 +0000 Subject: [PATCH 006/130] manage-data pages --- frontend/middleware/admin-only.ts | 4 ++-- frontend/middleware/can-organize-only.ts | 12 ++++++++++++ frontend/pages/group/data/categories.vue | 1 + frontend/pages/group/data/foods.vue | 2 +- frontend/pages/group/data/labels.vue | 1 + frontend/pages/group/data/recipes.vue | 1 + frontend/pages/group/data/tags.vue | 1 + frontend/pages/group/data/tools.vue | 1 + frontend/pages/group/data/units.vue | 1 + frontend/pages/user/profile/index.vue | 22 +++++++++++----------- 10 files changed, 32 insertions(+), 14 deletions(-) create mode 100644 frontend/middleware/can-organize-only.ts diff --git a/frontend/middleware/admin-only.ts b/frontend/middleware/admin-only.ts index 66a4e00f3566..b55f603d5151 100644 --- a/frontend/middleware/admin-only.ts +++ b/frontend/middleware/admin-only.ts @@ -1,8 +1,8 @@ -interface AuthRedirectParams { +interface AdminRedirectParams { $auth: any redirect: (path: string) => void } -export default function ({ $auth, redirect }: AuthRedirectParams) { +export default function ({ $auth, redirect }: AdminRedirectParams) { // If the user is not an admin redirect to the home page if (!$auth.user.admin) { return redirect("/") diff --git a/frontend/middleware/can-organize-only.ts b/frontend/middleware/can-organize-only.ts new file mode 100644 index 000000000000..9bb6b6603d75 --- /dev/null +++ b/frontend/middleware/can-organize-only.ts @@ -0,0 +1,12 @@ +interface CanOrganizeRedirectParams { + $auth: any + redirect: (path: string) => void +} +export default function ({ $auth, redirect }: CanOrganizeRedirectParams) { + console.log($auth.user); + // If the user is not allowed to organize redirect to the home page + if (!$auth.user.canOrganize) { + console.warn("User is not allowed to organize data"); + return redirect("/") + } +} diff --git a/frontend/pages/group/data/categories.vue b/frontend/pages/group/data/categories.vue index 290e4f79e083..038c716c0e43 100644 --- a/frontend/pages/group/data/categories.vue +++ b/frontend/pages/group/data/categories.vue @@ -73,6 +73,7 @@ import { useCategoryStore, useCategoryData } from "~/composables/store"; import { RecipeCategory } from "~/lib/api/types/admin"; export default defineComponent({ + middleware: ["auth", "can-organize-only"], setup() { const { i18n } = useContext(); const tableConfig = { diff --git a/frontend/pages/group/data/foods.vue b/frontend/pages/group/data/foods.vue index 32eda4322b7d..aca6f2074fb5 100644 --- a/frontend/pages/group/data/foods.vue +++ b/frontend/pages/group/data/foods.vue @@ -201,8 +201,8 @@ import { useFoodStore, useLabelStore } from "~/composables/store"; import { VForm } from "~/types/vuetify"; export default defineComponent({ - components: { MultiPurposeLabel, RecipeDataAliasManagerDialog }, + middleware: ["auth", "can-organize-only"], setup() { const userApi = useUserApi(); const { i18n } = useContext(); diff --git a/frontend/pages/group/data/labels.vue b/frontend/pages/group/data/labels.vue index 604135b05873..7d1e45ea85d0 100644 --- a/frontend/pages/group/data/labels.vue +++ b/frontend/pages/group/data/labels.vue @@ -122,6 +122,7 @@ import { useLabelData, useLabelStore } from "~/composables/store"; export default defineComponent({ components: { MultiPurposeLabel }, + middleware: ["auth", "can-organize-only"], setup() { const userApi = useUserApi(); const { i18n } = useContext(); diff --git a/frontend/pages/group/data/recipes.vue b/frontend/pages/group/data/recipes.vue index 0803f285badc..2cd55aad4ea9 100644 --- a/frontend/pages/group/data/recipes.vue +++ b/frontend/pages/group/data/recipes.vue @@ -176,6 +176,7 @@ enum MODES { export default defineComponent({ components: { RecipeDataTable, RecipeOrganizerSelector, GroupExportData, RecipeSettingsSwitches }, + middleware: ["auth", "can-organize-only"], scrollToTop: true, setup() { const { getAllRecipes, refreshRecipes } = useRecipes(true, true); diff --git a/frontend/pages/group/data/tags.vue b/frontend/pages/group/data/tags.vue index 076a23f08b4d..b2440b4e5c0c 100644 --- a/frontend/pages/group/data/tags.vue +++ b/frontend/pages/group/data/tags.vue @@ -73,6 +73,7 @@ import { useTagStore, useTagData } from "~/composables/store"; import { RecipeTag } from "~/lib/api/types/admin"; export default defineComponent({ + middleware: ["auth", "can-organize-only"], setup() { const { i18n } = useContext(); const tableConfig = { diff --git a/frontend/pages/group/data/tools.vue b/frontend/pages/group/data/tools.vue index 4ff3c547819b..49825faf2da1 100644 --- a/frontend/pages/group/data/tools.vue +++ b/frontend/pages/group/data/tools.vue @@ -80,6 +80,7 @@ import { useToolStore, useToolData } from "~/composables/store"; import { RecipeTool } from "~/lib/api/types/admin"; export default defineComponent({ + middleware: ["auth", "can-organize-only"], setup() { const { i18n } = useContext(); const tableConfig = { diff --git a/frontend/pages/group/data/units.vue b/frontend/pages/group/data/units.vue index 9ce5fa991110..f1d42ba464f1 100644 --- a/frontend/pages/group/data/units.vue +++ b/frontend/pages/group/data/units.vue @@ -218,6 +218,7 @@ import { VForm } from "~/types/vuetify"; export default defineComponent({ components: { RecipeDataAliasManagerDialog }, + middleware: ["auth", "can-organize-only"], setup() { const userApi = useUserApi(); const { i18n } = useContext(); diff --git a/frontend/pages/user/profile/index.vue b/frontend/pages/user/profile/index.vue index ddb011550d1a..54a1e1b48708 100644 --- a/frontend/pages/user/profile/index.vue +++ b/frontend/pages/user/profile/index.vue @@ -162,17 +162,16 @@ - - - - - {{ $t('profile.manage-data-description') }} - - - + + + + + {{ $t('profile.manage-data-description') }} + + Date: Wed, 31 Jan 2024 11:56:15 +0000 Subject: [PATCH 007/130] add avanced-only --- frontend/middleware/advanced-only.ts | 11 +++++++++++ frontend/middleware/can-organize-only.ts | 1 - 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 frontend/middleware/advanced-only.ts diff --git a/frontend/middleware/advanced-only.ts b/frontend/middleware/advanced-only.ts new file mode 100644 index 000000000000..e9d69a2fd852 --- /dev/null +++ b/frontend/middleware/advanced-only.ts @@ -0,0 +1,11 @@ +interface AdvancedOnlyRedirectParams { + $auth: any + redirect: (path: string) => void +} +export default function ({ $auth, redirect }: AdvancedOnlyRedirectParams) { + // If the user is not allowed to organize redirect to the home page + if (!$auth.user.advanced) { + console.warn("User is not allowed to access advanced features"); + return redirect("/") + } +} diff --git a/frontend/middleware/can-organize-only.ts b/frontend/middleware/can-organize-only.ts index 9bb6b6603d75..93d6c5c5aabf 100644 --- a/frontend/middleware/can-organize-only.ts +++ b/frontend/middleware/can-organize-only.ts @@ -3,7 +3,6 @@ interface CanOrganizeRedirectParams { redirect: (path: string) => void } export default function ({ $auth, redirect }: CanOrganizeRedirectParams) { - console.log($auth.user); // If the user is not allowed to organize redirect to the home page if (!$auth.user.canOrganize) { console.warn("User is not allowed to organize data"); From 7dafa6c7fe2e0d446de4de77b7924b74352ee817 Mon Sep 17 00:00:00 2001 From: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com> Date: Wed, 31 Jan 2024 15:25:21 +0000 Subject: [PATCH 008/130] add access controll to user pages --- frontend/pages/user/_id/favorites.vue | 1 + frontend/pages/user/profile/api-tokens.vue | 1 + frontend/pages/user/profile/edit.vue | 1 + frontend/pages/user/profile/index.vue | 2 +- 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/pages/user/_id/favorites.vue b/frontend/pages/user/_id/favorites.vue index ec4e5c8fd984..603428d428e7 100644 --- a/frontend/pages/user/_id/favorites.vue +++ b/frontend/pages/user/_id/favorites.vue @@ -14,6 +14,7 @@ import { useAsyncKey } from "~/composables/use-utils"; export default defineComponent({ components: { RecipeCardSection }, + middleware: "auth", setup() { const api = useUserApi(); const route = useRoute(); diff --git a/frontend/pages/user/profile/api-tokens.vue b/frontend/pages/user/profile/api-tokens.vue index 8e41c6216986..8b6d21b361b9 100644 --- a/frontend/pages/user/profile/api-tokens.vue +++ b/frontend/pages/user/profile/api-tokens.vue @@ -69,6 +69,7 @@ import { useUserApi } from "~/composables/api"; import { VForm } from "~/types/vuetify"; export default defineComponent({ + middleware: ["auth", "advanced-only"], setup() { const nuxtContext = useContext(); diff --git a/frontend/pages/user/profile/edit.vue b/frontend/pages/user/profile/edit.vue index 3eadc87ed1c9..2681e9c5df68 100644 --- a/frontend/pages/user/profile/edit.vue +++ b/frontend/pages/user/profile/edit.vue @@ -135,6 +135,7 @@ export default defineComponent({ UserAvatar, UserPasswordStrength, }, + middleware: "auth", setup() { const { $auth } = useContext(); const user = computed(() => $auth.user as unknown as UserOut); diff --git a/frontend/pages/user/profile/index.vue b/frontend/pages/user/profile/index.vue index 54a1e1b48708..37041726747e 100644 --- a/frontend/pages/user/profile/index.vue +++ b/frontend/pages/user/profile/index.vue @@ -207,7 +207,7 @@ export default defineComponent({ UserAvatar, StatsCards, }, - middleware: ["auth"], + middleware: "auth", scrollToTop: true, setup() { const { $auth, i18n } = useContext(); From 890b5d93a70b3072f0f26e177f06346b4816f0d6 Mon Sep 17 00:00:00 2001 From: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com> Date: Thu, 1 Feb 2024 07:50:09 +0000 Subject: [PATCH 009/130] access controll coobook index page --- frontend/pages/g/_groupSlug/cookbooks/index.vue | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/frontend/pages/g/_groupSlug/cookbooks/index.vue b/frontend/pages/g/_groupSlug/cookbooks/index.vue index db99713c3bcb..35ad12fa8111 100644 --- a/frontend/pages/g/_groupSlug/cookbooks/index.vue +++ b/frontend/pages/g/_groupSlug/cookbooks/index.vue @@ -90,24 +90,17 @@ From 3d73e7498f06017b06af12263b324e053d8ba7dc Mon Sep 17 00:00:00 2001 From: Litchi Pi Date: Tue, 13 Feb 2024 20:30:07 +0100 Subject: [PATCH 107/130] feat: allow overriding of some absolute paths using environment variables (#3102) * Allow overriding of alembic config file path using environment variable Signed-off-by: Litchi Pi * Allow overriding of MODEL_PATH using environment variable Signed-off-by: Litchi Pi --------- Signed-off-by: Litchi Pi --- mealie/db/init_db.py | 8 +++++++- mealie/services/parser_services/crfpp/processor.py | 3 ++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/mealie/db/init_db.py b/mealie/db/init_db.py index 1fcefe1c21bf..6f94ee962023 100644 --- a/mealie/db/init_db.py +++ b/mealie/db/init_db.py @@ -1,3 +1,4 @@ +import os from collections.abc import Callable from pathlib import Path from time import sleep @@ -87,7 +88,12 @@ def main(): if max_retry == 0: raise ConnectionError("Database connection failed - exiting application.") - alembic_cfg = Config(str(PROJECT_DIR / "alembic.ini")) + alembic_cfg_path = os.getenv("ALEMBIC_CONFIG_FILE", default=str(PROJECT_DIR / "alembic.ini")) + + if not os.path.isfile(alembic_cfg_path): + raise Exception("Provided alembic config path doesn't exist") + + alembic_cfg = Config(alembic_cfg_path) if db_is_at_head(alembic_cfg): logger.debug("Migration not needed.") else: diff --git a/mealie/services/parser_services/crfpp/processor.py b/mealie/services/parser_services/crfpp/processor.py index 5c114732f31a..8ca936885386 100644 --- a/mealie/services/parser_services/crfpp/processor.py +++ b/mealie/services/parser_services/crfpp/processor.py @@ -1,3 +1,4 @@ +import os import subprocess import tempfile from fractions import Fraction @@ -13,7 +14,7 @@ from . import utils from .pre_processor import pre_process_string CWD = Path(__file__).parent -MODEL_PATH = CWD / "model.crfmodel" +MODEL_PATH = os.getenv("CRF_MODEL_PATH", default=CWD / "model.crfmodel") class CRFConfidence(BaseModel): From 0ebc2a746be415324f9adea30a05005763def0b1 Mon Sep 17 00:00:00 2001 From: Brendan Date: Wed, 14 Feb 2024 01:19:12 +0000 Subject: [PATCH 108/130] Add id-token=write permission, for Depot.dev connection --- .github/workflows/nightly.yml | 5 +++++ .github/workflows/release.yml | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 2f4bb30a6a63..e6501305f384 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -22,6 +22,11 @@ jobs: permissions: contents: read packages: write + # The id-token write permission is needed to connect to Depot.dev + # as part of the partial-builder.yml action. It needs to be declared + # in the parent action, as noted here: + # https://github.com/orgs/community/discussions/76409#discussioncomment-8131390 + id-token: write name: Build Tagged Release uses: ./.github/workflows/partial-builder.yml needs: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8417fff333b7..817ba3e8c14e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,6 +17,11 @@ jobs: permissions: contents: read packages: write + # The id-token write permission is needed to connect to Depot.dev + # as part of the partial-builder.yml action. It needs to be declared + # in the parent action, as noted here: + # https://github.com/orgs/community/discussions/76409#discussioncomment-8131390 + id-token: write name: Build Tagged Release uses: ./.github/workflows/partial-builder.yml needs: From b0ce1483fed8cd3a2bcae2451550ee127161c64b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 13 Feb 2024 23:34:04 -0600 Subject: [PATCH 109/130] fix(deps): update dependency orjson to v3.9.14 (#3173) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- poetry.lock | 102 ++++++++++++++++++++++++++-------------------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/poetry.lock b/poetry.lock index b43c4b24e343..bd6b876c0bd0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1359,61 +1359,61 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] [[package]] name = "orjson" -version = "3.9.13" +version = "3.9.14" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.8" files = [ - {file = "orjson-3.9.13-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:fa6b67f8bef277c2a4aadd548d58796854e7d760964126c3209b19bccc6a74f1"}, - {file = "orjson-3.9.13-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b812417199eeb169c25f67815cfb66fd8de7ff098bf57d065e8c1943a7ba5c8f"}, - {file = "orjson-3.9.13-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7ccd5bd222e5041069ad9d9868ab59e6dbc53ecde8d8c82b919954fbba43b46b"}, - {file = "orjson-3.9.13-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaaf80957c38e9d3f796f355a80fad945e72cd745e6b64c210e635b7043b673e"}, - {file = "orjson-3.9.13-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:60da7316131185d0110a1848e9ad15311e6c8938ee0b5be8cbd7261e1d80ee8f"}, - {file = "orjson-3.9.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b98cd948372f0eb219bc309dee4633db1278687161e3280d9e693b6076951d2"}, - {file = "orjson-3.9.13-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3869d65561f10071d3e7f35ae58fd377056f67d7aaed5222f318390c3ad30339"}, - {file = "orjson-3.9.13-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:43fd6036b16bb6742d03dae62f7bdf8214d06dea47e4353cde7e2bd1358d186f"}, - {file = "orjson-3.9.13-cp310-none-win32.whl", hash = "sha256:0d3ba9d88e20765335260d7b25547d7c571eee2b698200f97afa7d8c7cd668fc"}, - {file = "orjson-3.9.13-cp310-none-win_amd64.whl", hash = "sha256:6e47153db080f5e87e8ba638f1a8b18995eede6b0abb93964d58cf11bcea362f"}, - {file = "orjson-3.9.13-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4584e8eb727bc431baaf1bf97e35a1d8a0109c924ec847395673dfd5f4ef6d6f"}, - {file = "orjson-3.9.13-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f37f0cdd026ef777a4336e599d8194c8357fc14760c2a5ddcfdf1965d45504b"}, - {file = "orjson-3.9.13-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d714595d81efab11b42bccd119977d94b25d12d3a806851ff6bfd286a4bce960"}, - {file = "orjson-3.9.13-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9171e8e1a1f221953e38e84ae0abffe8759002fd8968106ee379febbb5358b33"}, - {file = "orjson-3.9.13-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ab9dbdec3f13f3ea6f937564ce21651844cfbf2725099f2f490426acf683c23"}, - {file = "orjson-3.9.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:811ac076855e33e931549340288e0761873baf29276ad00f221709933c644330"}, - {file = "orjson-3.9.13-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:860d0f5b42d0c0afd73fa4177709f6e1b966ba691fcd72175affa902052a81d6"}, - {file = "orjson-3.9.13-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:838b898e8c1f26eb6b8d81b180981273f6f5110c76c22c384979aca854194f1b"}, - {file = "orjson-3.9.13-cp311-none-win32.whl", hash = "sha256:d3222db9df629ef3c3673124f2e05fb72bc4a320c117e953fec0d69dde82e36d"}, - {file = "orjson-3.9.13-cp311-none-win_amd64.whl", hash = "sha256:978117122ca4cc59b28af5322253017f6c5fc03dbdda78c7f4b94ae984c8dd43"}, - {file = "orjson-3.9.13-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:031df1026c7ea8303332d78711f180231e3ae8b564271fb748a03926587c5546"}, - {file = "orjson-3.9.13-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fd9a2101d04e85086ea6198786a3f016e45475f800712e6833e14bf9ce2832f"}, - {file = "orjson-3.9.13-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:446d9ad04204e79229ae19502daeea56479e55cbc32634655d886f5a39e91b44"}, - {file = "orjson-3.9.13-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b57c0954a9fdd2b05b9cec0f5a12a0bdce5bf021a5b3b09323041613972481ab"}, - {file = "orjson-3.9.13-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:266e55c83f81248f63cc93d11c5e3a53df49a5d2598fa9e9db5f99837a802d5d"}, - {file = "orjson-3.9.13-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31372ba3a9fe8ad118e7d22fba46bbc18e89039e3bfa89db7bc8c18ee722dca8"}, - {file = "orjson-3.9.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e3b0c4da61f39899561e08e571f54472a09fa71717d9797928af558175ae5243"}, - {file = "orjson-3.9.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2cc03a35bfc71c8ebf96ce49b82c2a7be6af4b3cd3ac34166fdb42ac510bbfff"}, - {file = "orjson-3.9.13-cp312-none-win_amd64.whl", hash = "sha256:49b7e3fe861cb246361825d1a238f2584ed8ea21e714bf6bb17cebb86772e61c"}, - {file = "orjson-3.9.13-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:62e9a99879c4d5a04926ac2518a992134bfa00d546ea5a4cae4b9be454d35a22"}, - {file = "orjson-3.9.13-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d92a3e835a5100f1d5b566fff79217eab92223ca31900dba733902a182a35ab0"}, - {file = "orjson-3.9.13-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:23f21faf072ed3b60b5954686f98157e073f6a8068eaa58dbde83e87212eda84"}, - {file = "orjson-3.9.13-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:828c502bb261588f7de897e06cb23c4b122997cb039d2014cb78e7dabe92ef0c"}, - {file = "orjson-3.9.13-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16946d095212a3dec552572c5d9bca7afa40f3116ad49695a397be07d529f1fa"}, - {file = "orjson-3.9.13-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3deadd8dc0e9ff844b5b656fa30a48dbee1c3b332d8278302dd9637f6b09f627"}, - {file = "orjson-3.9.13-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9b1b5adc5adf596c59dca57156b71ad301d73956f5bab4039b0e34dbf50b9fa0"}, - {file = "orjson-3.9.13-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ddc089315d030c54f0f03fb38286e2667c05009a78d659f108a8efcfbdf2e585"}, - {file = "orjson-3.9.13-cp38-none-win32.whl", hash = "sha256:ae77275a28667d9c82d4522b681504642055efa0368d73108511647c6499b31c"}, - {file = "orjson-3.9.13-cp38-none-win_amd64.whl", hash = "sha256:730385fdb99a21fce9bb84bb7fcbda72c88626facd74956bda712834b480729d"}, - {file = "orjson-3.9.13-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7e8e4a571d958910272af8d53a9cbe6599f9f5fd496a1bc51211183bb2072cbd"}, - {file = "orjson-3.9.13-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfad553a36548262e7da0f3a7464270e13900b898800fb571a5d4b298c3f8356"}, - {file = "orjson-3.9.13-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0d691c44604941945b00e0a13b19a7d9c1a19511abadf0080f373e98fdeb6b31"}, - {file = "orjson-3.9.13-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8c83718346de08d68b3cb1105c5d91e5fc39885d8610fdda16613d4e3941459"}, - {file = "orjson-3.9.13-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63ef57a53bfc2091a7cd50a640d9ae866bd7d92a5225a1bab6baa60ef62583f2"}, - {file = "orjson-3.9.13-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9156b96afa38db71344522f5517077eaedf62fcd2c9148392ff93d801128809c"}, - {file = "orjson-3.9.13-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31fb66b41fb2c4c817d9610f0bc7d31345728d7b5295ac78b63603407432a2b2"}, - {file = "orjson-3.9.13-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8a730bf07feacb0863974e67b206b7c503a62199de1cece2eb0d4c233ec29c11"}, - {file = "orjson-3.9.13-cp39-none-win32.whl", hash = "sha256:5ef58869f3399acbbe013518d8b374ee9558659eef14bca0984f67cb1fbd3c37"}, - {file = "orjson-3.9.13-cp39-none-win_amd64.whl", hash = "sha256:9bcf56efdb83244cde070e82a69c0f03c47c235f0a5cb6c81d9da23af7fbaae4"}, - {file = "orjson-3.9.13.tar.gz", hash = "sha256:fc6bc65b0cf524ee042e0bc2912b9206ef242edfba7426cf95763e4af01f527a"}, + {file = "orjson-3.9.14-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:793f6c9448ab6eb7d4974b4dde3f230345c08ca6c7995330fbceeb43a5c8aa5e"}, + {file = "orjson-3.9.14-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6bc7928d161840096adc956703494b5c0193ede887346f028216cac0af87500"}, + {file = "orjson-3.9.14-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:58b36f54da759602d8e2f7dad958752d453dfe2c7122767bc7f765e17dc59959"}, + {file = "orjson-3.9.14-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:abcda41ecdc950399c05eff761c3de91485d9a70d8227cb599ad3a66afe93bcc"}, + {file = "orjson-3.9.14-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df76ecd17b1b3627bddfd689faaf206380a1a38cc9f6c4075bd884eaedcf46c2"}, + {file = "orjson-3.9.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d450a8e0656efb5d0fcb062157b918ab02dcca73278975b4ee9ea49e2fcf5bd5"}, + {file = "orjson-3.9.14-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:95c03137b0cf66517c8baa65770507a756d3a89489d8ecf864ea92348e1beabe"}, + {file = "orjson-3.9.14-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:20837e10835c98973673406d6798e10f821e7744520633811a5a3d809762d8cc"}, + {file = "orjson-3.9.14-cp310-none-win32.whl", hash = "sha256:1f7b6f3ef10ae8e3558abb729873d033dbb5843507c66b1c0767e32502ba96bb"}, + {file = "orjson-3.9.14-cp310-none-win_amd64.whl", hash = "sha256:ea890e6dc1711aeec0a33b8520e395c2f3d59ead5b4351a788e06bf95fc7ba81"}, + {file = "orjson-3.9.14-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c19009ff37f033c70acd04b636380379499dac2cba27ae7dfc24f304deabbc81"}, + {file = "orjson-3.9.14-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19cdea0664aec0b7f385be84986d4defd3334e9c3c799407686ee1c26f7b8251"}, + {file = "orjson-3.9.14-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:135d518f73787ce323b1a5e21fb854fe22258d7a8ae562b81a49d6c7f826f2a3"}, + {file = "orjson-3.9.14-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d2cf1d0557c61c75e18cf7d69fb689b77896e95553e212c0cc64cf2087944b84"}, + {file = "orjson-3.9.14-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7c11667421df2d8b18b021223505dcc3ee51be518d54e4dc49161ac88ac2b87"}, + {file = "orjson-3.9.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2eefc41ba42e75ed88bc396d8fe997beb20477f3e7efa000cd7a47eda452fbb2"}, + {file = "orjson-3.9.14-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:917311d6a64d1c327c0dfda1e41f3966a7fb72b11ca7aa2e7a68fcccc7db35d9"}, + {file = "orjson-3.9.14-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4dc1c132259b38d12c6587d190cd09cd76e3b5273ce71fe1372437b4cbc65f6f"}, + {file = "orjson-3.9.14-cp311-none-win32.whl", hash = "sha256:6f39a10408478f4c05736a74da63727a1ae0e83e3533d07b19443400fe8591ca"}, + {file = "orjson-3.9.14-cp311-none-win_amd64.whl", hash = "sha256:26280a7fcb62d8257f634c16acebc3bec626454f9ab13558bbf7883b9140760e"}, + {file = "orjson-3.9.14-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:08e722a8d06b13b67a51f247a24938d1a94b4b3862e40e0eef3b2e98c99cd04c"}, + {file = "orjson-3.9.14-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2591faa0c031cf3f57e5bce1461cfbd6160f3f66b5a72609a130924917cb07d"}, + {file = "orjson-3.9.14-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e2450d87dd7b4f277f4c5598faa8b49a0c197b91186c47a2c0b88e15531e4e3e"}, + {file = "orjson-3.9.14-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:90903d2908158a2c9077a06f11e27545de610af690fb178fd3ba6b32492d4d1c"}, + {file = "orjson-3.9.14-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce6f095eef0026eae76fc212f20f786011ecf482fc7df2f4c272a8ae6dd7b1ef"}, + {file = "orjson-3.9.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:751250a31fef2bac05a2da2449aae7142075ea26139271f169af60456d8ad27a"}, + {file = "orjson-3.9.14-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9a1af21160a38ee8be3f4fcf24ee4b99e6184cadc7f915d599f073f478a94d2c"}, + {file = "orjson-3.9.14-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:449bf090b2aa4e019371d7511a6ea8a5a248139205c27d1834bb4b1e3c44d936"}, + {file = "orjson-3.9.14-cp312-none-win_amd64.whl", hash = "sha256:a603161318ff699784943e71f53899983b7dee571b4dd07c336437c9c5a272b0"}, + {file = "orjson-3.9.14-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:814f288c011efdf8f115c5ebcc1ab94b11da64b207722917e0ceb42f52ef30a3"}, + {file = "orjson-3.9.14-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a88cafb100af68af3b9b29b5ccd09fdf7a48c63327916c8c923a94c336d38dd3"}, + {file = "orjson-3.9.14-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ba3518b999f88882ade6686f1b71e207b52e23546e180499be5bbb63a2f9c6e6"}, + {file = "orjson-3.9.14-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:978f416bbff9da8d2091e3cf011c92da68b13f2c453dcc2e8109099b2a19d234"}, + {file = "orjson-3.9.14-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75fc593cf836f631153d0e21beaeb8d26e144445c73645889335c2247fcd71a0"}, + {file = "orjson-3.9.14-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d1528db3c7554f9d6eeb09df23cb80dd5177ec56eeb55cc5318826928de506"}, + {file = "orjson-3.9.14-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:7183cc68ee2113b19b0b8714221e5e3b07b3ba10ca2bb108d78fd49cefaae101"}, + {file = "orjson-3.9.14-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:df3266d54246cb56b8bb17fa908660d2a0f2e3f63fbc32451ffc1b1505051d07"}, + {file = "orjson-3.9.14-cp38-none-win32.whl", hash = "sha256:7913079b029e1b3501854c9a78ad938ed40d61fe09bebab3c93e60ff1301b189"}, + {file = "orjson-3.9.14-cp38-none-win_amd64.whl", hash = "sha256:29512eb925b620e5da2fd7585814485c67cc6ba4fe739a0a700c50467a8a8065"}, + {file = "orjson-3.9.14-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:5bf597530544db27a8d76aced49cfc817ee9503e0a4ebf0109cd70331e7bbe0c"}, + {file = "orjson-3.9.14-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac650d49366fa41fe702e054cb560171a8634e2865537e91f09a8d05ea5b1d37"}, + {file = "orjson-3.9.14-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:236230433a9a4968ab895140514c308fdf9f607cb8bee178a04372b771123860"}, + {file = "orjson-3.9.14-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3014ccbda9be0b1b5f8ea895121df7e6524496b3908f4397ff02e923bcd8f6dd"}, + {file = "orjson-3.9.14-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ac0c7eae7ad3a223bde690565442f8a3d620056bd01196f191af8be58a5248e1"}, + {file = "orjson-3.9.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fca33fdd0b38839b01912c57546d4f412ba7bfa0faf9bf7453432219aec2df07"}, + {file = "orjson-3.9.14-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f75823cc1674a840a151e999a7dfa0d86c911150dd6f951d0736ee9d383bf415"}, + {file = "orjson-3.9.14-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6f52ac2eb49e99e7373f62e2a68428c6946cda52ce89aa8fe9f890c7278e2d3a"}, + {file = "orjson-3.9.14-cp39-none-win32.whl", hash = "sha256:0572f174f50b673b7df78680fb52cd0087a8585a6d06d295a5f790568e1064c6"}, + {file = "orjson-3.9.14-cp39-none-win_amd64.whl", hash = "sha256:ab90c02cb264250b8a58cedcc72ed78a4a257d956c8d3c8bebe9751b818dfad8"}, + {file = "orjson-3.9.14.tar.gz", hash = "sha256:06fb40f8e49088ecaa02f1162581d39e2cf3fd9dbbfe411eb2284147c99bad79"}, ] [[package]] From 2b5372f693a23894606cfed74eb1e23d055f5a9a Mon Sep 17 00:00:00 2001 From: boc-the-git <3479092+boc-the-git@users.noreply.github.com> Date: Wed, 14 Feb 2024 19:58:21 +1100 Subject: [PATCH 110/130] Remove permissions block, so it doesn't override what is inherited from parent workflow --- .github/workflows/partial-builder.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/partial-builder.yml b/.github/workflows/partial-builder.yml index 60693047cfea..c6362ba3b970 100644 --- a/.github/workflows/partial-builder.yml +++ b/.github/workflows/partial-builder.yml @@ -18,8 +18,6 @@ on: jobs: publish: runs-on: ubuntu-latest - permissions: - id-token: write steps: - name: Checkout repository uses: actions/checkout@v4 From c1a3516b379874ae9027debfa4959833111892ab Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 14 Feb 2024 09:26:56 +0000 Subject: [PATCH 111/130] fix(deps): update dependency uvicorn to v0.27.1 --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index bd6b876c0bd0..214718d49170 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2764,13 +2764,13 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "uvicorn" -version = "0.27.0" +version = "0.27.1" description = "The lightning-fast ASGI server." optional = false python-versions = ">=3.8" files = [ - {file = "uvicorn-0.27.0-py3-none-any.whl", hash = "sha256:890b00f6c537d58695d3bb1f28e23db9d9e7a17cbcc76d7457c499935f933e24"}, - {file = "uvicorn-0.27.0.tar.gz", hash = "sha256:c855578045d45625fd027367f7653d249f7c49f9361ba15cf9624186b26b8eb6"}, + {file = "uvicorn-0.27.1-py3-none-any.whl", hash = "sha256:5c89da2f3895767472a35556e539fd59f7edbe9b1e9c0e1c99eebeadc61838e4"}, + {file = "uvicorn-0.27.1.tar.gz", hash = "sha256:3d9a267296243532db80c83a959a3400502165ade2c1338dea4e67915fd4745a"}, ] [package.dependencies] From 89a5326d3fdbe93ef40a6b1e306e52352ee45332 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 14 Feb 2024 09:42:35 +0000 Subject: [PATCH 112/130] chore(deps): update dependency pre-commit to v3.6.1 --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 214718d49170..4cd904baadea 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1578,13 +1578,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "3.6.0" +version = "3.6.1" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" files = [ - {file = "pre_commit-3.6.0-py2.py3-none-any.whl", hash = "sha256:c255039ef399049a5544b6ce13d135caba8f2c28c3b4033277a788f434308376"}, - {file = "pre_commit-3.6.0.tar.gz", hash = "sha256:d30bad9abf165f7785c15a21a1f46da7d0677cb00ee7ff4c579fd38922efe15d"}, + {file = "pre_commit-3.6.1-py2.py3-none-any.whl", hash = "sha256:9fe989afcf095d2c4796ce7c553cf28d4d4a9b9346de3cda079bcf40748454a4"}, + {file = "pre_commit-3.6.1.tar.gz", hash = "sha256:c90961d8aa706f75d60935aba09469a6b0bcb8345f127c3fbee4bdc5f114cf4b"}, ] [package.dependencies] From 83887e3c37387b0d9fa93907f29daa0d9e233669 Mon Sep 17 00:00:00 2001 From: boc-the-git <3479092+boc-the-git@users.noreply.github.com> Date: Fri, 16 Feb 2024 03:57:02 +1100 Subject: [PATCH 113/130] Set recipe rating (#3175) --- frontend/pages/group/mealplan/planner/view.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/pages/group/mealplan/planner/view.vue b/frontend/pages/group/mealplan/planner/view.vue index 028d2cfdbf3d..d982d1608392 100644 --- a/frontend/pages/group/mealplan/planner/view.vue +++ b/frontend/pages/group/mealplan/planner/view.vue @@ -39,6 +39,7 @@ :recipe-id="mealplan.recipe ? mealplan.recipe.id : ''" class="mb-2" :route="mealplan.recipe ? true : false" + :rating="mealplan.recipe ? mealplan.recipe.rating : 0" :slug="mealplan.recipe ? mealplan.recipe.slug : mealplan.title" :description="mealplan.recipe ? mealplan.recipe.description : mealplan.text" :name="mealplan.recipe ? mealplan.recipe.name : mealplan.title" From 0a3542e97c756276310b0e12c18634242cad6259 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Fri, 16 Feb 2024 06:05:46 -0600 Subject: [PATCH 114/130] New translations en-us.json (German) (#3179) --- mealie/lang/messages/de-DE.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/mealie/lang/messages/de-DE.json b/mealie/lang/messages/de-DE.json index 07396530601a..c442501b189f 100644 --- a/mealie/lang/messages/de-DE.json +++ b/mealie/lang/messages/de-DE.json @@ -33,12 +33,12 @@ "generic-deleted": "{name} wurde gelöscht" }, "datetime": { - "year": "year|years", - "day": "day|days", - "hour": "hour|hours", - "minute": "minute|minutes", - "second": "second|seconds", - "millisecond": "millisecond|milliseconds", - "microsecond": "microsecond|microseconds" + "year": "jahr|jahre", + "day": "tag|tage", + "hour": "stunde|stunden", + "minute": "minute|minuten", + "second": "sekunde|sekunden", + "millisecond": "millisekunde|millisekunden", + "microsecond": "mikrosekunde|mikrosekunden" } } From 6c4294dc49ecc4e0516ecc496b5fa43138850d14 Mon Sep 17 00:00:00 2001 From: boc-the-git <3479092+boc-the-git@users.noreply.github.com> Date: Sat, 17 Feb 2024 13:39:50 +1100 Subject: [PATCH 115/130] chore: Only run docker build and discord notify on the main repo (not forks) (#3176) --- .github/workflows/nightly.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index e6501305f384..21dd21b34d3e 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -28,6 +28,7 @@ jobs: # https://github.com/orgs/community/discussions/76409#discussioncomment-8131390 id-token: write name: Build Tagged Release + if: github.repository == 'mealie-recipes/mealie' uses: ./.github/workflows/partial-builder.yml needs: - frontend-tests @@ -40,6 +41,7 @@ jobs: notify-discord: name: Notify Discord + if: github.repository == 'mealie-recipes/mealie' needs: - build-release runs-on: ubuntu-latest From fa60d81e2694d345b3c7ca86bec5b0fe26906524 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 17 Feb 2024 02:43:03 +0000 Subject: [PATCH 116/130] fix(deps): update dependency paho-mqtt to v2 --- poetry.lock | 11 ++++++----- pyproject.toml | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index 4cd904baadea..2eda600da3aa 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1439,16 +1439,17 @@ files = [ [[package]] name = "paho-mqtt" -version = "1.6.1" +version = "2.0.0" description = "MQTT version 5.0/3.1.1 client class" optional = false -python-versions = "*" +python-versions = ">=3.7" files = [ - {file = "paho-mqtt-1.6.1.tar.gz", hash = "sha256:2a8291c81623aec00372b5a85558a372c747cbca8e9934dfe218638b8eefc26f"}, + {file = "paho_mqtt-2.0.0-py3-none-any.whl", hash = "sha256:2ef745073dfc9aa68bfec30d0b9b6f0304ea75182bae85a7c77a80cefce1eff5"}, + {file = "paho_mqtt-2.0.0.tar.gz", hash = "sha256:13b205f29251e4f2c66a6c923c31fc4fd780561e03b2d775cff8e4f2915cf947"}, ] [package.extras] -proxy = ["PySocks"] +proxy = ["pysocks"] [[package]] name = "pathspec" @@ -3028,4 +3029,4 @@ pgsql = ["psycopg2-binary"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "02c2f99d7ead3339de5c02e9b3ec3ac33fc4a7204130b9cd404e749d995e9a7b" +content-hash = "ee8a6a7655483cd1022a820d3a06c90b2ce52e6e725370ee8b8fcec5a196b0eb" diff --git a/pyproject.toml b/pyproject.toml index c0ad34cf870e..c34712e43b4f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,7 @@ isodate = "^0.6.1" text-unidecode = "^1.3" rapidfuzz = "^3.2.0" html2text = "^2020.1.16" -paho-mqtt = "^1.6.1" +paho-mqtt = "^2.0.0" pydantic-settings = "^2.1.0" [tool.poetry.group.postgres.dependencies] From cb06c8a87701e860865f36aba24d9db8d2218e3e Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Sat, 17 Feb 2024 06:22:05 -0600 Subject: [PATCH 117/130] New Crowdin updates (#3184) * New translations en-us.json (Spanish) * New translations en-us.json (Spanish) --- frontend/lang/messages/es-ES.json | 140 +++++++++++++++--------------- mealie/lang/messages/es-ES.json | 14 +-- 2 files changed, 77 insertions(+), 77 deletions(-) diff --git a/frontend/lang/messages/es-ES.json b/frontend/lang/messages/es-ES.json index c1011b400292..063d0e22dda5 100644 --- a/frontend/lang/messages/es-ES.json +++ b/frontend/lang/messages/es-ES.json @@ -9,11 +9,11 @@ "database-url": "URL de base de datos", "default-group": "Grupo Predeterminado", "demo": "Versión Demo", - "demo-status": "Estado Demo", + "demo-status": "Modo Demo", "development": "Desarrollo", "docs": "Documentación", "download-log": "Descargar Log", - "download-recipe-json": "Último JSON recuperado", + "download-recipe-json": "Último JSON extraído", "github": "Github", "log-lines": "Líneas de registro", "not-demo": "No Demo", @@ -33,7 +33,7 @@ "pdf": "PDF", "recipe": "Receta", "show-assets": "Mostrar recursos", - "error-submitting-form": "Se ha producido un error al enviar el formulario" + "error-submitting-form": "Error al enviar el formulario" }, "category": { "categories": "Categorías", @@ -200,7 +200,7 @@ "created-on-date": "Creado el {0}", "unsaved-changes": "Tienes cambios sin guardar. ¿Quieres guardar antes de salir? Aceptar para guardar, Cancelar para descartar cambios.", "clipboard-copy-failure": "No se pudo copiar al portapapeles.", - "confirm-delete-generic-items": "Are you sure you want to delete the following items?" + "confirm-delete-generic-items": "¿Estás seguro que quieres eliminar los siguientes elementos?" }, "group": { "are-you-sure-you-want-to-delete-the-group": "Por favor, confirma que deseas eliminar {groupName}", @@ -599,9 +599,9 @@ "import-summary": "Importar resumen", "partial-backup": "Copia de seguridad parcial", "unable-to-delete-backup": "No se puede eliminar la copia de seguridad.", - "experimental-description": "Backups a total snapshots of the database and data directory of the site. This includes all data and cannot be set to exclude subsets of data. You can think off this as a snapshot of Mealie at a specific time. Currently, {not-crossed-version} (data migrations are not done automatically). These serve as a database agnostic way to export and import data or backup the site to an external location.", + "experimental-description": "Las copias de seguridad son instantáneas completas de la base de datos y del directorio de datos del sitio. Esto incluye todos los datos y no se pueden configurar para excluir subconjuntos de datos. Puedes pensar en esto como una instantánea de Mealie en un momento específico. Estas sirven como una forma agnóstica de la base de datos para exportar e importar datos, o respaldar el sitio en una ubicación externa.", "backup-restore": "Restaurar Copia de Seguridad", - "back-restore-description": "Restoring this backup will overwrite all the current data in your database and in the data directory and replace them with the contents of this backup. {cannot-be-undone} If the restoration is successful, you will be logged out.", + "back-restore-description": "Restaurar esta copia de seguridad sobrescribirá todos los datos actuales de su base de datos y del directorio de datos y los sustituirá por el contenido de esta copia. {cannot-be-undone} Si la restauración se realiza correctamente, se cerrará su sesión.", "cannot-be-undone": "Esta acción no se puede deshacer, use con precaución.", "postgresql-note": "Si estás usando PostGreSQL, por favor revisa el {backup-restore-process} antes de restaurar.", "backup-restore-process-in-the-documentation": "copia de seguridad/proceso de restauración en la documentación", @@ -723,7 +723,7 @@ "ldap-ready": "LDAP Listo", "ldap-ready-error-text": "No todos los valores LDAP están configurados. Esto puede ignorarse si no está usando autenticación LDAP.", "ldap-ready-success-text": "Las variables LDAP requeridas están todas definidas.", - "build": "Compilar", + "build": "Compilación", "recipe-scraper-version": "Versión de Analizador de Recetas" }, "shopping-list": { @@ -962,9 +962,9 @@ "settings-chosen-explanation": "Los ajustes seleccionados aquí, excluyendo la opción bloqueada, se aplicarán a todas las recetas seleccionadas.", "selected-length-recipe-s-settings-will-be-updated": "Se actualizarán los ajustes de {count} receta(s).", "recipe-data": "Datos de la receta", - "recipe-data-description": "Use this section to manage the data associated with your recipes. You can perform several bulk actions on your recipes including exporting, deleting, tagging, and assigning categories.", - "recipe-columns": "Recipe Columns", - "data-exports-description": "This section provides links to available exports that are ready to download. These exports do expire, so be sure to grab them while they're still available.", + "recipe-data-description": "Utiliza esta sección para gestionar los datos asociados a tus recetas. Puedes realizar varias acciones de forma masiva en tus recetas, como exportar, eliminar, etiquetar y asignar categorías.", + "recipe-columns": "Columnas de Recetas", + "data-exports-description": "Esta sección proporciona enlaces a las exportaciones disponibles listas para descargar. Estas exportaciones caducan, así que asegúrate de descargarlas mientras estén disponibles.", "data-exports": "Exportación de datos", "tag": "Etiqueta", "categorize": "Clasificar", @@ -973,14 +973,14 @@ "categorize-recipes": "Categorizar recetas", "export-recipes": "Exportar recetas", "delete-recipes": "Borrar Recetas", - "source-unit-will-be-deleted": "Source Unit will be deleted" + "source-unit-will-be-deleted": "Se eliminará la unidad de origen" }, "create-alias": "Crear un Alias", "manage-aliases": "Administrar Alias", "seed-data": "Datos de ejemplo", "seed": "Semilla", - "data-management": "Data Management", - "data-management-description": "Select which data set you want to make changes to.", + "data-management": "Gestión de Datos", + "data-management-description": "Selecciona el conjunto de datos al que deseas hacer cambios.", "select-data": "Seleccionar datos", "select-language": "Seleccionar idioma", "columns": "Columnas", @@ -1003,7 +1003,7 @@ }, "user-registration": { "user-registration": "Registro de usuario", - "registration-success": "Registration Success", + "registration-success": "Registrado con éxito", "join-a-group": "Unirse a un grupo", "create-a-new-group": "Crear un grupo nuevo", "provide-registration-token-description": "Por favor, proporcione el token de registro asociado con el grupo al que desea unirse. Necesitará obtenerlo de un miembro del grupo.", @@ -1050,57 +1050,57 @@ }, "ocr-editor": { "ocr-editor": "Editor de OCR", - "toolbar": "Toolbar", + "toolbar": "Barra de Herramientas", "selection-mode": "Modo de selección", "pan-and-zoom-picture": "Desplazar y hacer zoom en la imagen", - "split-text": "Split text", - "preserve-line-breaks": "Preserve original line breaks", - "split-by-block": "Split by text block", - "flatten": "Flatten regardless of original formating", + "split-text": "Dividir texto", + "preserve-line-breaks": "Mantener los saltos de línea originales", + "split-by-block": "División por bloques de texto", + "flatten": "Acoplar independientemente del formato original", "help": { - "help": "Help", - "mouse-modes": "Mouse modes", - "selection-mode": "Selection Mode (default)", - "selection-mode-desc": "The selection mode is the main mode that can be used to enter data:", + "help": "Ayuda", + "mouse-modes": "Modos de ratón", + "selection-mode": "Modo selección (por defecto)", + "selection-mode-desc": "El modo de selección es el modo principal que puede utilizarse para introducir datos:", "selection-mode-steps": { - "draw": "Draw a rectangle on the text you want to select.", + "draw": "Dibuja un rectángulo sobre el texto que deseas seleccionar.", "click": "Click on any field on the right and then click back on the rectangle above the image.", "result": "The selected text will appear inside the previously selected field." }, - "pan-and-zoom-mode": "Pan and Zoom Mode", + "pan-and-zoom-mode": "Modo Panorámico y Zoom", "pan-and-zoom-desc": "Select pan and zoom by clicking the icon. This mode allows to zoom inside the image and move around to make using big images easier.", - "split-text-mode": "Split Text modes", + "split-text-mode": "Modos de división de texto", "split-modes": { - "line-mode": "Line mode (default)", + "line-mode": "Modo de línea (por defecto)", "line-mode-desc": "In line mode, the text will be propagated by keeping the original line breaks. This mode is useful when using bulk add on a list of ingredients where one ingredient is one line.", - "block-mode": "Block mode", - "block-mode-desc": "In block mode, the text will be split in blocks. This mode is useful when bulk adding instructions that are usually written in paragraphs.", - "flat-mode": "Flat mode", - "flat-mode-desc": "In flat mode, the text will be added to the selected recipe field with no line breaks." + "block-mode": "Modo en bloque", + "block-mode-desc": "En el modo de bloque, el texto se dividirá en bloques. Este modo es útil cuando se agregan instrucciones que están escritas en párrafos.", + "flat-mode": "Modo texto sin formato", + "flat-mode-desc": "En modo texto sin formato, el texto se añadirá al campo de la receta seleccionado sin saltos de línea." } } }, "admin": { "maintenance": { - "storage-details": "Storage Details", - "page-title": "Site Maintenance", - "summary-title": "Summary", - "button-label-get-summary": "Get Summary", + "storage-details": "Detalle del almacenamiento", + "page-title": "Mantenimiento del sitio", + "summary-title": "Índice", + "button-label-get-summary": "Obtener Resumen", "button-label-open-details": "Detalles", - "info-description-data-dir-size": "Data Directory Size", - "info-description-log-file-size": "Log File Size", - "info-description-cleanable-directories": "Cleanable Directories", - "info-description-cleanable-images": "Cleanable Images", + "info-description-data-dir-size": "Tamaño del directorio de datos", + "info-description-log-file-size": "Tamaño del archivo de registro", + "info-description-cleanable-directories": "Directorios eliminables", + "info-description-cleanable-images": "Imágenes eliminables", "storage": { "title-temporary-directory": "Directorio temporal (.temp)", - "title-backups-directory": "Backups Directory (backups)", - "title-groups-directory": "Groups Directory (groups)", - "title-recipes-directory": "Recipes Directory (recipes)", - "title-user-directory": "User Directory (user)" + "title-backups-directory": "Directorio de Copias de Seguridad (respaldos)", + "title-groups-directory": "Directorio de Grupos (grupos)", + "title-recipes-directory": "Directorio de Recetas (recetas)", + "title-user-directory": "Directorio de usuario (usuario)" }, - "action-delete-log-files-name": "Delete Log Files", - "action-delete-log-files-description": "Deletes all the log files", - "action-clean-directories-name": "Clean Directories", + "action-delete-log-files-name": "Borrar archivos de registro", + "action-delete-log-files-description": "Elimina todos los archivos de registro", + "action-clean-directories-name": "Limpiar directorios", "action-clean-directories-description": "Removes all the recipe folders that are not valid UUIDs", "action-clean-temporary-files-name": "Eliminar archivos temporales", "action-clean-temporary-files-description": "Eliminar todos los archivos y carpetas del directorio .temp", @@ -1119,8 +1119,8 @@ "ingredients-natural-language-processor": "Ingredients Natural Language Processor", "ingredients-natural-language-processor-explanation": "Mealie uses Conditional Random Fields (CRFs) for parsing and processing ingredients. The model used for ingredients is based off a data set of over 100,000 ingredients from a dataset compiled by the New York Times. Note that as the model is trained in English only, you may have varied results when using the model in other languages. This page is a playground for testing the model.", "ingredients-natural-language-processor-explanation-2": "It's not perfect, but it yields great results in general and is a good starting point for manually parsing ingredients into individual fields. Alternatively, you can also use the \"Brute\" processor that uses a pattern matching technique to identify ingredients.", - "nlp": "NLP", - "brute": "Brute", + "nlp": "PLN", + "brute": "En bruto", "show-individual-confidence": "Mostrar confianza individual", "ingredient-text": "Texto del ingrediente", "average-confident": "{0} Confianza", @@ -1148,31 +1148,31 @@ "user-settings-description": "Administrar preferencias, cambiar contraseña y actualizar correo electrónico", "api-tokens-description": "Administra tus API Tokens para acceder desde aplicaciones externas", "group-description": "These items are shared within your group. Editing one of them will change it for the whole group!", - "group-settings": "Group Settings", + "group-settings": "Ajustes de grupo", "group-settings-description": "Manage your common group settings like mealplan and privacy settings.", "cookbooks-description": "Manage a collection of recipe categories and generate pages for them.", - "members": "Members", - "members-description": "See who's in your group and manage their permissions.", + "members": "Miembros", + "members-description": "Ver quién está en tu grupo y administrar sus permisos.", "webhooks-description": "Setup webhooks that trigger on days that you have have mealplan scheduled.", - "notifiers": "Notifiers", + "notifiers": "Notificaciones", "notifiers-description": "Setup email and push notifications that trigger on specific events.", - "manage-data": "Manage Data", - "manage-data-description": "Manage your Food and Units (more options coming soon)", - "data-migrations": "Data Migrations", - "data-migrations-description": "Migrate your existing data from other applications like Nextcloud Recipes and Chowdown", - "email-sent": "Email Sent", - "error-sending-email": "Error Sending Email", - "personal-information": "Personal Information", - "preferences": "Preferences", - "show-advanced-description": "Show advanced features (API Keys, Webhooks, and Data Management)", - "back-to-profile": "Back to Profile", - "looking-for-privacy-settings": "Looking for Privacy Settings?", - "manage-your-api-tokens": "Manage Your API Tokens", - "manage-user-profile": "Manage User Profile", - "manage-cookbooks": "Manage Cookbooks", + "manage-data": "Administrar datos", + "manage-data-description": "Administra tu Comida y Unidades (próximamente más opciones)", + "data-migrations": "Migración de datos", + "data-migrations-description": "Migrar tus datos existentes de otras aplicaciones como Nextcloud Recipes y Chowdown", + "email-sent": "Email enviado", + "error-sending-email": "Error enviando email", + "personal-information": "Datos Personales", + "preferences": "Preferencias", + "show-advanced-description": "Mostrar características avanzadas (API Keys, Webhooks y Gestión de Datos)", + "back-to-profile": "Volver al perfil", + "looking-for-privacy-settings": "¿Buscas los ajustes de privacidad?", + "manage-your-api-tokens": "Gestiona tus API tokens", + "manage-user-profile": "Administrar Perfil de Usuario", + "manage-cookbooks": "Administrar Recetario", "manage-members": "Gestionar miembros", - "manage-webhooks": "Manage Webhooks", - "manage-notifiers": "Manage Notifiers", + "manage-webhooks": "Gestionar Webhooks", + "manage-notifiers": "Administrar Notificadores", "manage-data-migrations": "Administrar Migraciones de Datos" }, "cookbook": { @@ -1187,7 +1187,7 @@ "require-all-tools": "Requiere todos los utensilios", "cookbook-name": "Nombre del recetario", "cookbook-with-name": "Recetario {0}", - "create-a-cookbook": "Create a Cookbook", - "cookbook": "Cookbook" + "create-a-cookbook": "Crear Recetario", + "cookbook": "Recetario" } } diff --git a/mealie/lang/messages/es-ES.json b/mealie/lang/messages/es-ES.json index b7ee19a8697e..60c7cc2dd5c0 100644 --- a/mealie/lang/messages/es-ES.json +++ b/mealie/lang/messages/es-ES.json @@ -33,12 +33,12 @@ "generic-deleted": "Se ha eliminado {name}" }, "datetime": { - "year": "year|years", - "day": "day|days", - "hour": "hour|hours", - "minute": "minute|minutes", - "second": "second|seconds", - "millisecond": "millisecond|milliseconds", - "microsecond": "microsecond|microseconds" + "year": "año|años", + "day": "día|días", + "hour": "hora|horas", + "minute": "minuto|minutos", + "second": "segundo|segundos", + "millisecond": "milisegundo|milisegundos", + "microsecond": "microsegundo|microsegundos" } } From 5d686203823c713d533aefdce7df928a71da40dd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 17 Feb 2024 10:55:06 -0600 Subject: [PATCH 118/130] chore(deps): update dependency pytest-asyncio to v0.23.5 (#3136) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- poetry.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index 2eda600da3aa..2ce9e593d955 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1969,17 +1969,17 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no [[package]] name = "pytest-asyncio" -version = "0.23.4" +version = "0.23.5" description = "Pytest support for asyncio" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-asyncio-0.23.4.tar.gz", hash = "sha256:2143d9d9375bf372a73260e4114541485e84fca350b0b6b92674ca56ff5f7ea2"}, - {file = "pytest_asyncio-0.23.4-py3-none-any.whl", hash = "sha256:b0079dfac14b60cd1ce4691fbfb1748fe939db7d0234b5aba97197d10fbe0fef"}, + {file = "pytest-asyncio-0.23.5.tar.gz", hash = "sha256:3a048872a9c4ba14c3e90cc1aa20cbc2def7d01c7c8db3777ec281ba9c057675"}, + {file = "pytest_asyncio-0.23.5-py3-none-any.whl", hash = "sha256:4e7093259ba018d58ede7d5315131d21923a60f8a6e9ee266ce1589685c89eac"}, ] [package.dependencies] -pytest = ">=7.0.0,<8" +pytest = ">=7.0.0,<9" [package.extras] docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] From 349ccbad6f71ec4054438c44cf80bbe1039fd5ce Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 17 Feb 2024 11:11:10 -0600 Subject: [PATCH 119/130] fix(deps): update dependency pydantic-settings to v2.2.0 (#3182) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- poetry.lock | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 2ce9e593d955..38f0f5338858 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1813,19 +1813,23 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pydantic-settings" -version = "2.1.0" +version = "2.2.0" description = "Settings management using Pydantic" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_settings-2.1.0-py3-none-any.whl", hash = "sha256:7621c0cb5d90d1140d2f0ef557bdf03573aac7035948109adf2574770b77605a"}, - {file = "pydantic_settings-2.1.0.tar.gz", hash = "sha256:26b1492e0a24755626ac5e6d715e9077ab7ad4fb5f19a8b7ed7011d52f36141c"}, + {file = "pydantic_settings-2.2.0-py3-none-any.whl", hash = "sha256:5f7bcaf9ad4419559dc5ac155c0324a9aeb2547c60471ee7c7d026f467a6b515"}, + {file = "pydantic_settings-2.2.0.tar.gz", hash = "sha256:648d0a76673e69c51278979cba2e83cf16a23d57519bfd7e553d1c3f37db5560"}, ] [package.dependencies] pydantic = ">=2.3.0" python-dotenv = ">=0.21.0" +[package.extras] +toml = ["tomlkit (>=0.12)"] +yaml = ["pyyaml (>=6.0.1)"] + [[package]] name = "pydantic-to-typescript" version = "1.0.10" From 5d89d53a4a472179dd50bc8c9d2f21f010f277ac Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 17 Feb 2024 11:34:00 -0600 Subject: [PATCH 120/130] chore(deps): update dependency pytest to v8 (#3060) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- poetry.lock | 20 ++++++++++---------- pyproject.toml | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/poetry.lock b/poetry.lock index 38f0f5338858..d27a6e7c5c2d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1564,13 +1564,13 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2)", "pytest- [[package]] name = "pluggy" -version = "1.0.0" +version = "1.4.0" description = "plugin and hook calling mechanisms for python" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, + {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, + {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, ] [package.extras] @@ -1951,13 +1951,13 @@ rdflib = "*" [[package]] name = "pytest" -version = "7.4.4" +version = "8.0.1" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, - {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, + {file = "pytest-8.0.1-py3-none-any.whl", hash = "sha256:3e4f16fe1c0a9dc9d9389161c127c3edc5d810c38d6793042fb81d9f48a59fca"}, + {file = "pytest-8.0.1.tar.gz", hash = "sha256:267f6563751877d772019b13aacbe4e860d73fe8f651f28112e9ac37de7513ae"}, ] [package.dependencies] @@ -1965,7 +1965,7 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" -pluggy = ">=0.12,<2.0" +pluggy = ">=1.3.0,<2.0" tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] @@ -3033,4 +3033,4 @@ pgsql = ["psycopg2-binary"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "ee8a6a7655483cd1022a820d3a06c90b2ce52e6e725370ee8b8fcec5a196b0eb" +content-hash = "b8a0c715906337427ee8f676d2dfbc49be273118818823a19766412766e66a5c" diff --git a/pyproject.toml b/pyproject.toml index c34712e43b4f..0879ec737472 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ mypy = "^1.5.1" pre-commit = "^3.3.3" pydantic-to-typescript = "^1.0.7" pylint = "^3.0.0" -pytest = "^7.2.0" +pytest = "^8.0.0" pytest-asyncio = "^0.23.0" rich = "^13.5.2" ruff = "^0.2.0" From 1e4dbe4e95e03569135ff522a31db25e52e86c89 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 17 Feb 2024 19:45:32 -0600 Subject: [PATCH 121/130] chore(deps): update dependency ruff to v0.2.2 (#3187) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- poetry.lock | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/poetry.lock b/poetry.lock index d27a6e7c5c2d..cf0fd1d55104 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2470,28 +2470,28 @@ pyasn1 = ">=0.1.3" [[package]] name = "ruff" -version = "0.2.1" +version = "0.2.2" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.2.1-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:dd81b911d28925e7e8b323e8d06951554655021df8dd4ac3045d7212ac4ba080"}, - {file = "ruff-0.2.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:dc586724a95b7d980aa17f671e173df00f0a2eef23f8babbeee663229a938fec"}, - {file = "ruff-0.2.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c92db7101ef5bfc18e96777ed7bc7c822d545fa5977e90a585accac43d22f18a"}, - {file = "ruff-0.2.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:13471684694d41ae0f1e8e3a7497e14cd57ccb7dd72ae08d56a159d6c9c3e30e"}, - {file = "ruff-0.2.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a11567e20ea39d1f51aebd778685582d4c56ccb082c1161ffc10f79bebe6df35"}, - {file = "ruff-0.2.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:00a818e2db63659570403e44383ab03c529c2b9678ba4ba6c105af7854008105"}, - {file = "ruff-0.2.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be60592f9d218b52f03384d1325efa9d3b41e4c4d55ea022cd548547cc42cd2b"}, - {file = "ruff-0.2.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbd2288890b88e8aab4499e55148805b58ec711053588cc2f0196a44f6e3d855"}, - {file = "ruff-0.2.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3ef052283da7dec1987bba8d8733051c2325654641dfe5877a4022108098683"}, - {file = "ruff-0.2.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7022d66366d6fded4ba3889f73cd791c2d5621b2ccf34befc752cb0df70f5fad"}, - {file = "ruff-0.2.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0a725823cb2a3f08ee743a534cb6935727d9e47409e4ad72c10a3faf042ad5ba"}, - {file = "ruff-0.2.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0034d5b6323e6e8fe91b2a1e55b02d92d0b582d2953a2b37a67a2d7dedbb7acc"}, - {file = "ruff-0.2.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e5cb5526d69bb9143c2e4d2a115d08ffca3d8e0fddc84925a7b54931c96f5c02"}, - {file = "ruff-0.2.1-py3-none-win32.whl", hash = "sha256:6b95ac9ce49b4fb390634d46d6ece32ace3acdd52814671ccaf20b7f60adb232"}, - {file = "ruff-0.2.1-py3-none-win_amd64.whl", hash = "sha256:e3affdcbc2afb6f5bd0eb3130139ceedc5e3f28d206fe49f63073cb9e65988e0"}, - {file = "ruff-0.2.1-py3-none-win_arm64.whl", hash = "sha256:efababa8e12330aa94a53e90a81eb6e2d55f348bc2e71adbf17d9cad23c03ee6"}, - {file = "ruff-0.2.1.tar.gz", hash = "sha256:3b42b5d8677cd0c72b99fcaf068ffc62abb5a19e71b4a3b9cfa50658a0af02f1"}, + {file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0a9efb032855ffb3c21f6405751d5e147b0c6b631e3ca3f6b20f917572b97eb6"}, + {file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d450b7fbff85913f866a5384d8912710936e2b96da74541c82c1b458472ddb39"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecd46e3106850a5c26aee114e562c329f9a1fbe9e4821b008c4404f64ff9ce73"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e22676a5b875bd72acd3d11d5fa9075d3a5f53b877fe7b4793e4673499318ba"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1695700d1e25a99d28f7a1636d85bafcc5030bba9d0578c0781ba1790dbcf51c"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b0c232af3d0bd8f521806223723456ffebf8e323bd1e4e82b0befb20ba18388e"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f63d96494eeec2fc70d909393bcd76c69f35334cdbd9e20d089fb3f0640216ca"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a61ea0ff048e06de273b2e45bd72629f470f5da8f71daf09fe481278b175001"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1439c8f407e4f356470e54cdecdca1bd5439a0673792dbe34a2b0a551a2fe3"}, + {file = "ruff-0.2.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:940de32dc8853eba0f67f7198b3e79bc6ba95c2edbfdfac2144c8235114d6726"}, + {file = "ruff-0.2.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0c126da55c38dd917621552ab430213bdb3273bb10ddb67bc4b761989210eb6e"}, + {file = "ruff-0.2.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3b65494f7e4bed2e74110dac1f0d17dc8e1f42faaa784e7c58a98e335ec83d7e"}, + {file = "ruff-0.2.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1ec49be4fe6ddac0503833f3ed8930528e26d1e60ad35c2446da372d16651ce9"}, + {file = "ruff-0.2.2-py3-none-win32.whl", hash = "sha256:d920499b576f6c68295bc04e7b17b6544d9d05f196bb3aac4358792ef6f34325"}, + {file = "ruff-0.2.2-py3-none-win_amd64.whl", hash = "sha256:cc9a91ae137d687f43a44c900e5d95e9617cb37d4c989e462980ba27039d239d"}, + {file = "ruff-0.2.2-py3-none-win_arm64.whl", hash = "sha256:c9d15fc41e6054bfc7200478720570078f0b41c9ae4f010bcc16bd6f4d1aacdd"}, + {file = "ruff-0.2.2.tar.gz", hash = "sha256:e62ed7f36b3068a30ba39193a14274cd706bc486fad521276458022f7bccb31d"}, ] [[package]] From 59cd68d54ab3dbdb59c9ed5a30759fc4943400c4 Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Sun, 18 Feb 2024 06:47:21 +0000 Subject: [PATCH 122/130] fixed UUID check --- mealie/repos/repository_recipes.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mealie/repos/repository_recipes.py b/mealie/repos/repository_recipes.py index d2a9993628c2..7fbe99879fdd 100644 --- a/mealie/repos/repository_recipes.py +++ b/mealie/repos/repository_recipes.py @@ -140,7 +140,11 @@ class RepositoryRecipes(RepositoryGeneric[Recipe, RecipeModel]): if isinstance(i, UUID): ids.append(i) else: - slugs.append(i) + try: + i_as_uuid = UUID(i) + ids.append(i_as_uuid) + except ValueError: + slugs.append(i) additional_ids = self.session.execute(select(model.id).filter(model.slug.in_(slugs))).scalars().all() return ids + additional_ids From 34d742963a04a1c810d7985749decfec5178bf02 Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Sun, 18 Feb 2024 06:48:09 +0000 Subject: [PATCH 123/130] added tests --- .../user_recipe_tests/test_recipe_crud.py | 68 ++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/tests/integration_tests/user_recipe_tests/test_recipe_crud.py b/tests/integration_tests/user_recipe_tests/test_recipe_crud.py index 4a431827006f..6c78eeaddaed 100644 --- a/tests/integration_tests/user_recipe_tests/test_recipe_crud.py +++ b/tests/integration_tests/user_recipe_tests/test_recipe_crud.py @@ -17,8 +17,10 @@ from recipe_scrapers._schemaorg import SchemaOrg from slugify import slugify from mealie.repos.repository_factory import AllRepositories -from mealie.schema.recipe.recipe import RecipeCategory, RecipeSummary, RecipeTag +from mealie.schema.recipe.recipe import Recipe, RecipeCategory, RecipeSummary, RecipeTag +from mealie.schema.recipe.recipe_category import CategorySave, TagSave from mealie.schema.recipe.recipe_notes import RecipeNote +from mealie.schema.recipe.recipe_tool import RecipeToolSave from mealie.services.recipe.recipe_data_service import RecipeDataService from mealie.services.scraper.recipe_scraper import DEFAULT_SCRAPER_STRATEGIES from tests import data, utils @@ -740,6 +742,70 @@ def test_get_recipe_by_slug_or_id(api_client: TestClient, unique_user: utils.Tes assert recipe_data["id"] == recipe_id +@pytest.mark.parametrize("organizer_type", ["tags", "categories", "tools"]) +def test_get_recipes_organizer_filter( + api_client: TestClient, unique_user: utils.TestUser, organizer_type: str, database: AllRepositories +): + # create recipes with different organizers + tags = database.tags.by_group(unique_user.group_id).create_many( + [TagSave(name=random_string(), group_id=unique_user.group_id) for _ in range(3)] + ) + categories = database.categories.by_group(unique_user.group_id).create_many( + [CategorySave(name=random_string(), group_id=unique_user.group_id) for _ in range(3)] + ) + tools = database.tools.by_group(unique_user.group_id).create_many( + [RecipeToolSave(name=random_string(), group_id=unique_user.group_id) for _ in range(3)] + ) + + new_recipes_data: list[dict] = [] + for i in range(40): + name = random_string() + new_recipes_data.append( + Recipe( + id=uuid4(), + user_id=unique_user.user_id, + group_id=unique_user.group_id, + name=name, + slug=name, + tags=[random.choice(tags)] if i % 2 else [], + recipe_category=[random.choice(categories)] if i % 2 else [], + tools=[random.choice(tools)] if i % 2 else [], + ) + ) + + recipes = database.recipes.by_group(unique_user.group_id).create_many(new_recipes_data) # type: ignore + + # get recipes by organizer + if organizer_type == "tags": + organizer = random.choice(tags) + expected_recipe_ids = set( + str(recipe.id) for recipe in recipes if organizer.id in [tag.id for tag in recipe.tags or []] + ) + elif organizer_type == "categories": + organizer = random.choice(categories) + expected_recipe_ids = set( + str(recipe.id) + for recipe in recipes + if organizer.id in [category.id for category in recipe.recipe_category or []] + ) + elif organizer_type == "tools": + organizer = random.choice(tools) + expected_recipe_ids = set( + str(recipe.id) for recipe in recipes if organizer.id in [tool.id for tool in recipe.tools or []] + ) + else: + raise ValueError(f"Unknown organizer type: {organizer_type}") + + query_params = {organizer_type: str(organizer.id)} + response = api_client.get(api_routes.recipes, params=query_params, headers=unique_user.token) + assert response.status_code == 200 + + response_json = response.json() + assert len(response_json["items"]) == len(expected_recipe_ids) + fetched_recipes_ids = [recipe["id"] for recipe in response_json["items"]] + assert set(fetched_recipes_ids) == expected_recipe_ids + + def test_get_random_order(api_client: TestClient, unique_user: utils.TestUser): # Create more recipes for stable random ordering slugs = [random_string(10) for _ in range(7)] From ea7005e8227e0e15c7e4eb2c5bdb4e79f22469bc Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Sun, 18 Feb 2024 13:59:03 -0600 Subject: [PATCH 124/130] feat: Shopping List Editor Improvements (#3178) * modify new item factory to default to zero qty and use last isFood value * automatically set the label of an item when choosing a food * fix when switching to a food with no label * removed trivial type annotation * more lint * removed debug log --- .../Domain/ShoppingList/ShoppingListItemEditor.vue | 12 +++++++++++- frontend/pages/shopping-lists/_id.vue | 10 +++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/frontend/components/Domain/ShoppingList/ShoppingListItemEditor.vue b/frontend/components/Domain/ShoppingList/ShoppingListItemEditor.vue index f68c742e0abc..249e3f121b0e 100644 --- a/frontend/components/Domain/ShoppingList/ShoppingListItemEditor.vue +++ b/frontend/components/Domain/ShoppingList/ShoppingListItemEditor.vue @@ -95,7 +95,7 @@ diff --git a/frontend/pages/shopping-lists/_id.vue b/frontend/pages/shopping-lists/_id.vue index 9dabc3ae1e42..c5bdd2cc8249 100644 --- a/frontend/pages/shopping-lists/_id.vue +++ b/frontend/pages/shopping-lists/_id.vue @@ -665,6 +665,11 @@ export default defineComponent({ return; } + if (!createListItemData.value.foodId && !createListItemData.value.note) { + // don't create an empty item + return; + } + loadingCounter.value += 1; // make sure it's inserted into the end of the list, which may have been updated @@ -676,7 +681,6 @@ export default defineComponent({ if (data) { createListItemData.value = listItemFactory(createListItemData.value.isFood || false); - createEditorOpen.value = false; refresh(); } } From 2c1185e1d40e29664b154bc9b99b072954c35e0b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 15:37:07 -0600 Subject: [PATCH 129/130] fix(deps): update dependency pydantic-settings to v2.2.1 (#3197) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- poetry.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index 33514ebe8e80..034d05ce6eb9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1813,13 +1813,13 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pydantic-settings" -version = "2.2.0" +version = "2.2.1" description = "Settings management using Pydantic" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_settings-2.2.0-py3-none-any.whl", hash = "sha256:5f7bcaf9ad4419559dc5ac155c0324a9aeb2547c60471ee7c7d026f467a6b515"}, - {file = "pydantic_settings-2.2.0.tar.gz", hash = "sha256:648d0a76673e69c51278979cba2e83cf16a23d57519bfd7e553d1c3f37db5560"}, + {file = "pydantic_settings-2.2.1-py3-none-any.whl", hash = "sha256:0235391d26db4d2190cb9b31051c4b46882d28a51533f97440867f012d4da091"}, + {file = "pydantic_settings-2.2.1.tar.gz", hash = "sha256:00b9f6a5e95553590434c0fa01ead0b216c3e10bc54ae02e37f359948643c5ed"}, ] [package.dependencies] @@ -1827,7 +1827,7 @@ pydantic = ">=2.3.0" python-dotenv = ">=0.21.0" [package.extras] -toml = ["tomlkit (>=0.12)"] +toml = ["tomli (>=2.0.1)"] yaml = ["pyyaml (>=6.0.1)"] [[package]] From 9e1edbacb6e820cbf828be0e585cb11bccacb2c2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 20 Feb 2024 19:59:39 +0000 Subject: [PATCH 130/130] chore(deps): update dependency coverage to v7.4.2 --- poetry.lock | 106 ++++++++++++++++++++++++++-------------------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/poetry.lock b/poetry.lock index 034d05ce6eb9..0226f119f97a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -387,63 +387,63 @@ files = [ [[package]] name = "coverage" -version = "7.4.1" +version = "7.4.2" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:077d366e724f24fc02dbfe9d946534357fda71af9764ff99d73c3c596001bbd7"}, - {file = "coverage-7.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0193657651f5399d433c92f8ae264aff31fc1d066deee4b831549526433f3f61"}, - {file = "coverage-7.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d17bbc946f52ca67adf72a5ee783cd7cd3477f8f8796f59b4974a9b59cacc9ee"}, - {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3277f5fa7483c927fe3a7b017b39351610265308f5267ac6d4c2b64cc1d8d25"}, - {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dceb61d40cbfcf45f51e59933c784a50846dc03211054bd76b421a713dcdf19"}, - {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6008adeca04a445ea6ef31b2cbaf1d01d02986047606f7da266629afee982630"}, - {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c61f66d93d712f6e03369b6a7769233bfda880b12f417eefdd4f16d1deb2fc4c"}, - {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9bb62fac84d5f2ff523304e59e5c439955fb3b7f44e3d7b2085184db74d733b"}, - {file = "coverage-7.4.1-cp310-cp310-win32.whl", hash = "sha256:f86f368e1c7ce897bf2457b9eb61169a44e2ef797099fb5728482b8d69f3f016"}, - {file = "coverage-7.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:869b5046d41abfea3e381dd143407b0d29b8282a904a19cb908fa24d090cc018"}, - {file = "coverage-7.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8ffb498a83d7e0305968289441914154fb0ef5d8b3157df02a90c6695978295"}, - {file = "coverage-7.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3cacfaefe6089d477264001f90f55b7881ba615953414999c46cc9713ff93c8c"}, - {file = "coverage-7.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d6850e6e36e332d5511a48a251790ddc545e16e8beaf046c03985c69ccb2676"}, - {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18e961aa13b6d47f758cc5879383d27b5b3f3dcd9ce8cdbfdc2571fe86feb4dd"}, - {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfd1e1b9f0898817babf840b77ce9fe655ecbe8b1b327983df485b30df8cc011"}, - {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6b00e21f86598b6330f0019b40fb397e705135040dbedc2ca9a93c7441178e74"}, - {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:536d609c6963c50055bab766d9951b6c394759190d03311f3e9fcf194ca909e1"}, - {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7ac8f8eb153724f84885a1374999b7e45734bf93a87d8df1e7ce2146860edef6"}, - {file = "coverage-7.4.1-cp311-cp311-win32.whl", hash = "sha256:f3771b23bb3675a06f5d885c3630b1d01ea6cac9e84a01aaf5508706dba546c5"}, - {file = "coverage-7.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:9d2f9d4cc2a53b38cabc2d6d80f7f9b7e3da26b2f53d48f05876fef7956b6968"}, - {file = "coverage-7.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f68ef3660677e6624c8cace943e4765545f8191313a07288a53d3da188bd8581"}, - {file = "coverage-7.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23b27b8a698e749b61809fb637eb98ebf0e505710ec46a8aa6f1be7dc0dc43a6"}, - {file = "coverage-7.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3424c554391dc9ef4a92ad28665756566a28fecf47308f91841f6c49288e66"}, - {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0860a348bf7004c812c8368d1fc7f77fe8e4c095d661a579196a9533778e156"}, - {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe558371c1bdf3b8fa03e097c523fb9645b8730399c14fe7721ee9c9e2a545d3"}, - {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3468cc8720402af37b6c6e7e2a9cdb9f6c16c728638a2ebc768ba1ef6f26c3a1"}, - {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:02f2edb575d62172aa28fe00efe821ae31f25dc3d589055b3fb64d51e52e4ab1"}, - {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ca6e61dc52f601d1d224526360cdeab0d0712ec104a2ce6cc5ccef6ed9a233bc"}, - {file = "coverage-7.4.1-cp312-cp312-win32.whl", hash = "sha256:ca7b26a5e456a843b9b6683eada193fc1f65c761b3a473941efe5a291f604c74"}, - {file = "coverage-7.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:85ccc5fa54c2ed64bd91ed3b4a627b9cce04646a659512a051fa82a92c04a448"}, - {file = "coverage-7.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8bdb0285a0202888d19ec6b6d23d5990410decb932b709f2b0dfe216d031d218"}, - {file = "coverage-7.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:918440dea04521f499721c039863ef95433314b1db00ff826a02580c1f503e45"}, - {file = "coverage-7.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:379d4c7abad5afbe9d88cc31ea8ca262296480a86af945b08214eb1a556a3e4d"}, - {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b094116f0b6155e36a304ff912f89bbb5067157aff5f94060ff20bbabdc8da06"}, - {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f5968608b1fe2a1d00d01ad1017ee27efd99b3437e08b83ded9b7af3f6f766"}, - {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10e88e7f41e6197ea0429ae18f21ff521d4f4490aa33048f6c6f94c6045a6a75"}, - {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a4a3907011d39dbc3e37bdc5df0a8c93853c369039b59efa33a7b6669de04c60"}, - {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d224f0c4c9c98290a6990259073f496fcec1b5cc613eecbd22786d398ded3ad"}, - {file = "coverage-7.4.1-cp38-cp38-win32.whl", hash = "sha256:23f5881362dcb0e1a92b84b3c2809bdc90db892332daab81ad8f642d8ed55042"}, - {file = "coverage-7.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:a07f61fc452c43cd5328b392e52555f7d1952400a1ad09086c4a8addccbd138d"}, - {file = "coverage-7.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e738a492b6221f8dcf281b67129510835461132b03024830ac0e554311a5c54"}, - {file = "coverage-7.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46342fed0fff72efcda77040b14728049200cbba1279e0bf1188f1f2078c1d70"}, - {file = "coverage-7.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9641e21670c68c7e57d2053ddf6c443e4f0a6e18e547e86af3fad0795414a628"}, - {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeb2c2688ed93b027eb0d26aa188ada34acb22dceea256d76390eea135083950"}, - {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d12c923757de24e4e2110cf8832d83a886a4cf215c6e61ed506006872b43a6d1"}, - {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0491275c3b9971cdbd28a4595c2cb5838f08036bca31765bad5e17edf900b2c7"}, - {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8dfc5e195bbef80aabd81596ef52a1277ee7143fe419efc3c4d8ba2754671756"}, - {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1a78b656a4d12b0490ca72651fe4d9f5e07e3c6461063a9b6265ee45eb2bdd35"}, - {file = "coverage-7.4.1-cp39-cp39-win32.whl", hash = "sha256:f90515974b39f4dea2f27c0959688621b46d96d5a626cf9c53dbc653a895c05c"}, - {file = "coverage-7.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:64e723ca82a84053dd7bfcc986bdb34af8d9da83c521c19d6b472bc6880e191a"}, - {file = "coverage-7.4.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:32a8d985462e37cfdab611a6f95b09d7c091d07668fdc26e47a725ee575fe166"}, - {file = "coverage-7.4.1.tar.gz", hash = "sha256:1ed4b95480952b1a26d863e546fa5094564aa0065e1e5f0d4d0041f293251d04"}, + {file = "coverage-7.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bf54c3e089179d9d23900e3efc86d46e4431188d9a657f345410eecdd0151f50"}, + {file = "coverage-7.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fe6e43c8b510719b48af7db9631b5fbac910ade4bd90e6378c85ac5ac706382c"}, + {file = "coverage-7.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b98c89db1b150d851a7840142d60d01d07677a18f0f46836e691c38134ed18b"}, + {file = "coverage-7.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5f9683be6a5b19cd776ee4e2f2ffb411424819c69afab6b2db3a0a364ec6642"}, + {file = "coverage-7.4.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78cdcbf7b9cb83fe047ee09298e25b1cd1636824067166dc97ad0543b079d22f"}, + {file = "coverage-7.4.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2599972b21911111114100d362aea9e70a88b258400672626efa2b9e2179609c"}, + {file = "coverage-7.4.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ef00d31b7569ed3cb2036f26565f1984b9fc08541731ce01012b02a4c238bf03"}, + {file = "coverage-7.4.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:20a875bfd8c282985c4720c32aa05056f77a68e6d8bbc5fe8632c5860ee0b49b"}, + {file = "coverage-7.4.2-cp310-cp310-win32.whl", hash = "sha256:b3f2b1eb229f23c82898eedfc3296137cf1f16bb145ceab3edfd17cbde273fb7"}, + {file = "coverage-7.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7df95fdd1432a5d2675ce630fef5f239939e2b3610fe2f2b5bf21fa505256fa3"}, + {file = "coverage-7.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a8ddbd158e069dded57738ea69b9744525181e99974c899b39f75b2b29a624e2"}, + {file = "coverage-7.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81a5fb41b0d24447a47543b749adc34d45a2cf77b48ca74e5bf3de60a7bd9edc"}, + {file = "coverage-7.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2412e98e70f16243be41d20836abd5f3f32edef07cbf8f407f1b6e1ceae783ac"}, + {file = "coverage-7.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb79414c15c6f03f56cc68fa06994f047cf20207c31b5dad3f6bab54a0f66ef"}, + {file = "coverage-7.4.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf89ab85027427d351f1de918aff4b43f4eb5f33aff6835ed30322a86ac29c9e"}, + {file = "coverage-7.4.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a178b7b1ac0f1530bb28d2e51f88c0bab3e5949835851a60dda80bff6052510c"}, + {file = "coverage-7.4.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:06fe398145a2e91edaf1ab4eee66149c6776c6b25b136f4a86fcbbb09512fd10"}, + {file = "coverage-7.4.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:18cac867950943fe93d6cd56a67eb7dcd2d4a781a40f4c1e25d6f1ed98721a55"}, + {file = "coverage-7.4.2-cp311-cp311-win32.whl", hash = "sha256:f72cdd2586f9a769570d4b5714a3837b3a59a53b096bb954f1811f6a0afad305"}, + {file = "coverage-7.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:d779a48fac416387dd5673fc5b2d6bd903ed903faaa3247dc1865c65eaa5a93e"}, + {file = "coverage-7.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:adbdfcda2469d188d79771d5696dc54fab98a16d2ef7e0875013b5f56a251047"}, + {file = "coverage-7.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ac4bab32f396b03ebecfcf2971668da9275b3bb5f81b3b6ba96622f4ef3f6e17"}, + {file = "coverage-7.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:006d220ba2e1a45f1de083d5022d4955abb0aedd78904cd5a779b955b019ec73"}, + {file = "coverage-7.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3733545eb294e5ad274abe131d1e7e7de4ba17a144505c12feca48803fea5f64"}, + {file = "coverage-7.4.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42a9e754aa250fe61f0f99986399cec086d7e7a01dd82fd863a20af34cbce962"}, + {file = "coverage-7.4.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2ed37e16cf35c8d6e0b430254574b8edd242a367a1b1531bd1adc99c6a5e00fe"}, + {file = "coverage-7.4.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:b953275d4edfab6cc0ed7139fa773dfb89e81fee1569a932f6020ce7c6da0e8f"}, + {file = "coverage-7.4.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32b4ab7e6c924f945cbae5392832e93e4ceb81483fd6dc4aa8fb1a97b9d3e0e1"}, + {file = "coverage-7.4.2-cp312-cp312-win32.whl", hash = "sha256:f5df76c58977bc35a49515b2fbba84a1d952ff0ec784a4070334dfbec28a2def"}, + {file = "coverage-7.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:34423abbaad70fea9d0164add189eabaea679068ebdf693baa5c02d03e7db244"}, + {file = "coverage-7.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5b11f9c6587668e495cc7365f85c93bed34c3a81f9f08b0920b87a89acc13469"}, + {file = "coverage-7.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:51593a1f05c39332f623d64d910445fdec3d2ac2d96b37ce7f331882d5678ddf"}, + {file = "coverage-7.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69f1665165ba2fe7614e2f0c1aed71e14d83510bf67e2ee13df467d1c08bf1e8"}, + {file = "coverage-7.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3c8bbb95a699c80a167478478efe5e09ad31680931ec280bf2087905e3b95ec"}, + {file = "coverage-7.4.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:175f56572f25e1e1201d2b3e07b71ca4d201bf0b9cb8fad3f1dfae6a4188de86"}, + {file = "coverage-7.4.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8562ca91e8c40864942615b1d0b12289d3e745e6b2da901d133f52f2d510a1e3"}, + {file = "coverage-7.4.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d9a1ef0f173e1a19738f154fb3644f90d0ada56fe6c9b422f992b04266c55d5a"}, + {file = "coverage-7.4.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f40ac873045db4fd98a6f40387d242bde2708a3f8167bd967ccd43ad46394ba2"}, + {file = "coverage-7.4.2-cp38-cp38-win32.whl", hash = "sha256:d1b750a8409bec61caa7824bfd64a8074b6d2d420433f64c161a8335796c7c6b"}, + {file = "coverage-7.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b4ae777bebaed89e3a7e80c4a03fac434a98a8abb5251b2a957d38fe3fd30088"}, + {file = "coverage-7.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3ff7f92ae5a456101ca8f48387fd3c56eb96353588e686286f50633a611afc95"}, + {file = "coverage-7.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:861d75402269ffda0b33af94694b8e0703563116b04c681b1832903fac8fd647"}, + {file = "coverage-7.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3507427d83fa961cbd73f11140f4a5ce84208d31756f7238d6257b2d3d868405"}, + {file = "coverage-7.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bf711d517e21fb5bc429f5c4308fbc430a8585ff2a43e88540264ae87871e36a"}, + {file = "coverage-7.4.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c00e54f0bd258ab25e7f731ca1d5144b0bf7bec0051abccd2bdcff65fa3262c9"}, + {file = "coverage-7.4.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f8e845d894e39fb53834da826078f6dc1a933b32b1478cf437007367efaf6f6a"}, + {file = "coverage-7.4.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:840456cb1067dc350af9080298c7c2cfdddcedc1cb1e0b30dceecdaf7be1a2d3"}, + {file = "coverage-7.4.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c11ca2df2206a4e3e4c4567f52594637392ed05d7c7fb73b4ea1c658ba560265"}, + {file = "coverage-7.4.2-cp39-cp39-win32.whl", hash = "sha256:3ff5bdb08d8938d336ce4088ca1a1e4b6c8cd3bef8bb3a4c0eb2f37406e49643"}, + {file = "coverage-7.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:ac9e95cefcf044c98d4e2c829cd0669918585755dd9a92e28a1a7012322d0a95"}, + {file = "coverage-7.4.2-pp38.pp39.pp310-none-any.whl", hash = "sha256:f593a4a90118d99014517c2679e04a4ef5aee2d81aa05c26c734d271065efcb6"}, + {file = "coverage-7.4.2.tar.gz", hash = "sha256:1a5ee18e3a8d766075ce9314ed1cb695414bae67df6a4b0805f5137d93d6f1cb"}, ] [package.extras]