refactor(backend): ♻️ refactor backend services (#669)

* refactor(backend): ♻️ refactor backend services

* refactor(backend): ♻️ move user model folder into own directory for future expansion

* fix overriding results

Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
Hayden 2021-09-04 12:28:49 -08:00 committed by GitHub
parent b550dae593
commit 3d87ffc3a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 188 additions and 181 deletions

View File

@ -96,7 +96,7 @@ class BaseAccessModel(Generic[T, D]):
if any_case: if any_case:
search_attr = getattr(self.sql_model, key) search_attr = getattr(self.sql_model, key)
result = session.query(self.sql_model).filter(func.lower(search_attr) == key.lower()).one_or_none() result = session.query(self.sql_model).filter(func.lower(search_attr) == key.lower()).one_or_none()
else:
result = session.query(self.sql_model).filter_by(**{key: value}).one_or_none() result = session.query(self.sql_model).filter_by(**{key: value}).one_or_none()
if not result: if not result:

View File

@ -3,9 +3,10 @@ from logging import getLogger
from sqlalchemy.orm.session import Session from sqlalchemy.orm.session import Session
from mealie.db.data_access_layer.group_access_model import GroupDataAccessModel from mealie.db.data_access_layer.group_access_model import GroupDataAccessModel
from mealie.db.models.cookbook import CookBook
from mealie.db.models.event import Event, EventNotification from mealie.db.models.event import Event, EventNotification
from mealie.db.models.group import Group from mealie.db.models.group import Group
from mealie.db.models.group.cookbook import CookBook
from mealie.db.models.group.shopping_list import ShoppingList
from mealie.db.models.group.webhooks import GroupWebhooksModel from mealie.db.models.group.webhooks import GroupWebhooksModel
from mealie.db.models.mealplan import MealPlan from mealie.db.models.mealplan import MealPlan
from mealie.db.models.recipe.category import Category from mealie.db.models.recipe.category import Category
@ -13,7 +14,6 @@ from mealie.db.models.recipe.comment import RecipeComment
from mealie.db.models.recipe.ingredient import IngredientFoodModel, IngredientUnitModel from mealie.db.models.recipe.ingredient import IngredientFoodModel, IngredientUnitModel
from mealie.db.models.recipe.recipe import RecipeModel, Tag from mealie.db.models.recipe.recipe import RecipeModel, Tag
from mealie.db.models.settings import SiteSettings from mealie.db.models.settings import SiteSettings
from mealie.db.models.shopping_list import ShoppingList
from mealie.db.models.sign_up import SignUp from mealie.db.models.sign_up import SignUp
from mealie.db.models.users import LongLiveToken, User from mealie.db.models.users import LongLiveToken, User
from mealie.schema.admin import SiteSettings as SiteSettingsSchema from mealie.schema.admin import SiteSettings as SiteSettingsSchema
@ -39,7 +39,9 @@ from .user_access_model import UserDataAccessModel
logger = getLogger() logger = getLogger()
DEFAULT_PK = "id" pk_id = "id"
pk_slug = "slug"
pk_token = "token"
class CategoryDataAccessModel(BaseAccessModel): class CategoryDataAccessModel(BaseAccessModel):
@ -53,6 +55,7 @@ class TagsDataAccessModel(BaseAccessModel):
class DatabaseAccessLayer: class DatabaseAccessLayer:
def __init__(self) -> None:
""" """
`DatabaseAccessLayer` class is the data access layer for all database actions within `DatabaseAccessLayer` class is the data access layer for all database actions within
Mealie. Database uses composition from classes derived from BaseAccessModel. These Mealie. Database uses composition from classes derived from BaseAccessModel. These
@ -60,31 +63,29 @@ class DatabaseAccessLayer:
additional methods are required. additional methods are required.
""" """
def __init__(self) -> None:
# Recipes # Recipes
self.recipes = RecipeDataAccessModel("slug", RecipeModel, Recipe) self.recipes = RecipeDataAccessModel(pk_slug, RecipeModel, Recipe)
self.ingredient_foods = BaseAccessModel(DEFAULT_PK, IngredientFoodModel, IngredientFood) self.ingredient_foods = BaseAccessModel(pk_id, IngredientFoodModel, IngredientFood)
self.ingredient_units = BaseAccessModel(DEFAULT_PK, IngredientUnitModel, IngredientUnit) self.ingredient_units = BaseAccessModel(pk_id, IngredientUnitModel, IngredientUnit)
self.comments = BaseAccessModel(DEFAULT_PK, RecipeComment, CommentOut) self.comments = BaseAccessModel(pk_id, RecipeComment, CommentOut)
# Tags and Categories # Tags and Categories
self.categories = CategoryDataAccessModel("slug", Category, RecipeCategoryResponse) self.categories = CategoryDataAccessModel(pk_slug, Category, RecipeCategoryResponse)
self.tags = TagsDataAccessModel("slug", Tag, RecipeTagResponse) self.tags = TagsDataAccessModel(pk_slug, Tag, RecipeTagResponse)
# Site # Site
self.settings = BaseAccessModel(DEFAULT_PK, SiteSettings, SiteSettingsSchema) self.settings = BaseAccessModel(pk_id, SiteSettings, SiteSettingsSchema)
self.sign_ups = BaseAccessModel("token", SignUp, SignUpOut) self.sign_ups = BaseAccessModel(pk_token, SignUp, SignUpOut)
self.event_notifications = BaseAccessModel(DEFAULT_PK, EventNotification, EventNotificationIn) self.event_notifications = BaseAccessModel(pk_id, EventNotification, EventNotificationIn)
self.events = BaseAccessModel(DEFAULT_PK, Event, EventSchema) self.events = BaseAccessModel(pk_id, Event, EventSchema)
# Users # Users
self.users = UserDataAccessModel(DEFAULT_PK, User, PrivateUser) self.users = UserDataAccessModel(pk_id, User, PrivateUser)
self.api_tokens = BaseAccessModel(DEFAULT_PK, LongLiveToken, LongLiveTokenInDB) self.api_tokens = BaseAccessModel(pk_id, LongLiveToken, LongLiveTokenInDB)
# Group Data # Group Data
self.groups = GroupDataAccessModel(DEFAULT_PK, Group, GroupInDB) self.groups = GroupDataAccessModel(pk_id, Group, GroupInDB)
self.meals = BaseAccessModel(DEFAULT_PK, MealPlan, MealPlanOut) self.meals = BaseAccessModel(pk_id, MealPlan, MealPlanOut)
self.webhooks = BaseAccessModel(DEFAULT_PK, GroupWebhooksModel, ReadWebhook) self.webhooks = BaseAccessModel(pk_id, GroupWebhooksModel, ReadWebhook)
self.shopping_lists = BaseAccessModel(DEFAULT_PK, ShoppingList, ShoppingListOut) self.shopping_lists = BaseAccessModel(pk_id, ShoppingList, ShoppingListOut)
self.cookbooks = BaseAccessModel(DEFAULT_PK, CookBook, ReadCookBook) self.cookbooks = BaseAccessModel(pk_id, CookBook, ReadCookBook)

View File

@ -3,6 +3,5 @@ from .group import *
from .mealplan import * from .mealplan import *
from .recipe.recipe import * from .recipe.recipe import *
from .settings import * from .settings import *
from .shopping_list import *
from .sign_up import * from .sign_up import *
from .users import * from .users import *

View File

@ -1,4 +1,3 @@
import uuid
from datetime import datetime from datetime import datetime
from sqlalchemy import Column, DateTime, Integer from sqlalchemy import Column, DateTime, Integer
@ -7,22 +6,11 @@ from sqlalchemy.orm import declarative_base
from sqlalchemy.orm.session import Session from sqlalchemy.orm.session import Session
def get_uuid_as_hex() -> str:
"""
Generate a UUID as a hex string.
:return: UUID as a hex string.
"""
return uuid.uuid4().hex
@as_declarative() @as_declarative()
class Base: class Base:
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
created_at = Column(DateTime, default=datetime.now()) created_at = Column(DateTime, default=datetime.now())
update_at = Column(DateTime, default=datetime.now(), onupdate=datetime.now())
# @declared_attr
# def __tablename__(cls):
# return cls.__name__.lower()
class BaseMixins: class BaseMixins:

View File

@ -2,6 +2,8 @@ from sqlalchemy import Boolean, Column, DateTime, Integer, String
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
from ._model_utils import auto_init
class EventNotification(SqlAlchemyBase, BaseMixins): class EventNotification(SqlAlchemyBase, BaseMixins):
__tablename__ = "event_notifications" __tablename__ = "event_notifications"
@ -19,19 +21,9 @@ class EventNotification(SqlAlchemyBase, BaseMixins):
group = Column(Boolean, default=False) group = Column(Boolean, default=False)
user = Column(Boolean, default=False) user = Column(Boolean, default=False)
def __init__( @auto_init()
self, name, notification_url, type, general, recipe, backup, scheduled, migration, group, user, **_ def __init__(self, **_) -> None:
) -> None: pass
self.name = name
self.notification_url = notification_url
self.type = type
self.general = general
self.recipe = recipe
self.backup = backup
self.scheduled = scheduled
self.migration = migration
self.group = group
self.user = user
class Event(SqlAlchemyBase, BaseMixins): class Event(SqlAlchemyBase, BaseMixins):
@ -42,8 +34,6 @@ class Event(SqlAlchemyBase, BaseMixins):
time_stamp = Column(DateTime) time_stamp = Column(DateTime)
category = Column(String) category = Column(String)
def __init__(self, title, text, time_stamp, category, **_) -> None: @auto_init()
self.title = title def __init__(self, **_) -> None:
self.text = text pass
self.time_stamp = time_stamp
self.category = category

View File

@ -1 +1,3 @@
from .group import * from .group import *
from .shopping_list import *
from .webhooks import *

View File

@ -1,8 +1,8 @@
from sqlalchemy import Column, ForeignKey, Integer, String, orm from sqlalchemy import Column, ForeignKey, Integer, String, orm
from ._model_base import BaseMixins, SqlAlchemyBase from .._model_base import BaseMixins, SqlAlchemyBase
from ._model_utils import auto_init from .._model_utils import auto_init
from .recipe.category import Category, cookbooks_to_categories from ..recipe.category import Category, cookbooks_to_categories
class CookBook(SqlAlchemyBase, BaseMixins): class CookBook(SqlAlchemyBase, BaseMixins):

View File

@ -3,12 +3,12 @@ import sqlalchemy.orm as orm
from sqlalchemy.orm.session import Session from sqlalchemy.orm.session import Session
from mealie.core.config import settings from mealie.core.config import settings
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
from mealie.db.models.cookbook import CookBook
from mealie.db.models.group.webhooks import GroupWebhooksModel
from mealie.db.models.recipe.category import Category, group2categories
from .._model_base import BaseMixins, SqlAlchemyBase
from .._model_utils import auto_init from .._model_utils import auto_init
from ..group.webhooks import GroupWebhooksModel
from ..recipe.category import Category, group2categories
from .cookbook import CookBook
class Group(SqlAlchemyBase, BaseMixins): class Group(SqlAlchemyBase, BaseMixins):

View File

@ -3,8 +3,8 @@ from requests import Session
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
from sqlalchemy.ext.orderinglist import ordering_list from sqlalchemy.ext.orderinglist import ordering_list
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase from .._model_base import BaseMixins, SqlAlchemyBase
from mealie.db.models.group import Group from .group import Group
class ShoppingListItem(SqlAlchemyBase, BaseMixins): class ShoppingListItem(SqlAlchemyBase, BaseMixins):

View File

@ -5,7 +5,8 @@ from sqlalchemy.ext.orderinglist import ordering_list
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
from mealie.db.models.group import Group from mealie.db.models.group import Group
from mealie.db.models.recipe.recipe import RecipeModel from mealie.db.models.recipe.recipe import RecipeModel
from mealie.db.models.shopping_list import ShoppingList
from .group.shopping_list import ShoppingList
class Meal(SqlAlchemyBase): class Meal(SqlAlchemyBase):

View File

@ -2,6 +2,8 @@ from sqlalchemy import Boolean, Column, Integer, String
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
from ._model_utils import auto_init
class SignUp(SqlAlchemyBase, BaseMixins): class SignUp(SqlAlchemyBase, BaseMixins):
__tablename__ = "sign_ups" __tablename__ = "sign_ups"
@ -10,13 +12,6 @@ class SignUp(SqlAlchemyBase, BaseMixins):
name = Column(String, index=True) name = Column(String, index=True)
admin = Column(Boolean, default=False) admin = Column(Boolean, default=False)
def __init__( @auto_init()
self, def __init__(self, **_) -> None:
session, pass
token,
name,
admin,
) -> None:
self.token = token
self.name = name
self.admin = admin

View File

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

View File

@ -27,9 +27,11 @@ class User(SqlAlchemyBase, BaseMixins):
username = Column(String, index=True, unique=True) username = Column(String, index=True, unique=True)
email = Column(String, unique=True, index=True) email = Column(String, unique=True, index=True)
password = Column(String) password = Column(String)
admin = Column(Boolean, default=False)
group_id = Column(Integer, ForeignKey("groups.id")) group_id = Column(Integer, ForeignKey("groups.id"))
group = orm.relationship("Group", back_populates="users") group = orm.relationship("Group", back_populates="users")
admin = Column(Boolean, default=False)
tokens: list[LongLiveToken] = orm.relationship( tokens: list[LongLiveToken] = orm.relationship(
LongLiveToken, back_populates="user", cascade="all, delete, delete-orphan", single_parent=True LongLiveToken, back_populates="user", cascade="all, delete, delete-orphan", single_parent=True
) )

View File

@ -3,7 +3,7 @@ from typing import Optional
from fastapi_camelcase import CamelModel from fastapi_camelcase import CamelModel
from pydantic.utils import GetterDict from pydantic.utils import GetterDict
from mealie.db.models.shopping_list import ShoppingList from mealie.db.models.group.shopping_list import ShoppingList
class ListItem(CamelModel): class ListItem(CamelModel):

View File

@ -1,3 +1,2 @@
from .base_http_service import * from .http_services import *
from .base_service import *
from .router_factory import * from .router_factory import *

View File

@ -1,12 +1,12 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import Callable, Generic, Type, TypeVar from typing import Any, Callable, Generic, Type, TypeVar
from fastapi import BackgroundTasks, Depends, HTTPException, status from fastapi import BackgroundTasks, Depends, HTTPException, status
from sqlalchemy.orm.session import Session from sqlalchemy.orm.session import Session
from mealie.core.config import get_app_dirs, get_settings from mealie.core.config import get_app_dirs, get_settings
from mealie.core.dependencies.grouped import PublicDeps, UserDeps
from mealie.core.root_logger import get_logger from mealie.core.root_logger import get_logger
from mealie.db.data_access_layer.db_access import DatabaseAccessLayer
from mealie.db.database import get_database from mealie.db.database import get_database
from mealie.db.db_setup import SessionLocal from mealie.db.db_setup import SessionLocal
from mealie.schema.user.user import PrivateUser from mealie.schema.user.user import PrivateUser
@ -44,10 +44,10 @@ class BaseHttpService(Generic[T, D], ABC):
delete_one: Callable = None delete_one: Callable = None
delete_all: Callable = None delete_all: Callable = None
db_access: DatabaseAccessLayer = None
# Type Definitions # Type Definitions
_schema = None _schema = None
_create_schema = None
_update_schema = None
# Function called to create a server side event # Function called to create a server side event
event_func: Callable = None event_func: Callable = None
@ -67,14 +67,6 @@ class BaseHttpService(Generic[T, D], ABC):
self.app_dirs = get_app_dirs() self.app_dirs = get_app_dirs()
self.settings = get_settings() self.settings = get_settings()
@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")
self._group_id_cache = group.id
return self._group_id_cache
def _existing_factory(dependency: Type[CLS_DEP]) -> classmethod: def _existing_factory(dependency: Type[CLS_DEP]) -> classmethod:
def cls_method(cls, item_id: T, deps: CLS_DEP = Depends(dependency)): def cls_method(cls, item_id: T, deps: CLS_DEP = Depends(dependency)):
new_class = cls(deps.session, deps.user, deps.bg_task) new_class = cls(deps.session, deps.user, deps.bg_task)
@ -89,21 +81,42 @@ class BaseHttpService(Generic[T, D], ABC):
return classmethod(cls_method) return classmethod(cls_method)
# TODO: Refactor to allow for configurable dependencies base on substantiation @classmethod
read_existing = _existing_factory(PublicDeps) @abstractmethod
write_existing = _existing_factory(UserDeps) def public(cls, deps: Any):
pass
public = _class_method_factory(PublicDeps) @classmethod
private = _class_method_factory(UserDeps) @abstractmethod
def private(cls, deps: Any):
pass
@classmethod
@abstractmethod
def read_existing(cls, deps: Any):
pass
@classmethod
@abstractmethod
def write_existing(cls, deps: Any):
pass
@abstractmethod
def populate_item(self) -> None:
pass
@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")
self._group_id_cache = group.id
return self._group_id_cache
def assert_existing(self, id: T) -> None: def assert_existing(self, id: T) -> None:
self.populate_item(id) self.populate_item(id)
self._check_item() self._check_item()
@abstractmethod
def populate_item(self) -> None:
...
def _check_item(self) -> None: def _check_item(self) -> None:
if not self.item: if not self.item:
raise HTTPException(status.HTTP_404_NOT_FOUND) raise HTTPException(status.HTTP_404_NOT_FOUND)
@ -114,8 +127,38 @@ class BaseHttpService(Generic[T, D], ABC):
if not group_id or group_id != self.group_id: if not group_id or group_id != self.group_id:
raise HTTPException(status.HTTP_403_FORBIDDEN) raise HTTPException(status.HTTP_403_FORBIDDEN)
if hasattr(self, "check_item"):
self.check_item()
def _create_event(self, title: str, message: str) -> None: def _create_event(self, title: str, message: str) -> None:
if not self.__class__.event_func: if not self.__class__.event_func:
raise NotImplementedError("`event_func` must be set by child class") raise NotImplementedError("`event_func` must be set by child class")
self.background_tasks.add_task(self.__class__.event_func, title, message, self.session) self.background_tasks.add_task(self.__class__.event_func, title, message, self.session)
# Generic CRUD Functions
def _create_one(self, data: Any, exception_msg="generic-create-error") -> D:
try:
self.item = self.db_access.create(self.session, data)
except Exception as ex:
logger.exception(ex)
raise HTTPException(status.HTTP_400_BAD_REQUEST, detail={"message": exception_msg, "exception": str(ex)})
return self.item
def _update_one(self, data: Any, id: int = None) -> D:
if not self.item:
return
target_id = id or self.item.id
self.item = self.db_access.update(self.session, target_id, data)
return self.item
def _delete_one(self, id: int = None) -> D:
if not self.item:
return
target_id = id or self.item.id
self.item = self.db_access.delete(self.session, target_id)
return self.item

View File

@ -1,17 +0,0 @@
from mealie.core.config import get_app_dirs, get_settings
from mealie.core.root_logger import get_logger
from mealie.db.database import get_database
from mealie.db.db_setup import generate_session
logger = get_logger()
class BaseService:
def __init__(self) -> None:
# Static Globals Dependency Injection
self.db = get_database()
self.app_dirs = get_app_dirs()
self.settings = get_settings()
def session_context(self):
return generate_session()

View File

@ -1,5 +1,5 @@
import inspect import inspect
from typing import Any, Callable, Optional, Sequence, Type, TypeVar from typing import Any, Callable, Optional, Sequence, Type, TypeVar, get_type_hints
from fastapi import APIRouter from fastapi import APIRouter
from fastapi.params import Depends from fastapi.params import Depends
@ -8,7 +8,7 @@ from pydantic import BaseModel
from .base_http_service import BaseHttpService from .base_http_service import BaseHttpService
"""" """
This code is largely based off of the FastAPI Crud Router This code is largely based off of the FastAPI Crud Router
https://github.com/awtkns/fastapi-crudrouter/blob/master/fastapi_crudrouter/core/_base.py https://github.com/awtkns/fastapi-crudrouter/blob/master/fastapi_crudrouter/core/_base.py
""" """
@ -18,25 +18,40 @@ S = TypeVar("S", bound=BaseHttpService)
DEPENDENCIES = Optional[Sequence[Depends]] DEPENDENCIES = Optional[Sequence[Depends]]
def get_return(func: Callable, default) -> Type:
return get_type_hints(func).get("return", default)
def get_func_args(func: Callable) -> Sequence[str]:
for _, value in get_type_hints(func).items():
if value:
return value
else:
return None
class RouterFactory(APIRouter): class RouterFactory(APIRouter):
schema: Type[T] schema: Type[T]
create_schema: Type[T]
update_schema: Type[T]
_base_path: str = "/" _base_path: str = "/"
def __init__(self, service: Type[S], prefix: Optional[str] = None, tags: Optional[list[str]] = None, *_, **kwargs): def __init__(self, service: Type[S], prefix: Optional[str] = None, tags: Optional[list[str]] = None, *_, **kwargs):
"""
RouterFactory takes a concrete service class derived from the BaseHttpService class and returns common
CRUD Routes for the service. The following features are implmeneted in the RouterFactory:
1. API endpoint Descriptions are read from the docstrings of the methods in the passed in service class
2. Return types are inferred from the concrete service schema, or specified from the return type annotations.
This provides flexibility to return different types based on each route depending on client needs.
3. Arguemnt types are inferred for Post and Put routes where the first type annotated argument is the data that
is beging posted or updated. Note that this is only done for the first argument of the method.
4. The Get and Delete routes assume that you've defined the `write_existing` and `read_existing` methods in the
service class. The dependencies defined in the `write_existing` and `read_existing` methods are passed directly
to the FastAPI router and as such should include the `item_id` or equilivent argument.
"""
self.service: Type[S] = service self.service: Type[S] = service
self.schema: Type[T] = service._schema self.schema: Type[T] = service._schema
# HACK: Special Case for Coobooks, not sure this is a good way to handle the abstraction :/
if hasattr(self.service, "_get_one_schema"):
self.get_one_schema = self.service._get_one_schema
else:
self.get_one_schema = self.schema
self.update_schema: Type[T] = service._update_schema
self.create_schema: Type[T] = service._create_schema
prefix = str(prefix or self.schema.__name__).lower() prefix = str(prefix or self.schema.__name__).lower()
prefix = self._base_path + prefix.strip("/") prefix = self._base_path + prefix.strip("/")
tags = tags or [prefix.strip("/").capitalize()] tags = tags or [prefix.strip("/").capitalize()]
@ -88,7 +103,7 @@ class RouterFactory(APIRouter):
"/{item_id}", "/{item_id}",
self._get_one(), self._get_one(),
methods=["GET"], methods=["GET"],
response_model=self.get_one_schema, response_model=get_type_hints(self.service.populate_item).get("return", self.schema),
summary="Get One", summary="Get One",
description=inspect.cleandoc(self.service.populate_item.__doc__ or ""), description=inspect.cleandoc(self.service.populate_item.__doc__ or ""),
) )
@ -104,7 +119,6 @@ class RouterFactory(APIRouter):
) )
if self.service.delete_one: if self.service.delete_one:
print(self.service.delete_one.__doc__)
self._add_api_route( self._add_api_route(
"/{item_id}", "/{item_id}",
self._delete_one(), self._delete_one(),
@ -160,19 +174,25 @@ class RouterFactory(APIRouter):
return route return route
def _create(self, *args: Any, **kwargs: Any) -> Callable[..., Any]: def _create(self, *args: Any, **kwargs: Any) -> Callable[..., Any]:
def route(data: self.create_schema, service: S = Depends(self.service.private)) -> T: # type: ignore create_schema = get_func_args(self.service.create_one) or self.schema
def route(data: create_schema, service: S = Depends(self.service.private)) -> T: # type: ignore
return service.create_one(data) return service.create_one(data)
return route return route
def _update(self, *args: Any, **kwargs: Any) -> Callable[..., Any]: def _update(self, *args: Any, **kwargs: Any) -> Callable[..., Any]:
def route(data: self.update_schema, service: S = Depends(self.service.write_existing)) -> T: # type: ignore update_schema = get_func_args(self.service.update_one) or self.schema
def route(data: update_schema, service: S = Depends(self.service.write_existing)) -> T: # type: ignore
return service.update_one(data) return service.update_one(data)
return route return route
def _update_many(self, *args: Any, **kwargs: Any) -> Callable[..., Any]: def _update_many(self, *args: Any, **kwargs: Any) -> Callable[..., Any]:
def route(data: list[self.update_schema], service: S = Depends(self.service.write_existing)) -> T: # type: ignore update_many_schema = get_func_args(self.service.update_many) or list[self.schema]
def route(data: update_many_schema, service: S = Depends(self.service.private)) -> T: # type: ignore
return service.update_many(data) return service.update_many(data)
return route return route

View File

@ -1,11 +1,11 @@
from __future__ import annotations from __future__ import annotations
from fastapi import HTTPException, status
from mealie.core.root_logger import get_logger from mealie.core.root_logger import get_logger
from mealie.db.database import get_database
from mealie.schema.cookbook.cookbook import CreateCookBook, ReadCookBook, RecipeCookBook, SaveCookBook, UpdateCookBook from mealie.schema.cookbook.cookbook import CreateCookBook, ReadCookBook, RecipeCookBook, SaveCookBook, UpdateCookBook
from mealie.services.base_http_service.http_services import UserHttpService from mealie.services.base_http_service.http_services import UserHttpService
from mealie.services.events import create_group_event from mealie.services.events import create_group_event
from mealie.utils.error_messages import ErrorMessages
logger = get_logger(module=__name__) logger = get_logger(module=__name__)
@ -15,11 +15,10 @@ class CookbookService(UserHttpService[int, ReadCookBook]):
_restrict_by_group = True _restrict_by_group = True
_schema = ReadCookBook _schema = ReadCookBook
_create_schema = CreateCookBook
_update_schema = UpdateCookBook
_get_one_schema = RecipeCookBook
def populate_item(self, item_id: int | str): db_access = get_database().cookbooks
def populate_item(self, item_id: int) -> RecipeCookBook:
try: try:
item_id = int(item_id) item_id = int(item_id)
except Exception: except Exception:
@ -37,25 +36,13 @@ class CookbookService(UserHttpService[int, ReadCookBook]):
return items return items
def create_one(self, data: CreateCookBook) -> ReadCookBook: def create_one(self, data: CreateCookBook) -> ReadCookBook:
try: data = SaveCookBook(group_id=self.group_id, **data.dict())
self.item = self.db.cookbooks.create(self.session, SaveCookBook(group_id=self.group_id, **data.dict())) return self._create_one(data, ErrorMessages.cookbook_create_failure)
except Exception as ex:
raise HTTPException(
status.HTTP_400_BAD_REQUEST, detail={"message": "PAGE_CREATION_ERROR", "exception": str(ex)}
)
return self.item def update_one(self, data: UpdateCookBook, id: int = None) -> ReadCookBook:
return self._update_one(data, id)
def update_one(self, data: CreateCookBook, id: int = None) -> ReadCookBook: def update_many(self, data: list[UpdateCookBook]) -> list[ReadCookBook]:
if not self.item:
return
target_id = id or self.item.id
self.item = self.db.cookbooks.update(self.session, target_id, data)
return self.item
def update_many(self, data: list[ReadCookBook]) -> list[ReadCookBook]:
updated = [] updated = []
for cookbook in data: for cookbook in data:
@ -65,10 +52,4 @@ class CookbookService(UserHttpService[int, ReadCookBook]):
return updated return updated
def delete_one(self, id: int = None) -> ReadCookBook: def delete_one(self, id: int = None) -> ReadCookBook:
if not self.item: return self._delete_one(id)
return
target_id = id or self.item.id
self.item = self.db.cookbooks.delete(self.session, target_id)
return self.item

View File

@ -6,13 +6,13 @@ from mealie.core.dependencies.grouped import UserDeps
from mealie.core.root_logger import get_logger from mealie.core.root_logger import get_logger
from mealie.schema.recipe.recipe_category import CategoryBase from mealie.schema.recipe.recipe_category import CategoryBase
from mealie.schema.user.user import GroupInDB from mealie.schema.user.user import GroupInDB
from mealie.services.base_http_service.base_http_service import BaseHttpService from mealie.services.base_http_service.http_services import UserHttpService
from mealie.services.events import create_group_event from mealie.services.events import create_group_event
logger = get_logger(module=__name__) logger = get_logger(module=__name__)
class GroupSelfService(BaseHttpService[int, str]): class GroupSelfService(UserHttpService[int, str]):
_restrict_by_group = True _restrict_by_group = True
event_func = create_group_event event_func = create_group_event
item: GroupInDB item: GroupInDB
@ -36,8 +36,9 @@ class GroupSelfService(BaseHttpService[int, str]):
if self.item.id != self.group_id: if self.item.id != self.group_id:
raise HTTPException(status.HTTP_403_FORBIDDEN) raise HTTPException(status.HTTP_403_FORBIDDEN)
def populate_item(self, _: str = None): def populate_item(self, _: str = None) -> GroupInDB:
self.item = self.db.groups.get(self.session, self.group_id) self.item = self.db.groups.get(self.session, self.group_id)
return self.item
def update_categories(self, new_categories: list[CategoryBase]): def update_categories(self, new_categories: list[CategoryBase]):
if not self.item: if not self.item:

View File

@ -5,13 +5,13 @@ from fastapi import HTTPException, status
from mealie.core.root_logger import get_logger from mealie.core.root_logger import get_logger
from mealie.schema.group import ReadWebhook from mealie.schema.group import ReadWebhook
from mealie.schema.group.webhook import CreateWebhook, SaveWebhook from mealie.schema.group.webhook import CreateWebhook, SaveWebhook
from mealie.services.base_http_service.base_http_service import BaseHttpService from mealie.services.base_http_service.http_services import UserHttpService
from mealie.services.events import create_group_event from mealie.services.events import create_group_event
logger = get_logger(module=__name__) logger = get_logger(module=__name__)
class WebhookService(BaseHttpService[int, ReadWebhook]): class WebhookService(UserHttpService[int, ReadWebhook]):
event_func = create_group_event event_func = create_group_event
_restrict_by_group = True _restrict_by_group = True
@ -19,8 +19,9 @@ class WebhookService(BaseHttpService[int, ReadWebhook]):
_create_schema = CreateWebhook _create_schema = CreateWebhook
_update_schema = CreateWebhook _update_schema = CreateWebhook
def populate_item(self, id: int | str): def populate_item(self, id: int) -> ReadWebhook:
self.item = self.db.webhooks.get_one(self.session, id) self.item = self.db.webhooks.get_one(self.session, id)
return self.item
def get_all(self) -> list[ReadWebhook]: def get_all(self) -> list[ReadWebhook]:
return self.db.webhooks.get(self.session, self.group_id, match_key="group_id", limit=9999) return self.db.webhooks.get(self.session, self.group_id, match_key="group_id", limit=9999)

View File

@ -8,13 +8,13 @@ from sqlalchemy.exc import IntegrityError
from mealie.core.dependencies.grouped import PublicDeps, UserDeps from mealie.core.dependencies.grouped import PublicDeps, UserDeps
from mealie.core.root_logger import get_logger from mealie.core.root_logger import get_logger
from mealie.schema.recipe.recipe import CreateRecipe, Recipe from mealie.schema.recipe.recipe import CreateRecipe, Recipe
from mealie.services.base_http_service.base_http_service import BaseHttpService from mealie.services.base_http_service.http_services import PublicHttpService
from mealie.services.events import create_recipe_event from mealie.services.events import create_recipe_event
logger = get_logger(module=__name__) logger = get_logger(module=__name__)
class RecipeService(BaseHttpService[str, Recipe]): class RecipeService(PublicHttpService[str, Recipe]):
""" """
Class Methods: Class Methods:
`read_existing`: Reads an existing recipe from the database. `read_existing`: Reads an existing recipe from the database.

View File

@ -3,13 +3,13 @@ from fastapi import HTTPException, status
from mealie.core.root_logger import get_logger from mealie.core.root_logger import get_logger
from mealie.core.security import hash_password, verify_password from mealie.core.security import hash_password, verify_password
from mealie.schema.user.user import ChangePassword, PrivateUser from mealie.schema.user.user import ChangePassword, PrivateUser
from mealie.services.base_http_service.base_http_service import BaseHttpService from mealie.services.base_http_service.http_services import UserHttpService
from mealie.services.events import create_user_event from mealie.services.events import create_user_event
logger = get_logger(module=__name__) logger = get_logger(module=__name__)
class UserService(BaseHttpService[int, str]): class UserService(UserHttpService[int, str]):
event_func = create_user_event event_func = create_user_event
acting_user: PrivateUser = None acting_user: PrivateUser = None