feat(backend): add initial cookbook support

This commit is contained in:
hay-kot
2021-08-31 14:39:29 -08:00
parent 83ab858e46
commit d24e95c091
27 changed files with 284 additions and 490 deletions

View File

@@ -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)

View File

@@ -0,0 +1 @@
from .cookbook_service import *

View 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

View File

@@ -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):

View File

@@ -3,7 +3,7 @@ import unicodedata
replace_abbreviations = {
"cup ": "cup ",
"g ": "gram ",
" g ": "gram ",
"kg ": "kilogram ",
"lb ": "pound ",
"ml ": "milliliter ",

View File

@@ -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:

View File

@@ -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