munch-ease-backend/recipes/db.py

151 lines
4.3 KiB
Python
Raw Normal View History

2025-10-18 03:26:42 +00:00
import datetime
import json
from typing import Any, AsyncIterator, ClassVar, Iterable, List, Optional, Tuple, cast
from pydantic import BaseModel, Field
2024-01-13 03:21:38 +00:00
from ingredients import Ingredient, find_ingredients_by_recipe_id
2025-10-18 03:26:42 +00:00
from persons import Person
2024-01-13 03:21:38 +00:00
class Recipe(BaseModel):
2025-10-18 03:26:42 +00:00
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"]
2024-05-20 10:09:57 +00:00
id: int = -1
2024-01-13 03:21:38 +00:00
name: str
link: str
2024-05-19 11:22:30 +00:00
serves: int
2025-10-18 03:26:42 +00:00
image_urls: List[str] = Field(default_factory=list)
ingredients: List[Ingredient] = Field(default_factory=list)
2024-04-25 02:03:30 +00:00
based_on_recipe: Optional[int] = None
2025-10-18 03:26:42 +00:00
date_created: datetime.datetime = Field(
default_factory=lambda: datetime.datetime.now().astimezone()
)
created_by_id: Optional[int]
2024-04-25 02:03:30 +00:00
created_by: Optional[Person] = None
date_hidden: Optional[datetime.datetime] = None
hidden_by_id: Optional[int] = None
hidden_by: Optional[Person] = None
2024-01-13 03:21:38 +00:00
2025-10-18 03:26:42 +00:00
2024-01-13 03:21:38 +00:00
async def create(conn):
2025-10-18 03:26:42 +00:00
await conn.execute(
"""
2024-01-13 03:21:38 +00:00
CREATE TABLE IF NOT EXISTS Recipe (
id INTEGER PRIMARY KEY,
2024-04-25 02:03:30 +00:00
name TEXT NOT NULL,
link TEXT NOT NULL,
2024-05-19 11:22:30 +00:00
serves INTEGER NOT NULL,
2024-04-25 02:03:30 +00:00
image_urls TEXT NOT NULL,
based_on_recipe INTEGER NULL,
2024-10-14 05:59:05 +00:00
date_created DATETIME NOT NULL,
2024-04-25 02:03:30 +00:00
created_by_id INTEGER NOT NULL,
2024-10-14 05:59:05 +00:00
2024-04-25 02:03:30 +00:00
date_hidden DATETIME DEFAULT NULL,
hidden_by_id INTEGER DEFAULT NULL,
2024-10-14 05:59:05 +00:00
2024-04-25 02:03:30 +00:00
FOREIGN KEY (based_on_recipe) REFERENCES Recipe(id)
FOREIGN KEY (created_by_id) REFERENCES Person(id)
FOREIGN KEY (hidden_by_id) REFERENCES Person(id)
2025-10-18 03:26:42 +00:00
);"""
)
2024-01-13 03:21:38 +00:00
2024-05-19 11:22:30 +00:00
def _as_insert_field(recipe: Recipe, name: str):
value = getattr(recipe, name)
2025-10-18 03:26:42 +00:00
if name == "image_urls":
2024-05-19 11:22:30 +00:00
return json.dumps(value)
2024-10-14 05:59:05 +00:00
if isinstance(value, datetime.datetime):
return value.isoformat()
2025-10-18 03:26:42 +00:00
2024-05-19 11:22:30 +00:00
return value
2025-10-18 03:26:42 +00:00
2024-01-13 03:21:38 +00:00
async def insert_recipe(conn, recipe: Recipe):
2024-05-19 11:22:30 +00:00
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]
2025-10-18 03:26:42 +00:00
insert_stmt = f"""
2024-05-19 11:22:30 +00:00
INSERT INTO Recipe ({','.join(fields_to_insert)})
VALUES ({','.join(['?'] * len(fields_to_insert))})
2025-10-18 03:26:42 +00:00
"""
2024-05-19 11:22:30 +00:00
async with conn.execute(insert_stmt, actual_values) as cursor:
2024-01-13 03:21:38 +00:00
recipe.id = cursor.lastrowid
2025-10-18 03:26:42 +00:00
2024-04-28 03:57:02 +00:00
async def hide_recipe(conn, recipe_id: int, person: Person):
2025-10-18 03:26:42 +00:00
await conn.execute(
"""
2024-04-28 03:57:02 +00:00
UPDATE Recipe
2024-10-14 05:59:05 +00:00
SET date_hidden = ?, hidden_by_id = ?
2024-04-28 03:57:02 +00:00
WHERE id = ?
2025-10-18 03:26:42 +00:00
""",
(datetime.datetime.now().astimezone().isoformat(), person.id, recipe_id),
)
2024-04-28 03:57:02 +00:00
2025-10-18 03:26:42 +00:00
def row_to_recipe(col_tuples: Iterable[Tuple[str, object]]) -> Recipe:
d: dict[str, Any] = {k: v for k, v in col_tuples}
img_raw = (
cast(str, d["image_urls"]) if not isinstance(d["image_urls"], list) else d["image_urls"]
)
d["image_urls"] = cast(List[str], json.loads(img_raw) if isinstance(img_raw, str) else img_raw)
2024-01-13 08:44:07 +00:00
return Recipe(**d)
2025-10-18 03:26:42 +00:00
async def find_recipe_by_id(conn, recipe_id: int) -> Optional[Recipe]:
async with conn.execute(
f"""
2024-01-13 03:21:38 +00:00
SELECT {','.join(Recipe.KEYS)} FROM Recipe
WHERE id = ?
LIMIT 1
2025-10-18 03:26:42 +00:00
""",
(recipe_id,),
) as cursor:
2024-01-13 03:21:38 +00:00
async for row in cursor:
2025-10-18 03:26:42 +00:00
return row_to_recipe(list(zip(Recipe.KEYS, row)))
return None
2024-01-13 08:44:07 +00:00
2024-05-13 03:59:46 +00:00
async def find_recipes_by_name(conn, name: str) -> AsyncIterator[Recipe]:
2025-10-18 03:26:42 +00:00
async with conn.execute(
f"""
SELECT {','.join(Recipe.KEYS)} FROM Recipe
2024-04-28 03:57:02 +00:00
WHERE name LIKE ? AND date_hidden IS NULL
2025-10-18 03:26:42 +00:00
""",
(f"%{name}%",),
) as cursor:
async for row in cursor:
2025-10-18 03:26:42 +00:00
yield row_to_recipe(list(zip(Recipe.KEYS, row)))
2024-05-13 03:59:46 +00:00
async def get_all(conn) -> AsyncIterator[Recipe]:
2025-10-18 03:26:42 +00:00
async with conn.execute(
f"""
2024-04-28 03:57:02 +00:00
SELECT {','.join(Recipe.KEYS)} FROM Recipe WHERE date_hidden IS NULL
2025-10-18 03:26:42 +00:00
"""
) as cursor:
2024-01-13 08:44:07 +00:00
async for row in cursor:
2025-10-18 03:26:42 +00:00
yield row_to_recipe(list(zip(Recipe.KEYS, row)))
2024-01-17 10:39:48 +00:00
2024-04-25 04:57:39 +00:00
async def load_recipe_ingredients(conn, recipe: Recipe) -> None:
async for ingredient in find_ingredients_by_recipe_id(conn, recipe.id):
2025-10-18 03:26:42 +00:00
recipe.ingredients.append(ingredient)