Coverage for opt/mealie/lib/python3.12/site-packages/mealie/routes/recipe/exports.py: 67%
41 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-11-25 15:32 +0000
« prev ^ index » next coverage.py v7.10.6, created at 2025-11-25 15:32 +0000
1from shutil import rmtree 1a
2from zipfile import ZipFile 1a
4from fastapi import ( 1a
5 HTTPException,
6)
7from starlette.background import BackgroundTask 1a
8from starlette.responses import FileResponse 1a
10from mealie.core.dependencies import ( 1a
11 get_temporary_path,
12 get_temporary_zip_path,
13 validate_recipe_token,
14)
15from mealie.core.security import create_recipe_slug_token 1a
16from mealie.routes._base import controller 1a
17from mealie.routes._base.routers import UserAPIRouter 1a
18from mealie.schema.recipe import Recipe, RecipeImageTypes 1a
19from mealie.schema.recipe.request_helpers import ( 1a
20 RecipeZipTokenResponse,
21)
22from mealie.services.recipe.template_service import TemplateService 1a
24from ._base import BaseRecipeController, FormatResponse 1a
26router = UserAPIRouter(prefix="/recipes") 1a
29@controller(router) 1a
30class RecipeExportController(BaseRecipeController): 1a
31 # ==================================================================================================================
32 # Export Operations
34 @router.get("/exports", response_model=FormatResponse) 1a
35 def get_recipe_formats_and_templates(self): 1a
36 return TemplateService().templates 1c
38 @router.post("/{slug}/exports", response_model=RecipeZipTokenResponse) 1a
39 def get_recipe_zip_token(self, slug: str): 1a
40 """Generates a recipe zip token to be used to download a recipe as a zip file"""
41 return RecipeZipTokenResponse(token=create_recipe_slug_token(slug))
43 @router.get("/{slug}/exports", response_class=FileResponse) 1a
44 def get_recipe_as_format(self, slug: str, template_name: str): 1a
45 """
46 ## Parameters
47 `template_name`: The name of the template to use to use in the exports listed. Template type will automatically
48 be set on the backend. Because of this, it's important that your templates have unique names. See available
49 names and formats in the /api/recipes/exports endpoint.
51 """
52 with get_temporary_path(auto_unlink=False) as temp_path:
53 recipe = self.mixins.get_one(slug)
54 file = self.service.render_template(recipe, temp_path, template_name)
55 return FileResponse(file, background=BackgroundTask(rmtree, temp_path))
57 @router.get("/{slug}/exports/zip") 1a
58 def get_recipe_as_zip(self, slug: str, token: str): 1a
59 """Get a Recipe and Its Original Image as a Zip File"""
60 with get_temporary_zip_path(auto_unlink=False) as temp_path:
61 validated_slug = validate_recipe_token(token)
63 if validated_slug != slug:
64 raise HTTPException(status_code=400, detail="Invalid Slug")
66 recipe: Recipe = self.mixins.get_one(validated_slug)
67 image_asset = recipe.image_dir.joinpath(RecipeImageTypes.original.value)
68 with ZipFile(temp_path, "w") as myzip:
69 myzip.writestr(f"{slug}.json", recipe.model_dump_json())
71 if image_asset.is_file():
72 myzip.write(image_asset, arcname=image_asset.name)
74 return FileResponse(
75 temp_path, filename=f"{recipe.slug}.zip", background=BackgroundTask(temp_path.unlink, missing_ok=True)
76 )