From e5d5c28f7175d1dce922822bc45fbce6b8603ce2 Mon Sep 17 00:00:00 2001 From: Litchi Pi Date: Wed, 23 Jul 2025 08:40:30 +0200 Subject: [PATCH 01/20] Return error when sending incomplete data to PUT API endpoint Signed-off-by: Litchi Pi --- mealie/core/exceptions.py | 6 ++++++ mealie/routes/recipe/recipe_crud_routes.py | 15 ++++++++++++--- mealie/services/recipe/recipe_service.py | 12 +++++++++--- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/mealie/core/exceptions.py b/mealie/core/exceptions.py index 89ed9538e..de8cf7d39 100644 --- a/mealie/core/exceptions.py +++ b/mealie/core/exceptions.py @@ -46,3 +46,9 @@ class UserLockedOut(Exception): ... class MissingClaimException(Exception): ... + +class IncompleteData(Exception): + """ + This exception is raised when a user sends incomplete data to the API + """ + pass diff --git a/mealie/routes/recipe/recipe_crud_routes.py b/mealie/routes/recipe/recipe_crud_routes.py index 0504966c8..dd4a9fa25 100644 --- a/mealie/routes/recipe/recipe_crud_routes.py +++ b/mealie/routes/recipe/recipe_crud_routes.py @@ -84,6 +84,9 @@ class RecipeController(BaseRecipeController): elif thrownType == exceptions.NoEntryFound: self.logger.error("No Entry Found on recipe controller action") raise HTTPException(status_code=404, detail=ErrorResponse.respond(message="No Entry Found")) + elif thrownType == exceptions.IncompleteData: + self.logger.error("Incomplete data provided to API route") + raise HTTPException(status_code=400, detail=ErrorResponse.respond(message="Some data were missing on the body of this API request")) elif thrownType == sqlalchemy.exc.IntegrityError: self.logger.error("SQL Integrity Error on recipe controller action") raise HTTPException(status_code=400, detail=ErrorResponse.respond(message="Recipe already exists")) @@ -360,9 +363,15 @@ class RecipeController(BaseRecipeController): @router.put("/{slug}") def update_one(self, slug: str, data: Recipe): - """Updates a recipe by existing slug and data.""" + """ + Updates a recipe by existing slug and data. + + Requires all the fields of the recipe to be passed in the body + + If you wish to update parts of the recipe only, use a PATCH request to this route + """ try: - recipe = self.service.update_one(slug, data) + recipe = self.service.update_one(slug, data, strict=True) except Exception as e: self.handle_exceptions(e) @@ -387,7 +396,7 @@ class RecipeController(BaseRecipeController): lambda: defaultdict(list) ) for recipe in data: - r = self.service.update_one(recipe.id, recipe) # type: ignore + r = self.service.update_one(recipe.id, recipe, strict=True) # type: ignore updated_by_group_and_household[r.group_id][r.household_id].append(r) all_updated: list[Recipe] = [] diff --git a/mealie/services/recipe/recipe_service.py b/mealie/services/recipe/recipe_service.py index 0040ff88a..bbef50244 100644 --- a/mealie/services/recipe/recipe_service.py +++ b/mealie/services/recipe/recipe_service.py @@ -364,7 +364,7 @@ class RecipeService(RecipeServiceBase): return new_recipe - def _pre_update_check(self, slug_or_id: str | UUID, new_data: Recipe) -> Recipe: + def _pre_update_check(self, slug_or_id: str | UUID, new_data: Recipe, strict: bool) -> Recipe: """ gets the recipe from the database and performs a check to see if the user can update the recipe. If the user can't update the recipe, an exception is raised. @@ -377,6 +377,7 @@ class RecipeService(RecipeServiceBase): Args: slug_or_id (str | UUID): recipe slug or id new_data (Recipe): the new recipe data + strict: if the data provided must contain ALL fields, or can be incomplete Raises: exceptions.PermissionDenied (403) @@ -384,6 +385,11 @@ class RecipeService(RecipeServiceBase): recipe = self.get_one(slug_or_id) + if strict: + for field in new_data.__class__.model_fields: + if getattr(new_data, field) is None: + raise exceptions.IncompleteData("Incomplete recipe") + if recipe is None or recipe.settings is None: raise exceptions.NoEntryFound("Recipe not found.") @@ -396,8 +402,8 @@ class RecipeService(RecipeServiceBase): return recipe - def update_one(self, slug_or_id: str | UUID, update_data: Recipe) -> Recipe: - recipe = self._pre_update_check(slug_or_id, update_data) + def update_one(self, slug_or_id: str | UUID, update_data: Recipe, strict: bool) -> Recipe: + recipe = self._pre_update_check(slug_or_id, update_data, strict) new_data = self.group_recipes.update(recipe.slug, update_data) self.check_assets(new_data, recipe.slug) From e183dd1dcdf71f609e9295c1c723b02df7abdc57 Mon Sep 17 00:00:00 2001 From: Litchi Pi Date: Wed, 23 Jul 2025 11:07:07 +0200 Subject: [PATCH 02/20] format --- mealie/core/exceptions.py | 2 ++ mealie/routes/recipe/recipe_crud_routes.py | 11 +++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/mealie/core/exceptions.py b/mealie/core/exceptions.py index de8cf7d39..077a8644d 100644 --- a/mealie/core/exceptions.py +++ b/mealie/core/exceptions.py @@ -47,8 +47,10 @@ class UserLockedOut(Exception): ... class MissingClaimException(Exception): ... + class IncompleteData(Exception): """ This exception is raised when a user sends incomplete data to the API """ + pass diff --git a/mealie/routes/recipe/recipe_crud_routes.py b/mealie/routes/recipe/recipe_crud_routes.py index dd4a9fa25..9d236bf0c 100644 --- a/mealie/routes/recipe/recipe_crud_routes.py +++ b/mealie/routes/recipe/recipe_crud_routes.py @@ -86,7 +86,10 @@ class RecipeController(BaseRecipeController): raise HTTPException(status_code=404, detail=ErrorResponse.respond(message="No Entry Found")) elif thrownType == exceptions.IncompleteData: self.logger.error("Incomplete data provided to API route") - raise HTTPException(status_code=400, detail=ErrorResponse.respond(message="Some data were missing on the body of this API request")) + raise HTTPException( + status_code=400, + detail=ErrorResponse.respond(message="Some data were missing on the body of this API request"), + ) elif thrownType == sqlalchemy.exc.IntegrityError: self.logger.error("SQL Integrity Error on recipe controller action") raise HTTPException(status_code=400, detail=ErrorResponse.respond(message="Recipe already exists")) @@ -364,11 +367,11 @@ class RecipeController(BaseRecipeController): @router.put("/{slug}") def update_one(self, slug: str, data: Recipe): """ - Updates a recipe by existing slug and data. + Updates a recipe by existing slug and data. - Requires all the fields of the recipe to be passed in the body + Requires all the fields of the recipe to be passed in the body - If you wish to update parts of the recipe only, use a PATCH request to this route + If you wish to update parts of the recipe only, use a PATCH request to this route """ try: recipe = self.service.update_one(slug, data, strict=True) From c32c9d4502ea1668421ae64512082ea6a651bb0f Mon Sep 17 00:00:00 2001 From: Litchi Pi Date: Wed, 23 Jul 2025 12:03:39 +0200 Subject: [PATCH 03/20] fixup Signed-off-by: Litchi Pi --- mealie/routes/recipe/recipe_crud_routes.py | 4 ++-- mealie/services/recipe/recipe_service.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mealie/routes/recipe/recipe_crud_routes.py b/mealie/routes/recipe/recipe_crud_routes.py index 9d236bf0c..20065cf41 100644 --- a/mealie/routes/recipe/recipe_crud_routes.py +++ b/mealie/routes/recipe/recipe_crud_routes.py @@ -374,7 +374,7 @@ class RecipeController(BaseRecipeController): If you wish to update parts of the recipe only, use a PATCH request to this route """ try: - recipe = self.service.update_one(slug, data, strict=True) + recipe = self.service.update_one(slug, data) except Exception as e: self.handle_exceptions(e) @@ -399,7 +399,7 @@ class RecipeController(BaseRecipeController): lambda: defaultdict(list) ) for recipe in data: - r = self.service.update_one(recipe.id, recipe, strict=True) # type: ignore + r = self.service.update_one(recipe.id, recipe) # type: ignore updated_by_group_and_household[r.group_id][r.household_id].append(r) all_updated: list[Recipe] = [] diff --git a/mealie/services/recipe/recipe_service.py b/mealie/services/recipe/recipe_service.py index bbef50244..b87730f66 100644 --- a/mealie/services/recipe/recipe_service.py +++ b/mealie/services/recipe/recipe_service.py @@ -402,15 +402,15 @@ class RecipeService(RecipeServiceBase): return recipe - def update_one(self, slug_or_id: str | UUID, update_data: Recipe, strict: bool) -> Recipe: - recipe = self._pre_update_check(slug_or_id, update_data, strict) + def update_one(self, slug_or_id: str | UUID, update_data: Recipe) -> Recipe: + recipe = self._pre_update_check(slug_or_id, update_data, True) new_data = self.group_recipes.update(recipe.slug, update_data) self.check_assets(new_data, recipe.slug) return new_data def patch_one(self, slug_or_id: str | UUID, patch_data: Recipe) -> Recipe: - recipe: Recipe = self._pre_update_check(slug_or_id, patch_data) + recipe: Recipe = self._pre_update_check(slug_or_id, patch_data, False) new_data = self.group_recipes.patch(recipe.slug, patch_data.model_dump(exclude_unset=True)) From 1778790947dd9609a42f544475cc04991a8fc735 Mon Sep 17 00:00:00 2001 From: Litchi Pi Date: Wed, 23 Jul 2025 13:07:55 +0200 Subject: [PATCH 04/20] fixup tests Signed-off-by: Litchi Pi --- .../category_tag_tool_tests/test_organizers_common.py | 4 ++-- .../integration_tests/user_recipe_tests/test_recipe_crud.py | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/integration_tests/category_tag_tool_tests/test_organizers_common.py b/tests/integration_tests/category_tag_tool_tests/test_organizers_common.py index 733bdebd2..8b15cb652 100644 --- a/tests/integration_tests/category_tag_tool_tests/test_organizers_common.py +++ b/tests/integration_tests/category_tag_tool_tests/test_organizers_common.py @@ -175,7 +175,7 @@ def test_organizer_association( ] # Update Recipe - response = api_client.put(api_routes.recipes_slug(slug), json=as_json, headers=unique_user.token) + response = api_client.patch(api_routes.recipes_slug(slug), json=as_json, headers=unique_user.token) assert response.status_code == 200 # Get Recipe Data @@ -223,7 +223,7 @@ def test_organizer_get_by_slug( {"id": item["id"], "group_id": unique_user.group_id, "name": item["name"], "slug": item["slug"]} ] - response = api_client.put(api_routes.recipes_slug(slug), json=as_json, headers=unique_user.token) + response = api_client.patch(api_routes.recipes_slug(slug), json=as_json, headers=unique_user.token) assert response.status_code == 200 # Get Organizer by Slug 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 b87200fc6..36c8efded 100644 --- a/tests/integration_tests/user_recipe_tests/test_recipe_crud.py +++ b/tests/integration_tests/user_recipe_tests/test_recipe_crud.py @@ -470,6 +470,11 @@ def test_read_update( assert response.status_code == 200 assert json.loads(response.text).get("slug") == recipe_data.expected_slug + recipe_incomplete = recipe.copy() + del recipe_incomplete["notes"] + response = api_client.put(recipe_url, json=utils.jsonify(recipe_incomplete), headers=unique_user.token) + assert response.status_code == 400 + response = api_client.get(recipe_url, headers=unique_user.token) assert response.status_code == 200 recipe = json.loads(response.text) From 1834f3e304161ac33b7f3773d475016a4a4aaa5e Mon Sep 17 00:00:00 2001 From: Litchi Pi Date: Wed, 23 Jul 2025 14:29:28 +0200 Subject: [PATCH 05/20] Have a more debuggable output Signed-off-by: Litchi Pi --- mealie/services/recipe/recipe_service.py | 2 +- .../category_tag_tool_tests/test_organizers_common.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/mealie/services/recipe/recipe_service.py b/mealie/services/recipe/recipe_service.py index b87730f66..d636f5461 100644 --- a/mealie/services/recipe/recipe_service.py +++ b/mealie/services/recipe/recipe_service.py @@ -388,7 +388,7 @@ class RecipeService(RecipeServiceBase): if strict: for field in new_data.__class__.model_fields: if getattr(new_data, field) is None: - raise exceptions.IncompleteData("Incomplete recipe") + raise exceptions.IncompleteData(f"Incomplete recipe, missing {field}") if recipe is None or recipe.settings is None: raise exceptions.NoEntryFound("Recipe not found.") diff --git a/tests/integration_tests/category_tag_tool_tests/test_organizers_common.py b/tests/integration_tests/category_tag_tool_tests/test_organizers_common.py index 8b15cb652..7f558bd9a 100644 --- a/tests/integration_tests/category_tag_tool_tests/test_organizers_common.py +++ b/tests/integration_tests/category_tag_tool_tests/test_organizers_common.py @@ -109,6 +109,7 @@ def test_organizer_update( item[key] = update_data[key] response = api_client.put(route.item(item_id), json=item, headers=unique_user.token) + print(response) assert response.status_code == 200 response = api_client.get(route.item(item_id), headers=unique_user.token) From d96393f9c9eecddb388bc3ae5d834aaa18c6fd58 Mon Sep 17 00:00:00 2001 From: Litchi Pi Date: Wed, 23 Jul 2025 18:06:00 +0200 Subject: [PATCH 06/20] Ignore some fields in strict verification Signed-off-by: Litchi Pi --- mealie/services/recipe/recipe_service.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mealie/services/recipe/recipe_service.py b/mealie/services/recipe/recipe_service.py index d636f5461..30220871a 100644 --- a/mealie/services/recipe/recipe_service.py +++ b/mealie/services/recipe/recipe_service.py @@ -36,6 +36,7 @@ from mealie.services.scraper import cleaner from .template_service import TemplateService +POSSIBLE_NONE_FIELDS = [ "image" ] class RecipeServiceBase(BaseService): def __init__(self, repos: AllRepositories, user: PrivateUser, household: HouseholdInDB, translator: Translator): @@ -387,6 +388,9 @@ class RecipeService(RecipeServiceBase): if strict: for field in new_data.__class__.model_fields: + if field in POSSIBLE_NONE_FIELDS: + continue + if getattr(new_data, field) is None: raise exceptions.IncompleteData(f"Incomplete recipe, missing {field}") From c002b2051c76e8484628fd04626c466296c2ac27 Mon Sep 17 00:00:00 2001 From: Litchi Pi Date: Wed, 23 Jul 2025 19:08:02 +0200 Subject: [PATCH 07/20] format Signed-off-by: Litchi Pi --- mealie/services/recipe/recipe_service.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mealie/services/recipe/recipe_service.py b/mealie/services/recipe/recipe_service.py index 30220871a..bfff077ae 100644 --- a/mealie/services/recipe/recipe_service.py +++ b/mealie/services/recipe/recipe_service.py @@ -36,7 +36,8 @@ from mealie.services.scraper import cleaner from .template_service import TemplateService -POSSIBLE_NONE_FIELDS = [ "image" ] +POSSIBLE_NONE_FIELDS = ["image"] + class RecipeServiceBase(BaseService): def __init__(self, repos: AllRepositories, user: PrivateUser, household: HouseholdInDB, translator: Translator): From f289171ce17b36a0cb4da5d217dd4956c38653f1 Mon Sep 17 00:00:00 2001 From: Litchi Pi Date: Thu, 24 Jul 2025 11:12:44 +0200 Subject: [PATCH 08/20] Retrieve the missing field from the API request, use in errors Signed-off-by: Litchi Pi --- mealie/core/exceptions.py | 5 +++-- mealie/routes/recipe/recipe_crud_routes.py | 4 ++-- mealie/services/recipe/recipe_service.py | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/mealie/core/exceptions.py b/mealie/core/exceptions.py index 077a8644d..f1ad27144 100644 --- a/mealie/core/exceptions.py +++ b/mealie/core/exceptions.py @@ -52,5 +52,6 @@ class IncompleteData(Exception): """ This exception is raised when a user sends incomplete data to the API """ - - pass + def __init__(self, missing, *args, **kwargs): + Exception.__init__(*args, **kwargs): + self.missing = missing diff --git a/mealie/routes/recipe/recipe_crud_routes.py b/mealie/routes/recipe/recipe_crud_routes.py index 20065cf41..bdf9cf6ab 100644 --- a/mealie/routes/recipe/recipe_crud_routes.py +++ b/mealie/routes/recipe/recipe_crud_routes.py @@ -85,10 +85,10 @@ class RecipeController(BaseRecipeController): self.logger.error("No Entry Found on recipe controller action") raise HTTPException(status_code=404, detail=ErrorResponse.respond(message="No Entry Found")) elif thrownType == exceptions.IncompleteData: - self.logger.error("Incomplete data provided to API route") + self.logger.error("Incomplete data provided to API route:", ex.missing) raise HTTPException( status_code=400, - detail=ErrorResponse.respond(message="Some data were missing on the body of this API request"), + detail=ErrorResponse.respond(message=f"Field '{ex.missing}' missing on the body of this API request"), ) elif thrownType == sqlalchemy.exc.IntegrityError: self.logger.error("SQL Integrity Error on recipe controller action") diff --git a/mealie/services/recipe/recipe_service.py b/mealie/services/recipe/recipe_service.py index bfff077ae..a36deab27 100644 --- a/mealie/services/recipe/recipe_service.py +++ b/mealie/services/recipe/recipe_service.py @@ -393,7 +393,7 @@ class RecipeService(RecipeServiceBase): continue if getattr(new_data, field) is None: - raise exceptions.IncompleteData(f"Incomplete recipe, missing {field}") + raise exceptions.IncompleteData(field, f"Incomplete recipe, missing {field}") if recipe is None or recipe.settings is None: raise exceptions.NoEntryFound("Recipe not found.") From 9d0a0990e6c08a0fc1d01d29d7520f12baf3a347 Mon Sep 17 00:00:00 2001 From: Litchi Pi Date: Thu, 24 Jul 2025 11:16:51 +0200 Subject: [PATCH 09/20] lint Signed-off-by: Litchi Pi --- mealie/core/exceptions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mealie/core/exceptions.py b/mealie/core/exceptions.py index f1ad27144..6135ca407 100644 --- a/mealie/core/exceptions.py +++ b/mealie/core/exceptions.py @@ -52,6 +52,7 @@ class IncompleteData(Exception): """ This exception is raised when a user sends incomplete data to the API """ + def __init__(self, missing, *args, **kwargs): - Exception.__init__(*args, **kwargs): + Exception.__init__(*args, **kwargs) self.missing = missing From 354b00a7b4e0c0d975734953b0f82b13dd5fa2b7 Mon Sep 17 00:00:00 2001 From: Litchi Pi Date: Thu, 24 Jul 2025 11:29:07 +0200 Subject: [PATCH 10/20] fixup Signed-off-by: Litchi Pi --- mealie/routes/recipe/recipe_crud_routes.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mealie/routes/recipe/recipe_crud_routes.py b/mealie/routes/recipe/recipe_crud_routes.py index bdf9cf6ab..d06f77d9f 100644 --- a/mealie/routes/recipe/recipe_crud_routes.py +++ b/mealie/routes/recipe/recipe_crud_routes.py @@ -85,10 +85,11 @@ class RecipeController(BaseRecipeController): self.logger.error("No Entry Found on recipe controller action") raise HTTPException(status_code=404, detail=ErrorResponse.respond(message="No Entry Found")) elif thrownType == exceptions.IncompleteData: - self.logger.error("Incomplete data provided to API route:", ex.missing) + missing = ex.__context__["missing"] + self.logger.error("Incomplete data provided to API route:", missing) raise HTTPException( status_code=400, - detail=ErrorResponse.respond(message=f"Field '{ex.missing}' missing on the body of this API request"), + detail=ErrorResponse.respond(message=f"Field '{missing}' missing on the body of this API request"), ) elif thrownType == sqlalchemy.exc.IntegrityError: self.logger.error("SQL Integrity Error on recipe controller action") From 341a3f9adad3b52f57d47d5dada315cad4c655e8 Mon Sep 17 00:00:00 2001 From: Litchi Pi Date: Thu, 24 Jul 2025 11:29:15 +0200 Subject: [PATCH 11/20] fixup Signed-off-by: Litchi Pi --- mealie/core/exceptions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mealie/core/exceptions.py b/mealie/core/exceptions.py index 6135ca407..65d6b1bb4 100644 --- a/mealie/core/exceptions.py +++ b/mealie/core/exceptions.py @@ -54,5 +54,5 @@ class IncompleteData(Exception): """ def __init__(self, missing, *args, **kwargs): - Exception.__init__(*args, **kwargs) - self.missing = missing + Exception.__init__(self, *args, **kwargs) + self.__context__["missing"] = missing From 91315645d301b8341049438bde9904155292f119 Mon Sep 17 00:00:00 2001 From: Litchi Pi Date: Thu, 24 Jul 2025 11:34:52 +0200 Subject: [PATCH 12/20] fixup Signed-off-by: Litchi Pi --- mealie/core/exceptions.py | 4 +--- mealie/routes/recipe/recipe_crud_routes.py | 4 +++- mealie/services/recipe/recipe_service.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mealie/core/exceptions.py b/mealie/core/exceptions.py index 65d6b1bb4..077a8644d 100644 --- a/mealie/core/exceptions.py +++ b/mealie/core/exceptions.py @@ -53,6 +53,4 @@ class IncompleteData(Exception): This exception is raised when a user sends incomplete data to the API """ - def __init__(self, missing, *args, **kwargs): - Exception.__init__(self, *args, **kwargs) - self.__context__["missing"] = missing + pass diff --git a/mealie/routes/recipe/recipe_crud_routes.py b/mealie/routes/recipe/recipe_crud_routes.py index d06f77d9f..117d7efd8 100644 --- a/mealie/routes/recipe/recipe_crud_routes.py +++ b/mealie/routes/recipe/recipe_crud_routes.py @@ -85,7 +85,9 @@ class RecipeController(BaseRecipeController): self.logger.error("No Entry Found on recipe controller action") raise HTTPException(status_code=404, detail=ErrorResponse.respond(message="No Entry Found")) elif thrownType == exceptions.IncompleteData: - missing = ex.__context__["missing"] + print(ex.args) + print(ex.__context__) + missing = ex.__context__.missing self.logger.error("Incomplete data provided to API route:", missing) raise HTTPException( status_code=400, diff --git a/mealie/services/recipe/recipe_service.py b/mealie/services/recipe/recipe_service.py index a36deab27..9a0ce09fd 100644 --- a/mealie/services/recipe/recipe_service.py +++ b/mealie/services/recipe/recipe_service.py @@ -393,7 +393,7 @@ class RecipeService(RecipeServiceBase): continue if getattr(new_data, field) is None: - raise exceptions.IncompleteData(field, f"Incomplete recipe, missing {field}") + raise exceptions.IncompleteData("Incomplete recipe", missing=field) if recipe is None or recipe.settings is None: raise exceptions.NoEntryFound("Recipe not found.") From 5cd7c23174a7ec0b73219eb7e39fd7e514351c40 Mon Sep 17 00:00:00 2001 From: Litchi Pi Date: Thu, 24 Jul 2025 11:40:32 +0200 Subject: [PATCH 13/20] fixup Signed-off-by: Litchi Pi --- mealie/core/exceptions.py | 4 +++- mealie/routes/recipe/recipe_crud_routes.py | 4 +--- mealie/services/recipe/recipe_service.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mealie/core/exceptions.py b/mealie/core/exceptions.py index 077a8644d..24c580499 100644 --- a/mealie/core/exceptions.py +++ b/mealie/core/exceptions.py @@ -53,4 +53,6 @@ class IncompleteData(Exception): This exception is raised when a user sends incomplete data to the API """ - pass + def __init__(self, missing, *args, **kwargs): + Exception.__init__(self, *args, **kwargs) + self.missing = missing diff --git a/mealie/routes/recipe/recipe_crud_routes.py b/mealie/routes/recipe/recipe_crud_routes.py index 117d7efd8..fb4bde287 100644 --- a/mealie/routes/recipe/recipe_crud_routes.py +++ b/mealie/routes/recipe/recipe_crud_routes.py @@ -85,9 +85,7 @@ class RecipeController(BaseRecipeController): self.logger.error("No Entry Found on recipe controller action") raise HTTPException(status_code=404, detail=ErrorResponse.respond(message="No Entry Found")) elif thrownType == exceptions.IncompleteData: - print(ex.args) - print(ex.__context__) - missing = ex.__context__.missing + missing = ex.missing self.logger.error("Incomplete data provided to API route:", missing) raise HTTPException( status_code=400, diff --git a/mealie/services/recipe/recipe_service.py b/mealie/services/recipe/recipe_service.py index 9a0ce09fd..2ab6d1840 100644 --- a/mealie/services/recipe/recipe_service.py +++ b/mealie/services/recipe/recipe_service.py @@ -393,7 +393,7 @@ class RecipeService(RecipeServiceBase): continue if getattr(new_data, field) is None: - raise exceptions.IncompleteData("Incomplete recipe", missing=field) + raise exceptions.IncompleteData(missing, "Incomplete recipe") if recipe is None or recipe.settings is None: raise exceptions.NoEntryFound("Recipe not found.") From e76e820d37c767d3e6618be1b32da0becb742140 Mon Sep 17 00:00:00 2001 From: Litchi Pi Date: Thu, 24 Jul 2025 12:11:00 +0200 Subject: [PATCH 14/20] fixup Signed-off-by: Litchi Pi --- mealie/services/recipe/recipe_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mealie/services/recipe/recipe_service.py b/mealie/services/recipe/recipe_service.py index 2ab6d1840..92d76a238 100644 --- a/mealie/services/recipe/recipe_service.py +++ b/mealie/services/recipe/recipe_service.py @@ -393,7 +393,7 @@ class RecipeService(RecipeServiceBase): continue if getattr(new_data, field) is None: - raise exceptions.IncompleteData(missing, "Incomplete recipe") + raise exceptions.IncompleteData(field, "Incomplete recipe") if recipe is None or recipe.settings is None: raise exceptions.NoEntryFound("Recipe not found.") From 8ea589b7b5bd9903a685c014fedd666d94a8a749 Mon Sep 17 00:00:00 2001 From: Litchi Pi Date: Thu, 24 Jul 2025 12:21:14 +0200 Subject: [PATCH 15/20] fixup Signed-off-by: Litchi Pi --- mealie/routes/recipe/recipe_crud_routes.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mealie/routes/recipe/recipe_crud_routes.py b/mealie/routes/recipe/recipe_crud_routes.py index fb4bde287..81b639252 100644 --- a/mealie/routes/recipe/recipe_crud_routes.py +++ b/mealie/routes/recipe/recipe_crud_routes.py @@ -85,7 +85,8 @@ class RecipeController(BaseRecipeController): self.logger.error("No Entry Found on recipe controller action") raise HTTPException(status_code=404, detail=ErrorResponse.respond(message="No Entry Found")) elif thrownType == exceptions.IncompleteData: - missing = ex.missing + assert isinstance(ex.__context__, exceptions.IncompleteData) + missing = ex.__context__.missing self.logger.error("Incomplete data provided to API route:", missing) raise HTTPException( status_code=400, From 22efbaceaa0e7f890b5af47ec0a9a65d469d4aa2 Mon Sep 17 00:00:00 2001 From: Litchi Pi Date: Thu, 24 Jul 2025 12:37:33 +0200 Subject: [PATCH 16/20] fixup Signed-off-by: Litchi Pi --- mealie/core/exceptions.py | 4 +--- mealie/routes/recipe/recipe_crud_routes.py | 3 +-- mealie/services/recipe/recipe_service.py | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/mealie/core/exceptions.py b/mealie/core/exceptions.py index 24c580499..077a8644d 100644 --- a/mealie/core/exceptions.py +++ b/mealie/core/exceptions.py @@ -53,6 +53,4 @@ class IncompleteData(Exception): This exception is raised when a user sends incomplete data to the API """ - def __init__(self, missing, *args, **kwargs): - Exception.__init__(self, *args, **kwargs) - self.missing = missing + pass diff --git a/mealie/routes/recipe/recipe_crud_routes.py b/mealie/routes/recipe/recipe_crud_routes.py index 81b639252..466c7f51f 100644 --- a/mealie/routes/recipe/recipe_crud_routes.py +++ b/mealie/routes/recipe/recipe_crud_routes.py @@ -85,8 +85,7 @@ class RecipeController(BaseRecipeController): self.logger.error("No Entry Found on recipe controller action") raise HTTPException(status_code=404, detail=ErrorResponse.respond(message="No Entry Found")) elif thrownType == exceptions.IncompleteData: - assert isinstance(ex.__context__, exceptions.IncompleteData) - missing = ex.__context__.missing + missing = ex.args[-1] self.logger.error("Incomplete data provided to API route:", missing) raise HTTPException( status_code=400, diff --git a/mealie/services/recipe/recipe_service.py b/mealie/services/recipe/recipe_service.py index 92d76a238..d0d448e45 100644 --- a/mealie/services/recipe/recipe_service.py +++ b/mealie/services/recipe/recipe_service.py @@ -393,7 +393,7 @@ class RecipeService(RecipeServiceBase): continue if getattr(new_data, field) is None: - raise exceptions.IncompleteData(field, "Incomplete recipe") + raise exceptions.IncompleteData("Incomplete recipe", field) if recipe is None or recipe.settings is None: raise exceptions.NoEntryFound("Recipe not found.") From 8de0b4e6d1180e65cbc55e8c61d60666a48802c7 Mon Sep 17 00:00:00 2001 From: Litchi Pi Date: Thu, 24 Jul 2025 13:09:34 +0200 Subject: [PATCH 17/20] fixup Signed-off-by: Litchi Pi --- mealie/services/recipe/recipe_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mealie/services/recipe/recipe_service.py b/mealie/services/recipe/recipe_service.py index d0d448e45..4d9bb5051 100644 --- a/mealie/services/recipe/recipe_service.py +++ b/mealie/services/recipe/recipe_service.py @@ -36,7 +36,7 @@ from mealie.services.scraper import cleaner from .template_service import TemplateService -POSSIBLE_NONE_FIELDS = ["image"] +POSSIBLE_NONE_FIELDS = ["image", "recipe_yield"] class RecipeServiceBase(BaseService): From 8728bb372d82340a3ef80a96e2053dac7dd59fe2 Mon Sep 17 00:00:00 2001 From: Litchi Pi Date: Thu, 24 Jul 2025 14:01:14 +0200 Subject: [PATCH 18/20] fixup Signed-off-by: Litchi Pi --- mealie/services/recipe/recipe_service.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/mealie/services/recipe/recipe_service.py b/mealie/services/recipe/recipe_service.py index 4d9bb5051..f037f4d15 100644 --- a/mealie/services/recipe/recipe_service.py +++ b/mealie/services/recipe/recipe_service.py @@ -36,7 +36,15 @@ from mealie.services.scraper import cleaner from .template_service import TemplateService -POSSIBLE_NONE_FIELDS = ["image", "recipe_yield"] +MANDATORY_FIELDS = [ + "name", + "user_id", + "household_id", + "group_id", + "tags", + "recipe_ingredient", + "recipe_instructions", +] class RecipeServiceBase(BaseService): @@ -140,6 +148,7 @@ class RecipeService(RecipeServiceBase): if not additional_attrs.get("recipe_instructions"): additional_attrs["recipe_instructions"] = [RecipeStep(text=self.t("recipe.recipe-defaults.step-text"))] + assert all(f in additional_attrs for f in MANDATORY_FIELDS) return Recipe(**additional_attrs) def get_one(self, slug_or_id: str | UUID) -> Recipe: @@ -389,10 +398,7 @@ class RecipeService(RecipeServiceBase): if strict: for field in new_data.__class__.model_fields: - if field in POSSIBLE_NONE_FIELDS: - continue - - if getattr(new_data, field) is None: + if field in MANDATORY_FIELDS and getattr(new_data, field) is None: raise exceptions.IncompleteData("Incomplete recipe", field) if recipe is None or recipe.settings is None: From 7ccbcaf783ea8dd426784c290c515b2303f1ad47 Mon Sep 17 00:00:00 2001 From: Litchi Pi Date: Thu, 24 Jul 2025 14:16:37 +0200 Subject: [PATCH 19/20] fixup Signed-off-by: Litchi Pi --- mealie/services/recipe/recipe_service.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mealie/services/recipe/recipe_service.py b/mealie/services/recipe/recipe_service.py index f037f4d15..10271ed47 100644 --- a/mealie/services/recipe/recipe_service.py +++ b/mealie/services/recipe/recipe_service.py @@ -41,7 +41,6 @@ MANDATORY_FIELDS = [ "user_id", "household_id", "group_id", - "tags", "recipe_ingredient", "recipe_instructions", ] From 68dadf7cde425fbc31329d3fb46077439eec16d5 Mon Sep 17 00:00:00 2001 From: Litchi Pi Date: Thu, 24 Jul 2025 14:37:41 +0200 Subject: [PATCH 20/20] fixup Signed-off-by: Litchi Pi --- tests/integration_tests/user_recipe_tests/test_recipe_crud.py | 2 +- 1 file changed, 1 insertion(+), 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 36c8efded..d98152019 100644 --- a/tests/integration_tests/user_recipe_tests/test_recipe_crud.py +++ b/tests/integration_tests/user_recipe_tests/test_recipe_crud.py @@ -471,7 +471,7 @@ def test_read_update( assert json.loads(response.text).get("slug") == recipe_data.expected_slug recipe_incomplete = recipe.copy() - del recipe_incomplete["notes"] + del recipe_incomplete["group_id"] response = api_client.put(recipe_url, json=utils.jsonify(recipe_incomplete), headers=unique_user.token) assert response.status_code == 400