refactor(backend): ♻️ Refactor to make SessionLocal a globally accessible object
This commit is contained in:
parent
a1aad078da
commit
234db39cc7
33 changed files with 360 additions and 397 deletions
1
mealie/db/data_access_layer/__init__.py
Normal file
1
mealie/db/data_access_layer/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
from .db_access import DatabaseAccessLayer
|
|
@ -1,7 +1,7 @@
|
|||
from typing import Union
|
||||
from typing import Callable, Union
|
||||
|
||||
from mealie.core.root_logger import get_logger
|
||||
from mealie.db.models.model_base import SqlAlchemyBase
|
||||
from mealie.db.models._model_base import SqlAlchemyBase
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.orm import load_only
|
||||
|
@ -10,16 +10,25 @@ from sqlalchemy.orm.session import Session
|
|||
logger = get_logger()
|
||||
|
||||
|
||||
class BaseDocument:
|
||||
def __init__(self) -> None:
|
||||
self.primary_key: str
|
||||
self.store: str
|
||||
self.sql_model: SqlAlchemyBase
|
||||
self.schema: BaseModel
|
||||
self.observers: list = None
|
||||
class BaseAccessModel:
|
||||
def __init__(self, primary_key, sql_model, schema) -> None:
|
||||
self.primary_key: str = primary_key
|
||||
self.sql_model: SqlAlchemyBase = sql_model
|
||||
self.schema: BaseModel = schema
|
||||
|
||||
self.observers: list = []
|
||||
|
||||
def subscribe(self, func: Callable) -> None:
|
||||
self.observers.append(func)
|
||||
|
||||
# TODO: Run Observer in Async Background Task
|
||||
def update_observers(self) -> None:
|
||||
if self.observers:
|
||||
for observer in self.observers:
|
||||
observer()
|
||||
|
||||
def get_all(
|
||||
self, session: Session, limit: int = None, order_by: str = None, start=0, end=9999, override_schema=None
|
||||
self, session: Session, limit: int = None, order_by: str = None, start=0, override_schema=None
|
||||
) -> list[dict]:
|
||||
eff_schema = override_schema or self.schema
|
||||
|
||||
|
@ -130,7 +139,7 @@ class BaseDocument:
|
|||
session.add(new_document)
|
||||
session.commit()
|
||||
|
||||
if hasattr(self, "update_observers"):
|
||||
if self.observers:
|
||||
self.update_observers()
|
||||
|
||||
return self.schema.from_orm(new_document)
|
||||
|
@ -150,7 +159,7 @@ class BaseDocument:
|
|||
entry = self._query_one(session=session, match_value=match_value)
|
||||
entry.update(session=session, **new_data)
|
||||
|
||||
if hasattr(self, "update_observers"):
|
||||
if self.observers:
|
||||
self.update_observers()
|
||||
|
||||
session.commit()
|
||||
|
@ -176,7 +185,7 @@ class BaseDocument:
|
|||
session.delete(result)
|
||||
session.commit()
|
||||
|
||||
if hasattr(self, "update_observers"):
|
||||
if self.observers:
|
||||
self.update_observers()
|
||||
|
||||
return results_as_model
|
||||
|
@ -185,7 +194,7 @@ class BaseDocument:
|
|||
session.query(self.sql_model).delete()
|
||||
session.commit()
|
||||
|
||||
if hasattr(self, "update_observers"):
|
||||
if self.observers:
|
||||
self.update_observers()
|
||||
|
||||
def count_all(self, session: Session, match_key=None, match_value=None) -> int:
|
86
mealie/db/data_access_layer/db_access.py
Normal file
86
mealie/db/data_access_layer/db_access.py
Normal file
|
@ -0,0 +1,86 @@
|
|||
from logging import getLogger
|
||||
|
||||
from mealie.db.data_access_layer.group_access_model import GroupDataAccessModel
|
||||
from mealie.db.models.event import Event, EventNotification
|
||||
from mealie.db.models.group import Group
|
||||
from mealie.db.models.mealplan import MealPlan
|
||||
from mealie.db.models.recipe.comment import RecipeComment
|
||||
from mealie.db.models.recipe.ingredient import IngredientFoodModel, IngredientUnitModel
|
||||
from mealie.db.models.recipe.recipe import Category, RecipeModel, Tag
|
||||
from mealie.db.models.settings import CustomPage, SiteSettings
|
||||
from mealie.db.models.shopping_list import ShoppingList
|
||||
from mealie.db.models.sign_up import SignUp
|
||||
from mealie.db.models.theme import SiteThemeModel
|
||||
from mealie.db.models.users import LongLiveToken, User
|
||||
from mealie.schema.admin import CustomPageOut
|
||||
from mealie.schema.admin import SiteSettings as SiteSettingsSchema
|
||||
from mealie.schema.admin import SiteTheme
|
||||
from mealie.schema.events import Event as EventSchema
|
||||
from mealie.schema.events import EventNotificationIn
|
||||
from mealie.schema.meal_plan import MealPlanOut, ShoppingListOut
|
||||
from mealie.schema.recipe import (
|
||||
CommentOut,
|
||||
IngredientFood,
|
||||
IngredientUnit,
|
||||
Recipe,
|
||||
RecipeCategoryResponse,
|
||||
RecipeTagResponse,
|
||||
)
|
||||
from mealie.schema.user import GroupInDB, LongLiveTokenInDB, SignUpOut, UserInDB
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from ._base_access_model import BaseAccessModel
|
||||
from .recipe_access_model import RecipeDataAccessModel
|
||||
from .user_access_model import UserDataAccessModel
|
||||
|
||||
logger = getLogger()
|
||||
|
||||
|
||||
DEFAULT_PK = "id"
|
||||
|
||||
|
||||
class CategoryDataAccessModel(BaseAccessModel):
|
||||
def get_empty(self, session: Session):
|
||||
self.schema
|
||||
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:
|
||||
"""
|
||||
`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.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
|
||||
# Recipes
|
||||
self.recipes = RecipeDataAccessModel("slug", RecipeModel, Recipe)
|
||||
self.ingredient_foods = BaseAccessModel(DEFAULT_PK, IngredientFoodModel, IngredientFood)
|
||||
self.ingredient_units = BaseAccessModel(DEFAULT_PK, IngredientUnitModel, IngredientUnit)
|
||||
self.comments = BaseAccessModel(DEFAULT_PK, RecipeComment, CommentOut)
|
||||
|
||||
# Tags and Categories
|
||||
self.categories = CategoryDataAccessModel("slug", Category, RecipeCategoryResponse)
|
||||
self.tags = TagsDataAccessModel("slug", Tag, RecipeTagResponse)
|
||||
|
||||
# Site
|
||||
self.settings = BaseAccessModel(DEFAULT_PK, SiteSettings, SiteSettingsSchema)
|
||||
self.themes = BaseAccessModel(DEFAULT_PK, SiteThemeModel, SiteTheme)
|
||||
self.sign_ups = BaseAccessModel("token", SignUp, SignUpOut)
|
||||
self.custom_pages = BaseAccessModel(DEFAULT_PK, CustomPage, CustomPageOut)
|
||||
self.event_notifications = BaseAccessModel(DEFAULT_PK, EventNotification, EventNotificationIn)
|
||||
self.events = BaseAccessModel(DEFAULT_PK, Event, EventSchema)
|
||||
|
||||
# Users / Groups
|
||||
self.users = UserDataAccessModel(DEFAULT_PK, User, UserInDB)
|
||||
self.api_tokens = BaseAccessModel(DEFAULT_PK, LongLiveToken, LongLiveTokenInDB)
|
||||
self.groups = GroupDataAccessModel(DEFAULT_PK, Group, GroupInDB)
|
||||
self.meals = BaseAccessModel("uid", MealPlan, MealPlanOut)
|
||||
self.shopping_lists = BaseAccessModel(DEFAULT_PK, ShoppingList, ShoppingListOut)
|
22
mealie/db/data_access_layer/group_access_model.py
Normal file
22
mealie/db/data_access_layer/group_access_model.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
from mealie.schema.meal_plan.meal import MealPlanOut
|
||||
from mealie.schema.user.user import GroupInDB
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from ._base_access_model import BaseAccessModel
|
||||
|
||||
|
||||
class GroupDataAccessModel(BaseAccessModel):
|
||||
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
|
||||
|
||||
Args:
|
||||
session (Session): SqlAlchemy Session
|
||||
match_value (str): Match Value
|
||||
match_key (str, optional): Match Key. Defaults to "name".
|
||||
|
||||
Returns:
|
||||
list[MealPlanOut]: [description]
|
||||
"""
|
||||
group: GroupInDB = session.query(self.sql_model).filter_by(**{match_key: match_value}).one_or_none()
|
||||
|
||||
return group.mealplans
|
57
mealie/db/data_access_layer/recipe_access_model.py
Normal file
57
mealie/db/data_access_layer/recipe_access_model.py
Normal file
|
@ -0,0 +1,57 @@
|
|||
from random import randint
|
||||
|
||||
from mealie.db.models.recipe.recipe import RecipeModel
|
||||
from mealie.db.models.recipe.settings import RecipeSettings
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from ._base_access_model import BaseAccessModel
|
||||
|
||||
|
||||
class RecipeDataAccessModel(BaseAccessModel):
|
||||
def get_all_public(self, session: Session, limit: int = None, order_by: str = None, start=0, override_schema=None):
|
||||
eff_schema = override_schema or self.schema
|
||||
|
||||
if order_by:
|
||||
order_attr = getattr(self.sql_model, str(order_by))
|
||||
|
||||
return [
|
||||
eff_schema.from_orm(x)
|
||||
for x in session.query(self.sql_model)
|
||||
.join(RecipeSettings)
|
||||
.filter(RecipeSettings.public == True) # noqa: 711
|
||||
.order_by(order_attr.desc())
|
||||
.offset(start)
|
||||
.limit(limit)
|
||||
.all()
|
||||
]
|
||||
|
||||
return [
|
||||
eff_schema.from_orm(x)
|
||||
for x in session.query(self.sql_model)
|
||||
.join(RecipeSettings)
|
||||
.filter(RecipeSettings.public == True) # noqa: 711
|
||||
.offset(start)
|
||||
.limit(limit)
|
||||
.all()
|
||||
]
|
||||
|
||||
def update_image(self, session: Session, slug: str, _: str = None) -> str:
|
||||
entry: RecipeModel = self._query_one(session, match_value=slug)
|
||||
entry.image = randint(0, 255)
|
||||
session.commit()
|
||||
|
||||
return entry.image
|
||||
|
||||
def count_uncategorized(self, session: Session, 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:
|
||||
return self._count_attribute(
|
||||
session, attribute_name=RecipeModel.tags, attr_match=None, count=count, override_schema=override_schema
|
||||
)
|
10
mealie/db/data_access_layer/user_access_model.py
Normal file
10
mealie/db/data_access_layer/user_access_model.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
from ._base_access_model import BaseAccessModel
|
||||
|
||||
|
||||
class UserDataAccessModel(BaseAccessModel):
|
||||
def update_password(self, session, id, password: str):
|
||||
entry = self._query_one(session=session, match_value=id)
|
||||
entry.update_password(password)
|
||||
session.commit()
|
||||
|
||||
return self.schema.from_orm(entry)
|
|
@ -1,4 +1,4 @@
|
|||
from mealie.schema.recipe.recipe import IngredientUnit
|
||||
from mealie.schema.recipe.units_and_foods import CreateIngredientUnit
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from ..data_access_layer import DatabaseAccessLayer
|
||||
|
@ -7,21 +7,21 @@ from ..data_access_layer import DatabaseAccessLayer
|
|||
def get_default_units():
|
||||
return [
|
||||
# Volume
|
||||
IngredientUnit(name="teaspoon", abbreviation="tsp"),
|
||||
IngredientUnit(name="tablespoon", abbreviation="tbsp"),
|
||||
IngredientUnit(name="fluid ounce", abbreviation="fl oz"),
|
||||
IngredientUnit(name="cup", abbreviation="cup"),
|
||||
IngredientUnit(name="pint", abbreviation="pt"),
|
||||
IngredientUnit(name="quart", abbreviation="qt"),
|
||||
IngredientUnit(name="gallon", abbreviation="gal"),
|
||||
IngredientUnit(name="milliliter", abbreviation="ml"),
|
||||
IngredientUnit(name="liter", abbreviation="l"),
|
||||
CreateIngredientUnit(name="teaspoon", abbreviation="tsp"),
|
||||
CreateIngredientUnit(name="tablespoon", abbreviation="tbsp"),
|
||||
CreateIngredientUnit(name="fluid ounce", abbreviation="fl oz"),
|
||||
CreateIngredientUnit(name="cup", abbreviation="cup"),
|
||||
CreateIngredientUnit(name="pint", abbreviation="pt"),
|
||||
CreateIngredientUnit(name="quart", abbreviation="qt"),
|
||||
CreateIngredientUnit(name="gallon", abbreviation="gal"),
|
||||
CreateIngredientUnit(name="milliliter", abbreviation="ml"),
|
||||
CreateIngredientUnit(name="liter", abbreviation="l"),
|
||||
# Mass Weight
|
||||
IngredientUnit(name="pound", abbreviation="lb"),
|
||||
IngredientUnit(name="ounce", abbreviation="oz"),
|
||||
IngredientUnit(name="gram", abbreviation="g"),
|
||||
IngredientUnit(name="kilogram", abbreviation="kg"),
|
||||
IngredientUnit(name="milligram", abbreviation="mg"),
|
||||
CreateIngredientUnit(name="pound", abbreviation="lb"),
|
||||
CreateIngredientUnit(name="ounce", abbreviation="oz"),
|
||||
CreateIngredientUnit(name="gram", abbreviation="g"),
|
||||
CreateIngredientUnit(name="kilogram", abbreviation="kg"),
|
||||
CreateIngredientUnit(name="milligram", abbreviation="mg"),
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -1,268 +1,3 @@
|
|||
from logging import getLogger
|
||||
from random import randint
|
||||
from typing import Callable
|
||||
from .data_access_layer import DatabaseAccessLayer
|
||||
|
||||
from mealie.db.db_base import BaseDocument
|
||||
from mealie.db.models.event import Event, EventNotification
|
||||
from mealie.db.models.group import Group
|
||||
from mealie.db.models.mealplan import MealPlan
|
||||
from mealie.db.models.recipe.comment import RecipeComment
|
||||
from mealie.db.models.recipe.ingredient import IngredientFood, IngredientUnit
|
||||
from mealie.db.models.recipe.recipe import Category, RecipeModel, Tag
|
||||
from mealie.db.models.recipe.settings import RecipeSettings
|
||||
from mealie.db.models.settings import CustomPage, SiteSettings
|
||||
from mealie.db.models.shopping_list import ShoppingList
|
||||
from mealie.db.models.sign_up import SignUp
|
||||
from mealie.db.models.theme import SiteThemeModel
|
||||
from mealie.db.models.users import LongLiveToken, User
|
||||
from mealie.schema.admin import CustomPageOut
|
||||
from mealie.schema.admin import SiteSettings as SiteSettingsSchema
|
||||
from mealie.schema.admin import SiteTheme
|
||||
from mealie.schema.events import Event as EventSchema
|
||||
from mealie.schema.events import EventNotificationIn
|
||||
from mealie.schema.meal_plan import MealPlanOut, ShoppingListOut
|
||||
from mealie.schema.recipe import (
|
||||
CommentOut,
|
||||
Recipe,
|
||||
RecipeCategoryResponse,
|
||||
RecipeIngredientFood,
|
||||
RecipeIngredientUnit,
|
||||
RecipeTagResponse,
|
||||
)
|
||||
from mealie.schema.user import GroupInDB, LongLiveTokenInDB, SignUpOut, UserInDB
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
logger = getLogger()
|
||||
|
||||
|
||||
class _Recipes(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "slug"
|
||||
self.sql_model: RecipeModel = RecipeModel
|
||||
self.schema: Recipe = Recipe
|
||||
|
||||
self.observers = []
|
||||
|
||||
def get_all_public(self, session: Session, limit: int = None, order_by: str = None, start=0, override_schema=None):
|
||||
eff_schema = override_schema or self.schema
|
||||
|
||||
if order_by:
|
||||
order_attr = getattr(self.sql_model, str(order_by))
|
||||
|
||||
return [
|
||||
eff_schema.from_orm(x)
|
||||
for x in session.query(self.sql_model)
|
||||
.join(RecipeSettings)
|
||||
.filter(RecipeSettings.public == True) # noqa: 711
|
||||
.order_by(order_attr.desc())
|
||||
.offset(start)
|
||||
.limit(limit)
|
||||
.all()
|
||||
]
|
||||
|
||||
return [
|
||||
eff_schema.from_orm(x)
|
||||
for x in session.query(self.sql_model)
|
||||
.join(RecipeSettings)
|
||||
.filter(RecipeSettings.public == True) # noqa: 711
|
||||
.offset(start)
|
||||
.limit(limit)
|
||||
.all()
|
||||
]
|
||||
|
||||
def update_image(self, session: Session, slug: str, _: str = None) -> str:
|
||||
entry: RecipeModel = self._query_one(session, match_value=slug)
|
||||
entry.image = randint(0, 255)
|
||||
session.commit()
|
||||
|
||||
return entry.image
|
||||
|
||||
def count_uncategorized(self, session: Session, 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:
|
||||
return self._count_attribute(
|
||||
session, attribute_name=RecipeModel.tags, attr_match=None, count=count, override_schema=override_schema
|
||||
)
|
||||
|
||||
def subscribe(self, func: Callable) -> None:
|
||||
self.observers.append(func)
|
||||
|
||||
def update_observers(self) -> None:
|
||||
for observer in self.observers:
|
||||
observer()
|
||||
|
||||
|
||||
class _IngredientFoods(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "id"
|
||||
self.sql_model = IngredientFood
|
||||
self.schema = RecipeIngredientFood
|
||||
|
||||
|
||||
class _IngredientUnits(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "id"
|
||||
self.sql_model = IngredientUnit
|
||||
self.schema = RecipeIngredientUnit
|
||||
|
||||
|
||||
class _Categories(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "slug"
|
||||
self.sql_model = Category
|
||||
self.schema = RecipeCategoryResponse
|
||||
|
||||
def get_empty(self, session: Session):
|
||||
return session.query(Category).filter(~Category.recipes.any()).all()
|
||||
|
||||
|
||||
class _Tags(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "slug"
|
||||
self.sql_model = Tag
|
||||
self.schema = RecipeTagResponse
|
||||
|
||||
def get_empty(self, session: Session):
|
||||
return session.query(Tag).filter(~Tag.recipes.any()).all()
|
||||
|
||||
|
||||
class _Meals(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "uid"
|
||||
self.sql_model = MealPlan
|
||||
self.schema = MealPlanOut
|
||||
|
||||
|
||||
class _Settings(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "id"
|
||||
self.sql_model = SiteSettings
|
||||
self.schema = SiteSettingsSchema
|
||||
|
||||
|
||||
class _Themes(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "id"
|
||||
self.sql_model = SiteThemeModel
|
||||
self.schema = SiteTheme
|
||||
|
||||
|
||||
class _Users(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "id"
|
||||
self.sql_model = User
|
||||
self.schema = UserInDB
|
||||
|
||||
def update_password(self, session, id, password: str):
|
||||
entry = self._query_one(session=session, match_value=id)
|
||||
entry.update_password(password)
|
||||
session.commit()
|
||||
|
||||
return self.schema.from_orm(entry)
|
||||
|
||||
|
||||
class _Comments(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "id"
|
||||
self.sql_model = RecipeComment
|
||||
self.schema = CommentOut
|
||||
|
||||
|
||||
class _LongLiveToken(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "id"
|
||||
self.sql_model = LongLiveToken
|
||||
self.schema = LongLiveTokenInDB
|
||||
|
||||
|
||||
class _Groups(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "id"
|
||||
self.sql_model = Group
|
||||
self.schema = GroupInDB
|
||||
|
||||
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
|
||||
|
||||
Args:
|
||||
session (Session): SqlAlchemy Session
|
||||
match_value (str): Match Value
|
||||
match_key (str, optional): Match Key. Defaults to "name".
|
||||
|
||||
Returns:
|
||||
list[MealPlanOut]: [description]
|
||||
"""
|
||||
group: GroupInDB = session.query(self.sql_model).filter_by(**{match_key: match_value}).one_or_none()
|
||||
|
||||
return group.mealplans
|
||||
|
||||
|
||||
class _ShoppingList(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "id"
|
||||
self.sql_model = ShoppingList
|
||||
self.schema = ShoppingListOut
|
||||
|
||||
|
||||
class _SignUps(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "token"
|
||||
self.sql_model = SignUp
|
||||
self.schema = SignUpOut
|
||||
|
||||
|
||||
class _CustomPages(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "id"
|
||||
self.sql_model = CustomPage
|
||||
self.schema = CustomPageOut
|
||||
|
||||
|
||||
class _Events(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "id"
|
||||
self.sql_model = Event
|
||||
self.schema = EventSchema
|
||||
|
||||
|
||||
class _EventNotification(BaseDocument):
|
||||
def __init__(self) -> None:
|
||||
self.primary_key = "id"
|
||||
self.sql_model = EventNotification
|
||||
self.schema = EventNotificationIn
|
||||
|
||||
|
||||
class Database:
|
||||
def __init__(self) -> None:
|
||||
# Recipes
|
||||
self.recipes = _Recipes()
|
||||
self.ingredient_foods = _IngredientUnits()
|
||||
self.ingredient_units = _IngredientFoods()
|
||||
self.categories = _Categories()
|
||||
self.tags = _Tags()
|
||||
self.comments = _Comments()
|
||||
|
||||
# Site
|
||||
self.settings = _Settings()
|
||||
self.themes = _Themes()
|
||||
self.sign_ups = _SignUps()
|
||||
self.custom_pages = _CustomPages()
|
||||
self.event_notifications = _EventNotification()
|
||||
self.events = _Events()
|
||||
|
||||
# Users / Groups
|
||||
self.users = _Users()
|
||||
self.api_tokens = _LongLiveToken()
|
||||
self.groups = _Groups()
|
||||
self.meals = _Meals()
|
||||
self.shopping_lists = _ShoppingList()
|
||||
|
||||
|
||||
db = Database()
|
||||
db = DatabaseAccessLayer()
|
||||
|
|
|
@ -1,8 +1,22 @@
|
|||
import sqlalchemy as sa
|
||||
from mealie.core.config import settings
|
||||
from mealie.db.models.db_session import sql_global_init
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
SessionLocal = sql_global_init(settings.DB_URL)
|
||||
|
||||
def sql_global_init(db_url: str):
|
||||
connect_args = {}
|
||||
if "sqlite" in db_url:
|
||||
connect_args["check_same_thread"] = False
|
||||
|
||||
engine = sa.create_engine(db_url, echo=False, connect_args=connect_args)
|
||||
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
return SessionLocal, engine
|
||||
|
||||
|
||||
SessionLocal, engine = sql_global_init(settings.DB_URL)
|
||||
|
||||
|
||||
def create_session() -> Session:
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
from mealie.core import root_logger
|
||||
from mealie.core.config import settings
|
||||
from mealie.core.security import get_password_hash
|
||||
from mealie.db.data_initialization.init_units_foods import default_recipe_unit_init
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import create_session
|
||||
from mealie.db.db_setup import create_session, engine
|
||||
from mealie.db.models._model_base import SqlAlchemyBase
|
||||
from mealie.schema.admin import SiteSettings, SiteTheme
|
||||
from mealie.services.events import create_general_event
|
||||
from sqlalchemy.orm import Session
|
||||
|
@ -10,16 +12,26 @@ from sqlalchemy.orm import Session
|
|||
logger = root_logger.get_logger("init_db")
|
||||
|
||||
|
||||
def init_db(db: Session = None) -> None:
|
||||
if not db:
|
||||
db = create_session()
|
||||
def create_all_models():
|
||||
import mealie.db.models._all_models # noqa: F401
|
||||
|
||||
default_group_init(db)
|
||||
default_settings_init(db)
|
||||
default_theme_init(db)
|
||||
default_user_init(db)
|
||||
SqlAlchemyBase.metadata.create_all(engine)
|
||||
|
||||
db.close()
|
||||
|
||||
def init_db(session: Session = None) -> None:
|
||||
create_all_models()
|
||||
|
||||
if not session:
|
||||
session = create_session()
|
||||
|
||||
default_group_init(session)
|
||||
default_settings_init(session)
|
||||
default_theme_init(session)
|
||||
default_user_init(session)
|
||||
|
||||
default_recipe_unit_init(db, session)
|
||||
|
||||
session.close()
|
||||
|
||||
|
||||
def default_theme_init(session: Session):
|
||||
|
@ -67,8 +79,12 @@ def default_user_init(session: Session):
|
|||
|
||||
|
||||
def main():
|
||||
session = create_session()
|
||||
init_user = db.users.get(session, "1", "id")
|
||||
try:
|
||||
session = create_session()
|
||||
init_user = db.users.get(session, "1", "id")
|
||||
except Exception:
|
||||
init_db()
|
||||
return
|
||||
if init_user:
|
||||
logger.info("Database Exists")
|
||||
else:
|
||||
|
|
52
mealie/db/models/_model_base.py
Normal file
52
mealie/db/models/_model_base.py
Normal file
|
@ -0,0 +1,52 @@
|
|||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from mealie.db.db_setup import SessionLocal
|
||||
from sqlalchemy import Column, DateTime, Integer
|
||||
from sqlalchemy.ext.declarative import as_declarative
|
||||
from sqlalchemy.orm import declarative_base
|
||||
|
||||
|
||||
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()
|
||||
class Base:
|
||||
id = Column(Integer, primary_key=True)
|
||||
created_at = Column(DateTime, default=datetime.now())
|
||||
|
||||
# @declared_attr
|
||||
# def __tablename__(cls):
|
||||
# return cls.__name__.lower()
|
||||
|
||||
|
||||
class BaseMixins:
|
||||
"""
|
||||
`self.update` method which directly passing arugments to the `__init__`
|
||||
`cls.get_ref` method which will return the object from the database or none. Useful for many-to-many relationships.
|
||||
"""
|
||||
|
||||
class Config:
|
||||
get_attr = "id"
|
||||
|
||||
def update(self, *args, **kwarg):
|
||||
self.__init__(*args, **kwarg)
|
||||
|
||||
@classmethod
|
||||
def get_ref(cls, match_value: str, match_attr: str = None):
|
||||
match_attr = match_attr = cls.Config.get_attr
|
||||
|
||||
if match_value is None:
|
||||
return None
|
||||
|
||||
with SessionLocal() as session:
|
||||
eff_ref = getattr(cls, match_attr)
|
||||
return session.query(cls).filter(eff_ref == match_value).one_or_none()
|
||||
|
||||
|
||||
SqlAlchemyBase = declarative_base(cls=Base, constructor=None)
|
|
@ -1,19 +0,0 @@
|
|||
import sqlalchemy as sa
|
||||
from mealie.db.models.model_base import SqlAlchemyBase
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
|
||||
def sql_global_init(db_url: str):
|
||||
connect_args = {}
|
||||
if "sqlite" in db_url:
|
||||
connect_args["check_same_thread"] = False
|
||||
|
||||
engine = sa.create_engine(db_url, echo=False, connect_args=connect_args)
|
||||
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
import mealie.db.models._all_models # noqa: F401
|
||||
|
||||
SqlAlchemyBase.metadata.create_all(engine)
|
||||
|
||||
return SessionLocal
|
|
@ -1,4 +1,4 @@
|
|||
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
|
||||
from sqlalchemy import Boolean, Column, DateTime, Integer, String
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import sqlalchemy as sa
|
||||
import sqlalchemy.orm as orm
|
||||
from mealie.core.config import settings
|
||||
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
|
||||
from mealie.db.models.recipe.category import Category, group2categories
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import sqlalchemy.orm as orm
|
||||
from mealie.db.models.group import Group
|
||||
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
|
||||
from mealie.db.models.recipe.recipe import RecipeModel
|
||||
from mealie.db.models.shopping_list import ShoppingList
|
||||
from sqlalchemy import Column, Date, ForeignKey, Integer, String
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
import sqlalchemy.ext.declarative as dec
|
||||
from requests import Session
|
||||
|
||||
SqlAlchemyBase = dec.declarative_base()
|
||||
|
||||
|
||||
class BaseMixins:
|
||||
def update(self, *args, **kwarg):
|
||||
self.__init__(*args, **kwarg)
|
||||
|
||||
@classmethod
|
||||
def get_ref(cls_type, session: Session, match_value: str, match_attr: str = "id"):
|
||||
eff_ref = getattr(cls_type, match_attr)
|
||||
return session.query(cls_type).filter(eff_ref == match_value).one_or_none()
|
|
@ -1,5 +1,5 @@
|
|||
import sqlalchemy as sa
|
||||
from mealie.db.models.model_base import SqlAlchemyBase
|
||||
from mealie.db.models._model_base import SqlAlchemyBase
|
||||
|
||||
|
||||
class ApiExtras(SqlAlchemyBase):
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import sqlalchemy as sa
|
||||
from mealie.db.models.model_base import SqlAlchemyBase
|
||||
from mealie.db.models._model_base import SqlAlchemyBase
|
||||
|
||||
|
||||
class RecipeAsset(SqlAlchemyBase):
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import sqlalchemy as sa
|
||||
import sqlalchemy.orm as orm
|
||||
from mealie.core import root_logger
|
||||
from mealie.db.models.model_base import SqlAlchemyBase
|
||||
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
|
||||
from slugify import slugify
|
||||
from sqlalchemy.orm import validates
|
||||
|
||||
|
@ -36,29 +36,25 @@ custom_pages2categories = sa.Table(
|
|||
)
|
||||
|
||||
|
||||
class Category(SqlAlchemyBase):
|
||||
class Category(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "categories"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
name = sa.Column(sa.String, index=True, nullable=False)
|
||||
slug = sa.Column(sa.String, index=True, unique=True, nullable=False)
|
||||
recipes = orm.relationship("RecipeModel", secondary=recipes2categories, back_populates="recipe_category")
|
||||
|
||||
class Config:
|
||||
get_attr = "slug"
|
||||
|
||||
@validates("name")
|
||||
def validate_name(self, key, name):
|
||||
assert name != ""
|
||||
return name
|
||||
|
||||
def __init__(self, name, session=None) -> None:
|
||||
def __init__(self, name, **_) -> None:
|
||||
self.name = name.strip()
|
||||
self.slug = slugify(name)
|
||||
|
||||
def update(self, name, session=None) -> None:
|
||||
self.__init__(name, session)
|
||||
|
||||
@staticmethod
|
||||
def get_ref(session, slug: str):
|
||||
return session.query(Category).filter(Category.slug == slug).one()
|
||||
|
||||
@staticmethod
|
||||
def create_if_not_exist(session, name: str = None):
|
||||
test_slug = slugify(name)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from datetime import datetime
|
||||
from uuid import uuid4
|
||||
|
||||
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
|
||||
from mealie.db.models.recipe.recipe import RecipeModel
|
||||
from mealie.db.models.users import User
|
||||
from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, orm
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
|
||||
from requests import Session
|
||||
from sqlalchemy import Column, ForeignKey, Integer, String, Table, orm
|
||||
|
||||
|
@ -44,7 +44,7 @@ class IngredientFoodModel(SqlAlchemyBase, BaseMixins):
|
|||
pass
|
||||
|
||||
|
||||
class RecipeIngredient(SqlAlchemyBase):
|
||||
class RecipeIngredient(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "recipes_ingredients"
|
||||
id = Column(Integer, primary_key=True)
|
||||
position = Column(Integer)
|
||||
|
@ -63,6 +63,10 @@ class RecipeIngredient(SqlAlchemyBase):
|
|||
def __init__(self, title: str, note: str, unit: dict, food: dict, quantity: int, session: Session, **_) -> None:
|
||||
self.title = title
|
||||
self.note = note
|
||||
self.unit = IngredientUnitModel.get_ref_or_create(session, unit)
|
||||
self.food = IngredientFoodModel.get_ref_or_create(session, food)
|
||||
self.quantity = quantity
|
||||
|
||||
if unit:
|
||||
self.unit = IngredientUnitModel.get_ref(unit.get("id"))
|
||||
|
||||
if food:
|
||||
self.food = IngredientFoodModel.get_ref(unit.get("id"))
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from mealie.db.models.model_base import SqlAlchemyBase
|
||||
from mealie.db.models._model_base import SqlAlchemyBase
|
||||
from sqlalchemy import Column, ForeignKey, Integer, String
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import sqlalchemy as sa
|
||||
from mealie.db.models.model_base import SqlAlchemyBase
|
||||
from mealie.db.models._model_base import SqlAlchemyBase
|
||||
|
||||
|
||||
class Note(SqlAlchemyBase):
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import sqlalchemy as sa
|
||||
from mealie.db.models.model_base import SqlAlchemyBase
|
||||
from mealie.db.models._model_base import SqlAlchemyBase
|
||||
|
||||
|
||||
class Nutrition(SqlAlchemyBase):
|
||||
|
|
|
@ -3,26 +3,24 @@ from datetime import date
|
|||
|
||||
import sqlalchemy as sa
|
||||
import sqlalchemy.orm as orm
|
||||
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||
from mealie.db.models.recipe.api_extras import ApiExtras
|
||||
from mealie.db.models.recipe.assets import RecipeAsset
|
||||
from mealie.db.models.recipe.category import Category, recipes2categories
|
||||
from mealie.db.models.recipe.ingredient import RecipeIngredient
|
||||
from mealie.db.models.recipe.instruction import RecipeInstruction
|
||||
from mealie.db.models.recipe.note import Note
|
||||
from mealie.db.models.recipe.nutrition import Nutrition
|
||||
from mealie.db.models.recipe.settings import RecipeSettings
|
||||
from mealie.db.models.recipe.tag import Tag, recipes2tags
|
||||
from mealie.db.models.recipe.tool import Tool
|
||||
from sqlalchemy.ext.orderinglist import ordering_list
|
||||
from sqlalchemy.orm import validates
|
||||
|
||||
from .._model_base import BaseMixins, SqlAlchemyBase
|
||||
from .api_extras import ApiExtras
|
||||
from .assets import RecipeAsset
|
||||
from .category import Category, recipes2categories
|
||||
from .ingredient import RecipeIngredient
|
||||
from .instruction import RecipeInstruction
|
||||
from .note import Note
|
||||
from .nutrition import Nutrition
|
||||
from .settings import RecipeSettings
|
||||
from .tag import Tag, recipes2tags
|
||||
from .tool import Tool
|
||||
|
||||
|
||||
class RecipeModel(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "recipes"
|
||||
# Database Specific
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
|
||||
# General Recipe Properties
|
||||
name = sa.Column(sa.String, nullable=False)
|
||||
description = sa.Column(sa.String)
|
||||
|
@ -128,11 +126,11 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
|||
self.perform_time = perform_time
|
||||
self.cook_time = cook_time
|
||||
|
||||
self.recipe_category = [Category.create_if_not_exist(session=session, name=cat) for cat in recipe_category]
|
||||
self.recipe_category = [x for x in [Category.get_ref(cat) for cat in recipe_category] if x]
|
||||
|
||||
# Mealie Specific
|
||||
self.settings = RecipeSettings(**settings) if settings else RecipeSettings()
|
||||
self.tags = [Tag.create_if_not_exist(session=session, name=tag) for tag in tags]
|
||||
self.tags = [x for x in [Tag.get_ref(tag) for tag in tags] if x]
|
||||
self.slug = slug
|
||||
self.notes = [Note(**note) for note in notes]
|
||||
self.rating = rating
|
||||
|
@ -142,7 +140,3 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
|||
# Time Stampes
|
||||
self.date_added = date_added
|
||||
self.date_updated = datetime.datetime.now()
|
||||
|
||||
def update(self, **_):
|
||||
"""Updated a database entry by removing nested rows and rebuilds the row through the __init__ functions"""
|
||||
self.__init__(**_)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import sqlalchemy as sa
|
||||
from mealie.db.models.model_base import SqlAlchemyBase
|
||||
from mealie.db.models._model_base import SqlAlchemyBase
|
||||
|
||||
|
||||
class RecipeSettings(SqlAlchemyBase):
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import sqlalchemy as sa
|
||||
import sqlalchemy.orm as orm
|
||||
from mealie.core import root_logger
|
||||
from mealie.db.models.model_base import SqlAlchemyBase
|
||||
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
|
||||
from slugify import slugify
|
||||
from sqlalchemy.orm import validates
|
||||
|
||||
|
@ -15,13 +15,16 @@ recipes2tags = sa.Table(
|
|||
)
|
||||
|
||||
|
||||
class Tag(SqlAlchemyBase):
|
||||
class Tag(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "tags"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
name = sa.Column(sa.String, index=True, nullable=False)
|
||||
slug = sa.Column(sa.String, index=True, unique=True, nullable=False)
|
||||
recipes = orm.relationship("RecipeModel", secondary=recipes2tags, back_populates="tags")
|
||||
|
||||
class Config:
|
||||
get_attr = "slug"
|
||||
|
||||
@validates("name")
|
||||
def validate_name(self, key, name):
|
||||
assert name != ""
|
||||
|
@ -31,9 +34,6 @@ class Tag(SqlAlchemyBase):
|
|||
self.name = name.strip()
|
||||
self.slug = slugify(self.name)
|
||||
|
||||
def update(self, name, session=None) -> None:
|
||||
self.__init__(name, session)
|
||||
|
||||
@staticmethod
|
||||
def create_if_not_exist(session, name: str = None):
|
||||
test_slug = slugify(name)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import sqlalchemy as sa
|
||||
from mealie.db.models.model_base import SqlAlchemyBase
|
||||
from mealie.db.models._model_base import SqlAlchemyBase
|
||||
|
||||
|
||||
class Tool(SqlAlchemyBase):
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import sqlalchemy as sa
|
||||
import sqlalchemy.orm as orm
|
||||
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
|
||||
from mealie.db.models.recipe.category import Category, custom_pages2categories, site_settings2categories
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import sqlalchemy.orm as orm
|
||||
from mealie.db.models.group import Group
|
||||
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
|
||||
from requests import Session
|
||||
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
|
||||
from sqlalchemy.ext.orderinglist import ordering_list
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
|
||||
from sqlalchemy import Boolean, Column, Integer, String
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import sqlalchemy.orm as orm
|
||||
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
|
||||
from sqlalchemy import Column, ForeignKey, Integer, String
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from mealie.core.config import settings
|
||||
from mealie.db.models.group import Group
|
||||
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
|
||||
from mealie.db.models.recipe.recipe import RecipeModel
|
||||
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, orm
|
||||
|
||||
|
|
Loading…
Reference in a new issue