diff --git a/main.py b/main.py index 36ce8b6..76ae63f 100644 --- a/main.py +++ b/main.py @@ -32,8 +32,8 @@ async def cookie_person(user_id: Annotated[int, Cookie(alias='user_id')], conn: return await persons.get_by_id(conn, user_id) @app.get("/recipes/parse") -async def parse_recipe_handler(url: str, conn: sqlite3.Connection = Depends(get_db)) -> recipes.Recipe: - parsed = await recipes.parse_recipe(conn, url) +async def parse_recipe_handler(url: str, conn: sqlite3.Connection = Depends(get_db), person = Depends(cookie_person)) -> recipes.Recipe: + parsed = await recipes.parse_recipe(conn, person, url) if not parsed: return JSONResponse(status_code=400, content={'message': 'Recipe not found'}) return parsed diff --git a/recipes/__init__.py b/recipes/__init__.py index adb2cf5..f7ea5d6 100644 --- a/recipes/__init__.py +++ b/recipes/__init__.py @@ -1,18 +1,42 @@ +from persons import Person + from recipes.db import Recipe, insert_recipe, find_recipe_by_id, get_all, find_recipes_by_name, row_to_recipe, load_recipe_ingredients, hide_recipe from recipes.scraping import scrape_recipe_ldata as _scrape_recipe_ldata from ingredients import parse_ingredient_from_nlp as _parse_ingredient_from_nlp, match_existing_products as _match_existing_products -async def parse_recipe(conn, url: str) -> Recipe: +import re + +async def parse_recipe(conn, created_by: Person, url: str) -> Recipe: ldata = await _scrape_recipe_ldata(url) if ldata: - return await _get_recipe_from_ldata(conn, url, ldata) + return await _get_recipe_from_ldata(conn, url, ldata, created_by) return None + +def find_yield(recipe_ldata: dict) -> int: + if 'recipeYield' in recipe_ldata: + yield_vals = recipe_ldata['recipeYield'] + if not isinstance(yield_vals, list): + yield_vals = [yield_vals] + + for val in yield_vals: + try: + return int(val) + except ValueError: + pass + + for val in yield_vals: + match = re.match(r'(\d+)', val) + if match: + return int(match.group(1)) + + return 4 -async def _get_recipe_from_ldata(conn, url: str, ldata: dict) -> dict: +async def _get_recipe_from_ldata(conn, url: str, ldata: dict, created_by: Person) -> dict: ingredients = _parse_ingredient_from_nlp(ldata['recipeIngredient']) ingredients = await _match_existing_products(conn, ingredients) name = ldata['name'] if 'name' in ldata else url images = ldata['image'] if 'image' in ldata else [] + serves = find_yield(ldata) if isinstance(images, list) and len(images) > 0 and isinstance(images[0], dict): images = [image['url'] for image in images] @@ -27,6 +51,9 @@ async def _get_recipe_from_ldata(conn, url: str, ldata: dict) -> dict: id=0, name=name, link=url, + serves=serves, image_urls=images, - ingredients=ingredients + ingredients=ingredients, + created_by=created_by, + created_by_id=created_by.id, ) \ No newline at end of file diff --git a/recipes/db.py b/recipes/db.py index 94a8d64..e442869 100644 --- a/recipes/db.py +++ b/recipes/db.py @@ -7,16 +7,19 @@ from pydantic import BaseModel from typing import AsyncIterator, List, ClassVar, Tuple, Optional class Recipe(BaseModel): - KEYS: ClassVar[List[str]] = ['id', 'name', 'link', 'image_urls', 'based_on_recipe', 'created_by_id', 'date_created', 'hidden_by_id', 'date_hidden'] + KEYS: ClassVar[List[str]] = ['id', 'name', 'link', 'serves', 'image_urls', 'based_on_recipe', 'created_by_id', 'date_created', 'hidden_by_id', 'date_hidden'] + NON_INSERT_KEYS: ClassVar[List[str]] = ['id', 'created_date', 'hidden_by_id', 'date_hidden'] + id: int name: str link: str + serves: int image_urls: List[str] = [] ingredients: List[Ingredient] = [] based_on_recipe: Optional[int] = None - date_created: Optional[datetime.datetime] = None - created_by_id: Optional[int] = None + date_created: datetime.datetime = datetime.datetime.now() + created_by_id: int created_by: Optional[Person] = None date_hidden: Optional[datetime.datetime] = None @@ -29,6 +32,7 @@ async def create(conn): id INTEGER PRIMARY KEY, name TEXT NOT NULL, link TEXT NOT NULL, + serves INTEGER NOT NULL, image_urls TEXT NOT NULL, based_on_recipe INTEGER NULL, @@ -43,11 +47,22 @@ async def create(conn): FOREIGN KEY (hidden_by_id) REFERENCES Person(id) );''') +def _as_insert_field(recipe: Recipe, name: str): + value = getattr(recipe, name) + if name == 'image_urls': + return json.dumps(value) + return value + async def insert_recipe(conn, recipe: Recipe): - async with conn.execute(''' - INSERT INTO Recipe (name, link, image_urls, based_on_recipe, created_by_id) - VALUES (?, ?, ?, ?, ?) - ''', (recipe.name, recipe.link, json.dumps(recipe.image_urls), recipe.based_on_recipe, recipe.created_by_id)) as cursor: + fields_to_insert = [k for k in Recipe.KEYS if k not in Recipe.NON_INSERT_KEYS] + actual_values = [_as_insert_field(recipe, k) for k in fields_to_insert] + + insert_stmt = f''' + INSERT INTO Recipe ({','.join(fields_to_insert)}) + VALUES ({','.join(['?'] * len(fields_to_insert))}) + ''' + + async with conn.execute(insert_stmt, actual_values) as cursor: recipe.id = cursor.lastrowid async def hide_recipe(conn, recipe_id: int, person: Person): diff --git a/tests/test_data.py b/tests/test_data.py index 9d8bddd..cb28286 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -191,6 +191,7 @@ class Recipes: id=0, name='Broccoli Soup', link='https://www.bbcgoodfood.com/recipes/broccoli-soup', + serves=4, image_urls=['https://www.bbcgoodfood.com/sites/default/files/styles/recipe/public/recipe/recipe-image/2018/10/broccoli-soup.jpg'], ingredients=[Ingredients.broccoli_chopped_1kg], created_by_id=Persons.jacob.id, @@ -200,6 +201,7 @@ class Recipes: id=0, name="How to Steam Green Beans", link="https://www.thespruceeats.com/steamed-green-beans-3057051", + serves=4, image_urls=["https://www.thespruceeats.com/thmb/CLROdq9dlYbjKjlOlA_kmFdunTY=/1500x0/filters:no_upscale():max_bytes(150000):strip_icc()/steamed-green-beans-3057051-hero-01-b1c4f894da5b4bc0a01cd43886df0100.jpg"], ingredients=[Ingredients.green_beans,Ingredients.butter,Ingredients.salt,Ingredients.freshly_ground_black_pepper], created_by_id=Persons.jacob.id,