mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-02-03 22:43:11 -05:00
refactor(backend): ♻️ cleanup HTTP service classes and remove database singleton (#687)
* refactor(backend): ♻️ cleanup duplicate code in http services * refactor(backend): ♻️ refactor database away from singleton design removed the database single and instead injected the session into a new Database class that is created during each request life-cycle. Now sessions no longer need to be passed into each method on the database All tests pass, but there are likely some hidden breaking changes that were not discovered. * fix venv * disable venv cache * fix install script * bump poetry version * postgres fixes * revert install * fix db initialization for postgres * add postgres to docker * refactor(backend): ♻️ cleanup unused and duplicate code in http services * refactor(backend): remove sessions from arguments * refactor(backend): ♻️ convert units and ingredients to use http service class * test(backend): ✅ add unit and food tests * lint * update tags * re-enable cache * fix missing fraction in db * fix lint Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
@@ -1 +1 @@
|
||||
from .db_access import DatabaseAccessLayer
|
||||
from .access_model_factory import Database
|
||||
|
||||
@@ -14,7 +14,7 @@ T = TypeVar("T")
|
||||
D = TypeVar("D")
|
||||
|
||||
|
||||
class BaseAccessModel(Generic[T, D]):
|
||||
class AccessModel(Generic[T, D]):
|
||||
"""A Generic BaseAccess Model method to perform common operations on the database
|
||||
|
||||
Args:
|
||||
@@ -22,7 +22,8 @@ class BaseAccessModel(Generic[T, D]):
|
||||
Generic ([D]): Represents the SqlAlchemyModel Model
|
||||
"""
|
||||
|
||||
def __init__(self, primary_key: Union[str, int], sql_model: D, schema: T) -> None:
|
||||
def __init__(self, session: Session, primary_key: Union[str, int], sql_model: D, schema: T) -> None:
|
||||
self.session = session
|
||||
self.primary_key = primary_key
|
||||
self.sql_model = sql_model
|
||||
self.schema = schema
|
||||
@@ -37,9 +38,7 @@ class BaseAccessModel(Generic[T, D]):
|
||||
for observer in self.observers:
|
||||
observer()
|
||||
|
||||
def get_all(
|
||||
self, session: Session, limit: int = None, order_by: str = None, start=0, override_schema=None
|
||||
) -> list[T]:
|
||||
def get_all(self, limit: int = None, order_by: str = None, start=0, override_schema=None) -> list[T]:
|
||||
eff_schema = override_schema or self.schema
|
||||
|
||||
if order_by:
|
||||
@@ -47,27 +46,20 @@ class BaseAccessModel(Generic[T, D]):
|
||||
|
||||
return [
|
||||
eff_schema.from_orm(x)
|
||||
for x in session.query(self.sql_model).order_by(order_attr.desc()).offset(start).limit(limit).all()
|
||||
for x in self.session.query(self.sql_model).order_by(order_attr.desc()).offset(start).limit(limit).all()
|
||||
]
|
||||
|
||||
return [eff_schema.from_orm(x) for x in session.query(self.sql_model).offset(start).limit(limit).all()]
|
||||
return [eff_schema.from_orm(x) for x in self.session.query(self.sql_model).offset(start).limit(limit).all()]
|
||||
|
||||
def multi_query(
|
||||
self,
|
||||
session: Session,
|
||||
query_by: dict[str, str],
|
||||
start=0,
|
||||
limit: int = None,
|
||||
override_schema=None,
|
||||
) -> list[T]:
|
||||
def multi_query(self, query_by: dict[str, str], start=0, limit: int = None, override_schema=None) -> list[T]:
|
||||
eff_schema = override_schema or self.schema
|
||||
|
||||
return [
|
||||
eff_schema.from_orm(x)
|
||||
for x in session.query(self.sql_model).filter_by(**query_by).offset(start).limit(limit).all()
|
||||
for x in self.session.query(self.sql_model).filter_by(**query_by).offset(start).limit(limit).all()
|
||||
]
|
||||
|
||||
def get_all_limit_columns(self, session: Session, fields: list[str], limit: int = None) -> list[D]:
|
||||
def get_all_limit_columns(self, fields: list[str], limit: int = None) -> list[D]:
|
||||
"""Queries the database for the selected model. Restricts return responses to the
|
||||
keys specified under "fields"
|
||||
|
||||
@@ -79,9 +71,9 @@ class BaseAccessModel(Generic[T, D]):
|
||||
Returns:
|
||||
list[SqlAlchemyBase]: Returns a list of ORM objects
|
||||
"""
|
||||
return session.query(self.sql_model).options(load_only(*fields)).limit(limit).all()
|
||||
return self.session.query(self.sql_model).options(load_only(*fields)).limit(limit).all()
|
||||
|
||||
def get_all_primary_keys(self, session: Session) -> list[str]:
|
||||
def get_all_primary_keys(self) -> list[str]:
|
||||
"""Queries the database of the selected model and returns a list
|
||||
of all primary_key values
|
||||
|
||||
@@ -91,11 +83,11 @@ class BaseAccessModel(Generic[T, D]):
|
||||
Returns:
|
||||
list[str]:
|
||||
"""
|
||||
results = session.query(self.sql_model).options(load_only(str(self.primary_key)))
|
||||
results = self.session.query(self.sql_model).options(load_only(str(self.primary_key)))
|
||||
results_as_dict = [x.dict() for x in results]
|
||||
return [x.get(self.primary_key) for x in results_as_dict]
|
||||
|
||||
def _query_one(self, session: Session, match_value: str, match_key: str = None) -> D:
|
||||
def _query_one(self, match_value: str, match_key: str = None) -> D:
|
||||
"""
|
||||
Query the sql database for one item an return the sql alchemy model
|
||||
object. If no match key is provided the primary_key attribute will be used.
|
||||
@@ -103,16 +95,16 @@ class BaseAccessModel(Generic[T, D]):
|
||||
if match_key is None:
|
||||
match_key = self.primary_key
|
||||
|
||||
return session.query(self.sql_model).filter_by(**{match_key: match_value}).one()
|
||||
return self.session.query(self.sql_model).filter_by(**{match_key: match_value}).one()
|
||||
|
||||
def get_one(self, session: Session, value: str | int, key: str = None, any_case=False, override_schema=None) -> T:
|
||||
def get_one(self, value: str | int, key: str = None, any_case=False, override_schema=None) -> T:
|
||||
key = key or self.primary_key
|
||||
|
||||
if any_case:
|
||||
search_attr = getattr(self.sql_model, key)
|
||||
result = session.query(self.sql_model).filter(func.lower(search_attr) == key.lower()).one_or_none()
|
||||
result = self.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 = self.session.query(self.sql_model).filter_by(**{key: value}).one_or_none()
|
||||
|
||||
if not result:
|
||||
return
|
||||
@@ -121,7 +113,7 @@ class BaseAccessModel(Generic[T, D]):
|
||||
return eff_schema.from_orm(result)
|
||||
|
||||
def get(
|
||||
self, session: Session, match_value: str, match_key: str = None, limit=1, any_case=False, override_schema=None
|
||||
self, match_value: str, match_key: str = None, limit=1, any_case=False, override_schema=None
|
||||
) -> T | list[T]:
|
||||
"""Retrieves an entry from the database by matching a key/value pair. If no
|
||||
key is provided the class objects primary key will be used to match against.
|
||||
@@ -141,10 +133,13 @@ class BaseAccessModel(Generic[T, D]):
|
||||
if any_case:
|
||||
search_attr = getattr(self.sql_model, match_key)
|
||||
result = (
|
||||
session.query(self.sql_model).filter(func.lower(search_attr) == match_value.lower()).limit(limit).all()
|
||||
self.session.query(self.sql_model)
|
||||
.filter(func.lower(search_attr) == match_value.lower())
|
||||
.limit(limit)
|
||||
.all()
|
||||
)
|
||||
else:
|
||||
result = session.query(self.sql_model).filter_by(**{match_key: match_value}).limit(limit).all()
|
||||
result = self.session.query(self.sql_model).filter_by(**{match_key: match_value}).limit(limit).all()
|
||||
|
||||
eff_schema = override_schema or self.schema
|
||||
|
||||
@@ -156,7 +151,7 @@ class BaseAccessModel(Generic[T, D]):
|
||||
|
||||
return [eff_schema.from_orm(x) for x in result]
|
||||
|
||||
def create(self, session: Session, document: T) -> T:
|
||||
def create(self, document: T) -> T:
|
||||
"""Creates a new database entry for the given SQL Alchemy Model.
|
||||
|
||||
Args:
|
||||
@@ -167,17 +162,17 @@ class BaseAccessModel(Generic[T, D]):
|
||||
dict: A dictionary representation of the database entry
|
||||
"""
|
||||
document = document if isinstance(document, dict) else document.dict()
|
||||
new_document = self.sql_model(session=session, **document)
|
||||
session.add(new_document)
|
||||
session.commit()
|
||||
session.refresh(new_document)
|
||||
new_document = self.sql_model(session=self.session, **document)
|
||||
self.session.add(new_document)
|
||||
self.session.commit()
|
||||
self.session.refresh(new_document)
|
||||
|
||||
if self.observers:
|
||||
self.update_observers()
|
||||
|
||||
return self.schema.from_orm(new_document)
|
||||
|
||||
def update(self, session: Session, match_value: str, new_data: dict) -> T:
|
||||
def update(self, match_value: str, new_data: dict) -> T:
|
||||
"""Update a database entry.
|
||||
Args:
|
||||
session (Session): Database Session
|
||||
@@ -189,19 +184,19 @@ class BaseAccessModel(Generic[T, D]):
|
||||
"""
|
||||
new_data = new_data if isinstance(new_data, dict) else new_data.dict()
|
||||
|
||||
entry = self._query_one(session=session, match_value=match_value)
|
||||
entry.update(session=session, **new_data)
|
||||
entry = self._query_one(match_value=match_value)
|
||||
entry.update(session=self.session, **new_data)
|
||||
|
||||
if self.observers:
|
||||
self.update_observers()
|
||||
|
||||
session.commit()
|
||||
self.session.commit()
|
||||
return self.schema.from_orm(entry)
|
||||
|
||||
def patch(self, session: Session, match_value: str, new_data: dict) -> T:
|
||||
def patch(self, match_value: str, new_data: dict) -> T:
|
||||
new_data = new_data if isinstance(new_data, dict) else new_data.dict()
|
||||
|
||||
entry = self._query_one(session=session, match_value=match_value)
|
||||
entry = self._query_one(match_value=match_value)
|
||||
|
||||
if not entry:
|
||||
return
|
||||
@@ -209,43 +204,43 @@ class BaseAccessModel(Generic[T, D]):
|
||||
entry_as_dict = self.schema.from_orm(entry).dict()
|
||||
entry_as_dict.update(new_data)
|
||||
|
||||
return self.update(session, match_value, entry_as_dict)
|
||||
return self.update(match_value, entry_as_dict)
|
||||
|
||||
def delete(self, session: Session, primary_key_value) -> D:
|
||||
result = session.query(self.sql_model).filter_by(**{self.primary_key: primary_key_value}).one()
|
||||
def delete(self, primary_key_value) -> D:
|
||||
result = self.session.query(self.sql_model).filter_by(**{self.primary_key: primary_key_value}).one()
|
||||
results_as_model = self.schema.from_orm(result)
|
||||
|
||||
session.delete(result)
|
||||
session.commit()
|
||||
self.session.delete(result)
|
||||
self.session.commit()
|
||||
|
||||
if self.observers:
|
||||
self.update_observers()
|
||||
|
||||
return results_as_model
|
||||
|
||||
def delete_all(self, session: Session) -> None:
|
||||
session.query(self.sql_model).delete()
|
||||
session.commit()
|
||||
def delete_all(self) -> None:
|
||||
self.session.query(self.sql_model).delete()
|
||||
self.session.commit()
|
||||
|
||||
if self.observers:
|
||||
self.update_observers()
|
||||
|
||||
def count_all(self, session: Session, match_key=None, match_value=None) -> int:
|
||||
def count_all(self, match_key=None, match_value=None) -> int:
|
||||
if None in [match_key, match_value]:
|
||||
return session.query(self.sql_model).count()
|
||||
return self.session.query(self.sql_model).count()
|
||||
else:
|
||||
return session.query(self.sql_model).filter_by(**{match_key: match_value}).count()
|
||||
return self.session.query(self.sql_model).filter_by(**{match_key: match_value}).count()
|
||||
|
||||
def _count_attribute(
|
||||
self, session: Session, attribute_name: str, attr_match: str = None, count=True, override_schema=None
|
||||
self, attribute_name: str, attr_match: str = None, count=True, override_schema=None
|
||||
) -> Union[int, T]:
|
||||
eff_schema = override_schema or self.schema
|
||||
# attr_filter = getattr(self.sql_model, attribute_name)
|
||||
|
||||
if count:
|
||||
return session.query(self.sql_model).filter(attribute_name == attr_match).count() # noqa: 711
|
||||
return self.session.query(self.sql_model).filter(attribute_name == attr_match).count() # noqa: 711
|
||||
else:
|
||||
return [
|
||||
eff_schema.from_orm(x)
|
||||
for x in session.query(self.sql_model).filter(attribute_name == attr_match).all() # noqa: 711
|
||||
for x in self.session.query(self.sql_model).filter(attribute_name == attr_match).all() # noqa: 711
|
||||
]
|
||||
145
mealie/db/data_access_layer/access_model_factory.py
Normal file
145
mealie/db/data_access_layer/access_model_factory.py
Normal file
@@ -0,0 +1,145 @@
|
||||
from functools import cached_property
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from mealie.db.models.event import Event, EventNotification
|
||||
from mealie.db.models.group import Group, GroupMealPlan
|
||||
from mealie.db.models.group.cookbook import CookBook
|
||||
from mealie.db.models.group.invite_tokens import GroupInviteToken
|
||||
from mealie.db.models.group.preferences import GroupPreferencesModel
|
||||
from mealie.db.models.group.webhooks import GroupWebhooksModel
|
||||
from mealie.db.models.recipe.category import Category
|
||||
from mealie.db.models.recipe.comment import RecipeComment
|
||||
from mealie.db.models.recipe.ingredient import IngredientFoodModel, IngredientUnitModel
|
||||
from mealie.db.models.recipe.recipe import RecipeModel
|
||||
from mealie.db.models.recipe.tag import Tag
|
||||
from mealie.db.models.settings import SiteSettings
|
||||
from mealie.db.models.sign_up import SignUp
|
||||
from mealie.db.models.users import LongLiveToken, User
|
||||
from mealie.schema.admin import SiteSettings as SiteSettingsSchema
|
||||
from mealie.schema.cookbook.cookbook import ReadCookBook
|
||||
from mealie.schema.events import Event as EventSchema
|
||||
from mealie.schema.events import EventNotificationIn
|
||||
from mealie.schema.group.group_preferences import ReadGroupPreferences
|
||||
from mealie.schema.group.invite_token import ReadInviteToken
|
||||
from mealie.schema.group.webhook import ReadWebhook
|
||||
from mealie.schema.meal_plan.new_meal import ReadPlanEntry
|
||||
from mealie.schema.recipe import CommentOut, Recipe, RecipeCategoryResponse, RecipeTagResponse
|
||||
from mealie.schema.recipe.recipe_ingredient import IngredientFood, IngredientUnit
|
||||
from mealie.schema.user import GroupInDB, LongLiveTokenInDB, PrivateUser, SignUpOut
|
||||
|
||||
from ._access_model import AccessModel
|
||||
from .group_access_model import GroupDataAccessModel
|
||||
from .meal_access_model import MealDataAccessModel
|
||||
from .recipe_access_model import RecipeDataAccessModel
|
||||
from .user_access_model import UserDataAccessModel
|
||||
|
||||
pk_id = "id"
|
||||
pk_slug = "slug"
|
||||
pk_token = "token"
|
||||
|
||||
|
||||
class CategoryDataAccessModel(AccessModel):
|
||||
def get_empty(self):
|
||||
return self.session.query(Category).filter(~Category.recipes.any()).all()
|
||||
|
||||
|
||||
class TagsDataAccessModel(AccessModel):
|
||||
def get_empty(self):
|
||||
return self.session.query(Tag).filter(~Tag.recipes.any()).all()
|
||||
|
||||
|
||||
class Database:
|
||||
def __init__(self, session: Session) -> None:
|
||||
"""
|
||||
`DatabaseAccessLayer` class is the data access layer for all database actions within
|
||||
Mealie. Database uses composition from classes derived from AccessModel. These
|
||||
can be substantiated from the AccessModel class or through inheritance when
|
||||
additional methods are required.
|
||||
"""
|
||||
|
||||
self.session = session
|
||||
|
||||
# ================================================================
|
||||
# Recipe Items
|
||||
|
||||
@cached_property
|
||||
def recipes(self) -> RecipeDataAccessModel:
|
||||
return RecipeDataAccessModel(self.session, pk_slug, RecipeModel, Recipe)
|
||||
|
||||
@cached_property
|
||||
def ingredient_foods(self) -> AccessModel:
|
||||
return AccessModel(self.session, pk_id, IngredientFoodModel, IngredientFood)
|
||||
|
||||
@cached_property
|
||||
def ingredient_units(self) -> AccessModel:
|
||||
return AccessModel(self.session, pk_id, IngredientUnitModel, IngredientUnit)
|
||||
|
||||
@cached_property
|
||||
def comments(self) -> AccessModel:
|
||||
return AccessModel(self.session, pk_id, RecipeComment, CommentOut)
|
||||
|
||||
@cached_property
|
||||
def categories(self) -> CategoryDataAccessModel:
|
||||
return CategoryDataAccessModel(self.session, pk_id, Category, RecipeCategoryResponse)
|
||||
|
||||
@cached_property
|
||||
def tags(self) -> TagsDataAccessModel:
|
||||
return TagsDataAccessModel(self.session, pk_id, Tag, RecipeTagResponse)
|
||||
|
||||
# ================================================================
|
||||
# Site Items
|
||||
|
||||
@cached_property
|
||||
def settings(self) -> AccessModel:
|
||||
return AccessModel(self.session, pk_id, SiteSettings, SiteSettingsSchema)
|
||||
|
||||
@cached_property
|
||||
def sign_up(self) -> AccessModel:
|
||||
return AccessModel(self.session, pk_id, SignUp, SignUpOut)
|
||||
|
||||
@cached_property
|
||||
def event_notifications(self) -> AccessModel:
|
||||
return AccessModel(self.session, pk_id, EventNotification, EventNotificationIn)
|
||||
|
||||
@cached_property
|
||||
def events(self) -> AccessModel:
|
||||
return AccessModel(self.session, pk_id, Event, EventSchema)
|
||||
|
||||
# ================================================================
|
||||
# User Items
|
||||
|
||||
@cached_property
|
||||
def users(self) -> UserDataAccessModel:
|
||||
return UserDataAccessModel(self.session, pk_id, User, PrivateUser)
|
||||
|
||||
@cached_property
|
||||
def api_tokens(self) -> AccessModel:
|
||||
return AccessModel(self.session, pk_id, LongLiveToken, LongLiveTokenInDB)
|
||||
|
||||
# ================================================================
|
||||
# Group Items
|
||||
|
||||
@cached_property
|
||||
def groups(self) -> GroupDataAccessModel:
|
||||
return GroupDataAccessModel(self.session, pk_id, Group, GroupInDB)
|
||||
|
||||
@cached_property
|
||||
def group_invite_tokens(self) -> AccessModel:
|
||||
return AccessModel(self.session, "token", GroupInviteToken, ReadInviteToken)
|
||||
|
||||
@cached_property
|
||||
def group_preferences(self) -> AccessModel:
|
||||
return AccessModel(self.session, "group_id", GroupPreferencesModel, ReadGroupPreferences)
|
||||
|
||||
@cached_property
|
||||
def meals(self) -> MealDataAccessModel:
|
||||
return MealDataAccessModel(self.session, pk_id, GroupMealPlan, ReadPlanEntry)
|
||||
|
||||
@cached_property
|
||||
def cookbooks(self) -> AccessModel:
|
||||
return AccessModel(self.session, pk_id, CookBook, ReadCookBook)
|
||||
|
||||
@cached_property
|
||||
def webhooks(self) -> AccessModel:
|
||||
return AccessModel(self.session, pk_id, GroupWebhooksModel, ReadWebhook)
|
||||
@@ -1,98 +0,0 @@
|
||||
from logging import getLogger
|
||||
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from mealie.db.data_access_layer.group_access_model import GroupDataAccessModel
|
||||
from mealie.db.data_access_layer.meal_access_model import MealDataAccessModel
|
||||
from mealie.db.models.event import Event, EventNotification
|
||||
from mealie.db.models.group import Group, GroupMealPlan
|
||||
from mealie.db.models.group.cookbook import CookBook
|
||||
from mealie.db.models.group.invite_tokens import GroupInviteToken
|
||||
from mealie.db.models.group.preferences import GroupPreferencesModel
|
||||
from mealie.db.models.group.shopping_list import ShoppingList
|
||||
from mealie.db.models.group.webhooks import GroupWebhooksModel
|
||||
from mealie.db.models.recipe.category import Category
|
||||
from mealie.db.models.recipe.comment import RecipeComment
|
||||
from mealie.db.models.recipe.ingredient import IngredientFoodModel, IngredientUnitModel
|
||||
from mealie.db.models.recipe.recipe import RecipeModel, Tag
|
||||
from mealie.db.models.settings import SiteSettings
|
||||
from mealie.db.models.sign_up import SignUp
|
||||
from mealie.db.models.users import LongLiveToken, User
|
||||
from mealie.schema.admin import SiteSettings as SiteSettingsSchema
|
||||
from mealie.schema.cookbook import ReadCookBook
|
||||
from mealie.schema.events import Event as EventSchema
|
||||
from mealie.schema.events import EventNotificationIn
|
||||
from mealie.schema.group.group_preferences import ReadGroupPreferences
|
||||
from mealie.schema.group.invite_token import ReadInviteToken
|
||||
from mealie.schema.group.webhook import ReadWebhook
|
||||
from mealie.schema.meal_plan import ShoppingListOut
|
||||
from mealie.schema.meal_plan.new_meal import ReadPlanEntry
|
||||
from mealie.schema.recipe import (
|
||||
CommentOut,
|
||||
IngredientFood,
|
||||
IngredientUnit,
|
||||
Recipe,
|
||||
RecipeCategoryResponse,
|
||||
RecipeTagResponse,
|
||||
)
|
||||
from mealie.schema.user import GroupInDB, LongLiveTokenInDB, PrivateUser, SignUpOut
|
||||
|
||||
from ._base_access_model import BaseAccessModel
|
||||
from .recipe_access_model import RecipeDataAccessModel
|
||||
from .user_access_model import UserDataAccessModel
|
||||
|
||||
logger = getLogger()
|
||||
|
||||
|
||||
pk_id = "id"
|
||||
pk_slug = "slug"
|
||||
pk_token = "token"
|
||||
|
||||
|
||||
class CategoryDataAccessModel(BaseAccessModel):
|
||||
def get_empty(self, session: Session):
|
||||
return session.query(Category).filter(~Category.recipes.any()).all()
|
||||
|
||||
|
||||
class TagsDataAccessModel(BaseAccessModel):
|
||||
def get_empty(self, session: Session):
|
||||
return session.query(Tag).filter(~Tag.recipes.any()).all()
|
||||
|
||||
|
||||
class DatabaseAccessLayer:
|
||||
def __init__(self) -> None:
|
||||
"""
|
||||
`DatabaseAccessLayer` class is the data access layer for all database actions within
|
||||
Mealie. Database uses composition from classes derived from BaseAccessModel. These
|
||||
can be substantiated from the BaseAccessModel class or through inheritance when
|
||||
additional methods are required.
|
||||
"""
|
||||
|
||||
# Recipes
|
||||
self.recipes = RecipeDataAccessModel(pk_slug, RecipeModel, Recipe)
|
||||
self.ingredient_foods = BaseAccessModel(pk_id, IngredientFoodModel, IngredientFood)
|
||||
self.ingredient_units = BaseAccessModel(pk_id, IngredientUnitModel, IngredientUnit)
|
||||
self.comments = BaseAccessModel(pk_id, RecipeComment, CommentOut)
|
||||
|
||||
# Tags and Categories
|
||||
self.categories = CategoryDataAccessModel(pk_slug, Category, RecipeCategoryResponse)
|
||||
self.tags = TagsDataAccessModel(pk_slug, Tag, RecipeTagResponse)
|
||||
|
||||
# Site
|
||||
self.settings = BaseAccessModel(pk_id, SiteSettings, SiteSettingsSchema)
|
||||
self.sign_ups = BaseAccessModel(pk_token, SignUp, SignUpOut)
|
||||
self.event_notifications = BaseAccessModel(pk_id, EventNotification, EventNotificationIn)
|
||||
self.events = BaseAccessModel(pk_id, Event, EventSchema)
|
||||
|
||||
# Users
|
||||
self.users = UserDataAccessModel(pk_id, User, PrivateUser)
|
||||
self.api_tokens = BaseAccessModel(pk_id, LongLiveToken, LongLiveTokenInDB)
|
||||
|
||||
# Group Data
|
||||
self.groups = GroupDataAccessModel(pk_id, Group, GroupInDB)
|
||||
self.group_tokens = BaseAccessModel("token", GroupInviteToken, ReadInviteToken)
|
||||
self.meals = MealDataAccessModel(pk_id, GroupMealPlan, ReadPlanEntry)
|
||||
self.webhooks = BaseAccessModel(pk_id, GroupWebhooksModel, ReadWebhook)
|
||||
self.shopping_lists = BaseAccessModel(pk_id, ShoppingList, ShoppingListOut)
|
||||
self.cookbooks = BaseAccessModel(pk_id, CookBook, ReadCookBook)
|
||||
self.group_preferences = BaseAccessModel("group_id", GroupPreferencesModel, ReadGroupPreferences)
|
||||
@@ -4,10 +4,10 @@ from mealie.db.models.group import Group
|
||||
from mealie.schema.meal_plan.meal import MealPlanOut
|
||||
from mealie.schema.user.user import GroupInDB
|
||||
|
||||
from ._base_access_model import BaseAccessModel
|
||||
from ._access_model import AccessModel
|
||||
|
||||
|
||||
class GroupDataAccessModel(BaseAccessModel[GroupInDB, Group]):
|
||||
class GroupDataAccessModel(AccessModel[GroupInDB, Group]):
|
||||
def get_meals(self, session: Session, match_value: str, match_key: str = "name") -> list[MealPlanOut]:
|
||||
"""A Helper function to get the group from the database and return a sorted list of
|
||||
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
from datetime import date
|
||||
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from mealie.db.models.group import GroupMealPlan
|
||||
from mealie.schema.meal_plan.new_meal import ReadPlanEntry
|
||||
|
||||
from ._base_access_model import BaseAccessModel
|
||||
from ._access_model import AccessModel
|
||||
|
||||
|
||||
class MealDataAccessModel(BaseAccessModel[ReadPlanEntry, GroupMealPlan]):
|
||||
def get_slice(self, session: Session, start: date, end: date, group_id: int) -> list[ReadPlanEntry]:
|
||||
class MealDataAccessModel(AccessModel[ReadPlanEntry, GroupMealPlan]):
|
||||
def get_slice(self, start: date, end: date, group_id: int) -> list[ReadPlanEntry]:
|
||||
start = start.strftime("%Y-%m-%d")
|
||||
end = end.strftime("%Y-%m-%d")
|
||||
qry = session.query(GroupMealPlan).filter(
|
||||
qry = self.session.query(GroupMealPlan).filter(
|
||||
GroupMealPlan.date.between(start, end),
|
||||
GroupMealPlan.group_id == group_id,
|
||||
)
|
||||
|
||||
return [self.schema.from_orm(x) for x in qry.all()]
|
||||
|
||||
def get_today(self, session: Session, group_id: int) -> list[ReadPlanEntry]:
|
||||
def get_today(self, group_id: int) -> list[ReadPlanEntry]:
|
||||
today = date.today()
|
||||
qry = session.query(GroupMealPlan).filter(GroupMealPlan.date == today, GroupMealPlan.group_id == group_id)
|
||||
qry = self.session.query(GroupMealPlan).filter(GroupMealPlan.date == today, GroupMealPlan.group_id == group_id)
|
||||
|
||||
return [self.schema.from_orm(x) for x in qry.all()]
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
from random import randint
|
||||
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from mealie.db.models.recipe.recipe import RecipeModel
|
||||
from mealie.db.models.recipe.settings import RecipeSettings
|
||||
from mealie.schema.recipe import Recipe
|
||||
|
||||
from ._base_access_model import BaseAccessModel
|
||||
from ._access_model import AccessModel
|
||||
|
||||
|
||||
class RecipeDataAccessModel(BaseAccessModel[Recipe, RecipeModel]):
|
||||
def get_all_public(self, session: Session, limit: int = None, order_by: str = None, start=0, override_schema=None):
|
||||
class RecipeDataAccessModel(AccessModel[Recipe, RecipeModel]):
|
||||
def get_all_public(self, limit: int = None, order_by: str = None, start=0, override_schema=None):
|
||||
eff_schema = override_schema or self.schema
|
||||
|
||||
if order_by:
|
||||
@@ -18,7 +16,7 @@ class RecipeDataAccessModel(BaseAccessModel[Recipe, RecipeModel]):
|
||||
|
||||
return [
|
||||
eff_schema.from_orm(x)
|
||||
for x in session.query(self.sql_model)
|
||||
for x in self.session.query(self.sql_model)
|
||||
.join(RecipeSettings)
|
||||
.filter(RecipeSettings.public == True) # noqa: 711
|
||||
.order_by(order_attr.desc())
|
||||
@@ -29,7 +27,7 @@ class RecipeDataAccessModel(BaseAccessModel[Recipe, RecipeModel]):
|
||||
|
||||
return [
|
||||
eff_schema.from_orm(x)
|
||||
for x in session.query(self.sql_model)
|
||||
for x in self.session.query(self.sql_model)
|
||||
.join(RecipeSettings)
|
||||
.filter(RecipeSettings.public == True) # noqa: 711
|
||||
.offset(start)
|
||||
@@ -37,23 +35,25 @@ class RecipeDataAccessModel(BaseAccessModel[Recipe, RecipeModel]):
|
||||
.all()
|
||||
]
|
||||
|
||||
def update_image(self, session: Session, slug: str, _: str = None) -> str:
|
||||
entry: RecipeModel = self._query_one(session, match_value=slug)
|
||||
def update_image(self, slug: str, _: str = None) -> str:
|
||||
entry: RecipeModel = self._query_one(match_value=slug)
|
||||
entry.image = randint(0, 255)
|
||||
session.commit()
|
||||
self.session.commit()
|
||||
|
||||
return entry.image
|
||||
|
||||
def count_uncategorized(self, session: Session, count=True, override_schema=None) -> int:
|
||||
def count_uncategorized(self, count=True, override_schema=None) -> int:
|
||||
return self._count_attribute(
|
||||
session,
|
||||
attribute_name=RecipeModel.recipe_category,
|
||||
attr_match=None,
|
||||
count=count,
|
||||
override_schema=override_schema,
|
||||
)
|
||||
|
||||
def count_untagged(self, session: Session, count=True, override_schema=None) -> int:
|
||||
def count_untagged(self, count=True, override_schema=None) -> int:
|
||||
return self._count_attribute(
|
||||
session, attribute_name=RecipeModel.tags, attr_match=None, count=count, override_schema=override_schema
|
||||
attribute_name=RecipeModel.tags,
|
||||
attr_match=None,
|
||||
count=count,
|
||||
override_schema=override_schema,
|
||||
)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
from mealie.db.models.users import User
|
||||
from mealie.schema.user.user import PrivateUser
|
||||
|
||||
from ._base_access_model import BaseAccessModel
|
||||
from ._access_model import AccessModel
|
||||
|
||||
|
||||
class UserDataAccessModel(BaseAccessModel[PrivateUser, User]):
|
||||
class UserDataAccessModel(AccessModel[PrivateUser, User]):
|
||||
def update_password(self, session, id, password: str):
|
||||
entry = self._query_one(session=session, match_value=id)
|
||||
entry = self._query_one(match_value=id)
|
||||
entry.update_password(password)
|
||||
session.commit()
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from ..data_access_layer import DatabaseAccessLayer
|
||||
from mealie.db.data_access_layer.access_model_factory import Database
|
||||
|
||||
CWD = Path(__file__).parent
|
||||
|
||||
@@ -20,15 +18,15 @@ def get_default_units():
|
||||
return units
|
||||
|
||||
|
||||
def default_recipe_unit_init(db: DatabaseAccessLayer, session: Session) -> None:
|
||||
def default_recipe_unit_init(db: Database) -> None:
|
||||
for unit in get_default_units():
|
||||
try:
|
||||
db.ingredient_units.create(session, unit)
|
||||
db.ingredient_units.create(unit)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
for food in get_default_foods():
|
||||
try:
|
||||
db.ingredient_foods.create(session, food)
|
||||
db.ingredient_foods.create(food)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
from functools import lru_cache
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from .data_access_layer import DatabaseAccessLayer
|
||||
|
||||
db = DatabaseAccessLayer()
|
||||
from .data_access_layer.access_model_factory import Database
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_database():
|
||||
return db
|
||||
def get_database(session: Session):
|
||||
return Database(session)
|
||||
|
||||
@@ -10,7 +10,7 @@ def sql_global_init(db_url: str):
|
||||
if "sqlite" in db_url:
|
||||
connect_args["check_same_thread"] = False
|
||||
|
||||
engine = sa.create_engine(db_url, echo=False, connect_args=connect_args)
|
||||
engine = sa.create_engine(db_url, echo=False, connect_args=connect_args, pool_pre_ping=True)
|
||||
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from mealie.core import root_logger
|
||||
from mealie.core.config import settings
|
||||
from mealie.core.security import hash_password
|
||||
from mealie.db.data_access_layer.access_model_factory import Database
|
||||
from mealie.db.data_initialization.init_units_foods import default_recipe_unit_init
|
||||
from mealie.db.database import db
|
||||
from mealie.db.database import get_database
|
||||
from mealie.db.db_setup import create_session, engine
|
||||
from mealie.db.models._model_base import SqlAlchemyBase
|
||||
from mealie.schema.admin import SiteSettings
|
||||
@@ -21,30 +20,24 @@ def create_all_models():
|
||||
SqlAlchemyBase.metadata.create_all(engine)
|
||||
|
||||
|
||||
def init_db(session: Session = None) -> None:
|
||||
create_all_models()
|
||||
|
||||
if not session:
|
||||
session = create_session()
|
||||
|
||||
with session:
|
||||
default_group_init(session)
|
||||
default_settings_init(session)
|
||||
default_user_init(session)
|
||||
default_recipe_unit_init(db, session)
|
||||
def init_db(db: Database) -> None:
|
||||
default_group_init(db)
|
||||
default_settings_init(db)
|
||||
default_user_init(db)
|
||||
default_recipe_unit_init(db)
|
||||
|
||||
|
||||
def default_settings_init(session: Session):
|
||||
document = db.settings.create(session, SiteSettings().dict())
|
||||
def default_settings_init(db: Database):
|
||||
document = db.settings.create(SiteSettings().dict())
|
||||
logger.info(f"Created Site Settings: \n {document}")
|
||||
|
||||
|
||||
def default_group_init(session: Session):
|
||||
def default_group_init(db: Database):
|
||||
logger.info("Generating Default Group")
|
||||
create_new_group(session, GroupBase(name=settings.DEFAULT_GROUP))
|
||||
create_new_group(db, GroupBase(name=settings.DEFAULT_GROUP))
|
||||
|
||||
|
||||
def default_user_init(session: Session):
|
||||
def default_user_init(db: Database):
|
||||
default_user = {
|
||||
"full_name": "Change Me",
|
||||
"username": "admin",
|
||||
@@ -55,21 +48,26 @@ def default_user_init(session: Session):
|
||||
}
|
||||
|
||||
logger.info("Generating Default User")
|
||||
db.users.create(session, default_user)
|
||||
db.users.create(default_user)
|
||||
|
||||
|
||||
def main():
|
||||
create_all_models()
|
||||
|
||||
session = create_session()
|
||||
db = get_database(session)
|
||||
|
||||
try:
|
||||
session = create_session()
|
||||
init_user = db.users.get(session, "1", "id")
|
||||
init_user = db.users.get("1", "id")
|
||||
except Exception:
|
||||
init_db()
|
||||
init_db(db)
|
||||
return
|
||||
|
||||
if init_user:
|
||||
logger.info("Database Exists")
|
||||
else:
|
||||
logger.info("Database Doesn't Exists, Initializing...")
|
||||
init_db()
|
||||
init_db(db)
|
||||
create_general_event("Initialize Database", "Initialize database with default values", session)
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from sqlalchemy import Column, ForeignKey, Integer, String, orm
|
||||
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, orm
|
||||
|
||||
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
|
||||
|
||||
@@ -11,6 +11,7 @@ class IngredientUnitModel(SqlAlchemyBase, BaseMixins):
|
||||
name = Column(String)
|
||||
description = Column(String)
|
||||
abbreviation = Column(String)
|
||||
fraction = Column(Boolean)
|
||||
ingredients = orm.relationship("RecipeIngredient", back_populates="unit")
|
||||
|
||||
@auto_init()
|
||||
|
||||
Reference in New Issue
Block a user