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:
Hayden
2021-09-19 15:31:34 -08:00
committed by GitHub
parent c0e3f04c23
commit 476aefeeb0
68 changed files with 1131 additions and 1084 deletions

View File

@@ -1 +1 @@
from .db_access import DatabaseAccessLayer
from .access_model_factory import Database

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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