mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-02-07 08:23:12 -05:00
feat(backend): ✨ add initial cookbook support
This commit is contained in:
@@ -48,25 +48,19 @@ class BaseHttpService(Generic[T, D]):
|
||||
def assert_existing(self, data: T) -> None:
|
||||
raise NotImplementedError("`assert_existing` must by implemented by child class")
|
||||
|
||||
def _create_event(self, title: str, message: str) -> None:
|
||||
if not self.__class__.event_func:
|
||||
raise NotImplementedError("`event_func` must be set by child class")
|
||||
|
||||
self.background_tasks.add_task(self.__class__.event_func, title, message, self.session)
|
||||
|
||||
@classmethod
|
||||
def read_existing(cls, id: T, deps: ReadDeps = Depends()):
|
||||
"""
|
||||
Used for dependency injection for routes that require an existing recipe. If the recipe doesn't exist
|
||||
or the user doens't not have the required permissions, the proper HTTP Status code will be raised.
|
||||
|
||||
Args:
|
||||
slug (str): Recipe Slug used to query the database
|
||||
session (Session, optional): The Injected SQLAlchemy Session.
|
||||
user (bool, optional): The injected determination of is_logged_in.
|
||||
|
||||
Raises:
|
||||
HTTPException: 404 Not Found
|
||||
HTTPException: 403 Forbidden
|
||||
|
||||
Returns:
|
||||
RecipeService: The Recipe Service class with a populated recipe attribute
|
||||
"""
|
||||
new_class = cls(deps.session, deps.user, deps.bg_tasks)
|
||||
new_class = cls(deps.session, deps.user, deps.bg_task)
|
||||
new_class.assert_existing(id)
|
||||
return new_class
|
||||
|
||||
@@ -75,35 +69,21 @@ class BaseHttpService(Generic[T, D]):
|
||||
"""
|
||||
Used for dependency injection for routes that require an existing recipe. The only difference between
|
||||
read_existing and write_existing is that the user is required to be logged in on write_existing method.
|
||||
|
||||
Args:
|
||||
slug (str): Recipe Slug used to query the database
|
||||
session (Session, optional): The Injected SQLAlchemy Session.
|
||||
user (bool, optional): The injected determination of is_logged_in.
|
||||
|
||||
Raises:
|
||||
HTTPException: 404 Not Found
|
||||
HTTPException: 403 Forbidden
|
||||
|
||||
Returns:
|
||||
RecipeService: The Recipe Service class with a populated recipe attribute
|
||||
"""
|
||||
new_class = cls(deps.session, deps.user, deps.bg_task)
|
||||
new_class.assert_existing(id)
|
||||
return new_class
|
||||
|
||||
@classmethod
|
||||
def base(cls, deps: WriteDeps = Depends()):
|
||||
"""A Base instance to be used as a router dependency
|
||||
|
||||
Raises:
|
||||
HTTPException: 400 Bad Request
|
||||
|
||||
def public(cls, deps: ReadDeps = Depends()):
|
||||
"""
|
||||
A Base instance to be used as a router dependency
|
||||
"""
|
||||
return cls(deps.session, deps.user, deps.bg_task)
|
||||
|
||||
def _create_event(self, title: str, message: str) -> None:
|
||||
if not self.__class__.event_func:
|
||||
raise NotImplementedError("`event_func` must be set by child class")
|
||||
|
||||
self.background_tasks.add_task(self.__class__.event_func, title, message, self.session)
|
||||
@classmethod
|
||||
def private(cls, deps: WriteDeps = Depends()):
|
||||
"""
|
||||
A Base instance to be used as a router dependency
|
||||
"""
|
||||
return cls(deps.session, deps.user, deps.bg_task)
|
||||
|
||||
1
mealie/services/cookbook/__init__.py
Normal file
1
mealie/services/cookbook/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .cookbook_service import *
|
||||
85
mealie/services/cookbook/cookbook_service.py
Normal file
85
mealie/services/cookbook/cookbook_service.py
Normal file
@@ -0,0 +1,85 @@
|
||||
from fastapi import HTTPException, status
|
||||
|
||||
from mealie.core.root_logger import get_logger
|
||||
from mealie.schema.cookbook.cookbook import CreateCookBook, ReadCookBook, SaveCookBook
|
||||
from mealie.services.base_http_service.base_http_service import BaseHttpService
|
||||
from mealie.services.events import create_group_event
|
||||
|
||||
logger = get_logger(module=__name__)
|
||||
|
||||
|
||||
class CookbookService(BaseHttpService[str, str]):
|
||||
"""
|
||||
Class Methods:
|
||||
`read_existing`: Reads an existing recipe from the database.
|
||||
`write_existing`: Updates an existing recipe in the database.
|
||||
`base`: Requires write permissions, but doesn't perform recipe checks
|
||||
"""
|
||||
|
||||
event_func = create_group_event
|
||||
cookbook: ReadCookBook # Required for proper type hints
|
||||
|
||||
_group_id_cache = None
|
||||
|
||||
@property
|
||||
def group_id(self):
|
||||
# TODO: Populate Group in Private User Call WARNING: May require significant refactoring
|
||||
if not self._group_id_cache:
|
||||
group = self.db.groups.get(self.session, self.user.group, "name")
|
||||
print(group)
|
||||
self._group_id_cache = group.id
|
||||
return self._group_id_cache
|
||||
|
||||
def assert_existing(self, id: str):
|
||||
self.populate_cookbook(id)
|
||||
|
||||
if not self.cookbook:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND)
|
||||
|
||||
if self.cookbook.group_id != self.group_id:
|
||||
raise HTTPException(status.HTTP_403_FORBIDDEN)
|
||||
|
||||
def populate_cookbook(self, id):
|
||||
self.cookbook = self.db.cookbooks.get(self.session, id)
|
||||
|
||||
def get_all(self) -> list[ReadCookBook]:
|
||||
items = self.db.cookbooks.get(self.session, self.group_id, "group_id", limit=999)
|
||||
items.sort(key=lambda x: x.position)
|
||||
return items
|
||||
|
||||
def create_one(self, data: CreateCookBook) -> ReadCookBook:
|
||||
try:
|
||||
self.cookbook = self.db.cookbooks.create(self.session, SaveCookBook(group_id=self.group_id, **data.dict()))
|
||||
except Exception as ex:
|
||||
raise HTTPException(
|
||||
status.HTTP_400_BAD_REQUEST, detail={"message": "PAGE_CREATION_ERROR", "exception": str(ex)}
|
||||
)
|
||||
|
||||
return self.cookbook
|
||||
|
||||
def update_one(self, data: CreateCookBook, id: int = None) -> ReadCookBook:
|
||||
if not self.cookbook:
|
||||
return
|
||||
|
||||
target_id = id or self.cookbook.id
|
||||
self.cookbook = self.db.cookbooks.update(self.session, target_id, data)
|
||||
|
||||
return self.cookbook
|
||||
|
||||
def update_many(self, data: list[ReadCookBook]) -> list[ReadCookBook]:
|
||||
updated = []
|
||||
|
||||
for cookbook in data:
|
||||
cb = self.db.cookbooks.update(self.session, cookbook.id, cookbook)
|
||||
updated.append(cb)
|
||||
|
||||
return updated
|
||||
|
||||
def delete_one(self, id: int = None) -> ReadCookBook:
|
||||
if not self.cookbook:
|
||||
return
|
||||
|
||||
target_id = id or self.cookbook.id
|
||||
self.cookbook = self.db.cookbooks.delete(self.session, target_id)
|
||||
|
||||
return self.cookbook
|
||||
@@ -5,7 +5,7 @@ from typing import Union
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
from mealie.core.dependencies.grouped import WriteDeps
|
||||
from mealie.core.dependencies.grouped import ReadDeps, WriteDeps
|
||||
from mealie.core.root_logger import get_logger
|
||||
from mealie.schema.recipe.recipe import CreateRecipe, Recipe
|
||||
from mealie.services.base_http_service.base_http_service import BaseHttpService
|
||||
@@ -21,6 +21,7 @@ class RecipeService(BaseHttpService[str, str]):
|
||||
`write_existing`: Updates an existing recipe in the database.
|
||||
`base`: Requires write permissions, but doesn't perform recipe checks
|
||||
"""
|
||||
|
||||
event_func = create_recipe_event
|
||||
recipe: Recipe # Required for proper type hints
|
||||
|
||||
@@ -29,7 +30,7 @@ class RecipeService(BaseHttpService[str, str]):
|
||||
return super().write_existing(slug, deps)
|
||||
|
||||
@classmethod
|
||||
def read_existing(cls, slug: str, deps: WriteDeps = Depends()):
|
||||
def read_existing(cls, slug: str, deps: ReadDeps = Depends()):
|
||||
return super().write_existing(slug, deps)
|
||||
|
||||
def assert_existing(self, slug: str):
|
||||
|
||||
@@ -3,7 +3,7 @@ import unicodedata
|
||||
|
||||
replace_abbreviations = {
|
||||
"cup ": "cup ",
|
||||
"g ": "gram ",
|
||||
" g ": "gram ",
|
||||
"kg ": "kilogram ",
|
||||
"lb ": "pound ",
|
||||
"ml ": "milliliter ",
|
||||
|
||||
@@ -55,8 +55,8 @@ def _exec_crf_test(input_text):
|
||||
|
||||
|
||||
def convert_list_to_crf_model(list_of_ingrdeint_text: list[str]):
|
||||
print(list_of_ingrdeint_text)
|
||||
crf_output = _exec_crf_test([pre_process_string(x) for x in list_of_ingrdeint_text])
|
||||
|
||||
crf_models = [CRFIngredient(**ingredient) for ingredient in utils.import_data(crf_output.split("\n"))]
|
||||
|
||||
for model in crf_models:
|
||||
|
||||
@@ -11,6 +11,7 @@ def cleanUnicodeFractions(s):
|
||||
"""
|
||||
Replace unicode fractions with ascii representation, preceded by a
|
||||
space.
|
||||
|
||||
"1\x215e" => "1 7/8"
|
||||
"""
|
||||
|
||||
@@ -47,7 +48,7 @@ def unclump(s):
|
||||
|
||||
def normalizeToken(s):
|
||||
"""
|
||||
TODO: FIX THIS. We used to use the pattern.en package to singularize words, but
|
||||
ToDo: FIX THIS. We used to use the pattern.en package to singularize words, but
|
||||
in the name of simple deployments, we took it out. We should fix this at some
|
||||
point.
|
||||
"""
|
||||
@@ -133,12 +134,13 @@ def insideParenthesis(token, tokens):
|
||||
return True
|
||||
else:
|
||||
line = " ".join(tokens)
|
||||
return re.match(r".*\(.*" + re.escape(token) + r".*\).*", line) is not None
|
||||
return re.match(r".*\(.*" + re.escape(token) + ".*\).*", line) is not None
|
||||
|
||||
|
||||
def displayIngredient(ingredient):
|
||||
"""
|
||||
Format a list of (tag, [tokens]) tuples as an HTML string for display.
|
||||
|
||||
displayIngredient([("qty", ["1"]), ("name", ["cat", "pie"])])
|
||||
# => <span class='qty'>1</span> <span class='name'>cat pie</span>
|
||||
"""
|
||||
@@ -220,21 +222,7 @@ def import_data(lines):
|
||||
|
||||
# turn B-NAME/123 back into "name"
|
||||
tag, confidence = re.split(r"/", columns[-1], 1)
|
||||
tag = re.sub(r"^[BI]\-", "", tag).lower()
|
||||
|
||||
# TODO: Integrate Confidence into API Response
|
||||
print("Confidence", confidence)
|
||||
|
||||
# new token
|
||||
if prevTag != tag or token == "n/a":
|
||||
display[-1].append((tag, [token]))
|
||||
data[-1][tag] = []
|
||||
prevTag = tag
|
||||
|
||||
# continuation
|
||||
else:
|
||||
display[-1][-1][1].append(token)
|
||||
data[-1][tag].append(token)
|
||||
tag = re.sub("^[BI]\-", "", tag).lower()
|
||||
|
||||
# ---- DISPLAY ----
|
||||
# build a structure which groups each token by its tag, so we can
|
||||
|
||||
Reference in New Issue
Block a user