Coverage for opt/mealie/lib/python3.12/site-packages/mealie/services/scraper/scraper.py: 51%

45 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-11-25 17:29 +0000

1from enum import Enum 1a

2from re import search as regex_search 1a

3from uuid import uuid4 1a

4 

5from fastapi import HTTPException, status 1a

6from slugify import slugify 1a

7 

8from mealie.core.root_logger import get_logger 1a

9from mealie.lang.providers import Translator 1a

10from mealie.pkgs import cache 1a

11from mealie.schema.recipe import Recipe 1a

12from mealie.services.recipe.recipe_data_service import RecipeDataService 1a

13from mealie.services.scraper.scraped_extras import ScrapedExtras 1a

14 

15from .recipe_scraper import RecipeScraper 1a

16 

17 

18class ParserErrors(str, Enum): 1a

19 BAD_RECIPE_DATA = "BAD_RECIPE_DATA" 1a

20 NO_RECIPE_DATA = "NO_RECIPE_DATA" 1a

21 CONNECTION_ERROR = "CONNECTION_ERROR" 1a

22 

23 

24async def create_from_html( 1a

25 url: str, translator: Translator, html: str | None = None 

26) -> tuple[Recipe, ScrapedExtras | None]: 

27 """Main entry point for generating a recipe from a URL. Pass in a URL and 

28 a Recipe object will be returned if successful. Optionally pass in the HTML to skip fetching it. 

29 

30 Args: 

31 url (str): a valid string representing a URL 

32 html (str | None): optional HTML string to skip network request. Defaults to None. 

33 

34 Returns: 

35 Recipe: Recipe Object 

36 """ 

37 scraper = RecipeScraper(translator) 1bdc

38 

39 if not html: 1bdc

40 extracted_url = regex_search(r"(https?://|www\.)[^\s]+", url) 1bdc

41 if not extracted_url: 41 ↛ 43line 41 didn't jump to line 43 because the condition on line 41 was always true1bdc

42 raise HTTPException(status.HTTP_400_BAD_REQUEST, {"details": ParserErrors.BAD_RECIPE_DATA.value}) 1bdc

43 url = extracted_url.group(0) 

44 

45 new_recipe, extras = await scraper.scrape(url, html) 1bc

46 

47 if not new_recipe: 47 ↛ 50line 47 didn't jump to line 50 because the condition on line 47 was always true1bc

48 raise HTTPException(status.HTTP_400_BAD_REQUEST, {"details": ParserErrors.BAD_RECIPE_DATA.value}) 1bc

49 

50 new_recipe.id = uuid4() 

51 logger = get_logger() 

52 logger.debug(f"Image {new_recipe.image}") 

53 

54 recipe_data_service = RecipeDataService(new_recipe.id) 

55 

56 try: 

57 if new_recipe.image and isinstance(new_recipe.image, list): 

58 new_recipe.image = new_recipe.image[0] 

59 await recipe_data_service.scrape_image(new_recipe.image) # type: ignore 

60 

61 if new_recipe.name is None: 

62 new_recipe.name = "Untitled" 

63 

64 new_recipe.slug = slugify(new_recipe.name) 

65 new_recipe.image = cache.new_key(4) 

66 except Exception as e: 

67 recipe_data_service.logger.exception(f"Error Scraping Image: {e}") 

68 new_recipe.image = "no image" 

69 

70 if new_recipe.name is None or new_recipe.name == "": 

71 new_recipe.name = f"No Recipe Name Found - {uuid4()!s}" 

72 new_recipe.slug = slugify(new_recipe.name) 

73 

74 return new_recipe, extras