refactor: move dependencies to controllers (#1550)

* Moves dependencies directly to controllers
* Reduces use of @cached_property - (I have a suspicion that this is a factor in memory usage)
* reduce duplicate ways to access the same property on a controller.
This commit is contained in:
Hayden 2022-08-11 20:13:22 -08:00 committed by GitHub
parent 8a98288248
commit 71d3db7aef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 183 additions and 215 deletions

View File

@ -1,4 +1,3 @@
from .base_controllers import *
from .controller import *
from .dependencies import *
from .mixins import *

View File

@ -1,70 +1,107 @@
from abc import ABC
from functools import cached_property
from logging import Logger
from fastapi import Depends
from pydantic import UUID4
from sqlalchemy.orm import Session
from mealie.core.config import get_app_dirs, get_app_settings
from mealie.core.dependencies.dependencies import get_admin_user, get_current_user
from mealie.core.exceptions import mealie_registered_exceptions
from mealie.core.root_logger import get_logger
from mealie.core.settings.directories import AppDirectories
from mealie.core.settings.settings import AppSettings
from mealie.db.db_setup import generate_session
from mealie.lang import local_provider
from mealie.lang.providers import Translator
from mealie.repos.all_repositories import AllRepositories
from mealie.routes._base.checks import OperationChecks
from mealie.routes._base.dependencies import SharedDependencies
from mealie.schema.user.user import GroupInDB, PrivateUser
class BasePublicController(ABC):
class _BaseController(ABC):
session: Session = Depends(generate_session)
translator: Translator = Depends(local_provider)
_repos: AllRepositories | None
_logger: Logger | None
_settings: AppSettings | None
_folders: AppDirectories | None
@property
def t(self):
return self.translator.t if self.translator else local_provider().t
@property
def repos(self):
if not self._repos:
self._repos = AllRepositories(self.session)
return self._repos
@property
def logger(self) -> Logger:
if not self._logger:
self._logger = get_logger()
return self._logger
@property
def settings(self) -> AppSettings:
if not self._settings:
self._settings = get_app_settings()
return self._settings
@property
def folders(self) -> AppDirectories:
if not self._folders:
self._folders = get_app_dirs()
return self._folders
class Config:
arbitrary_types_allowed = True
class BasePublicController(_BaseController):
"""
This is a public class for all User restricted controllers in the API.
It includes the common SharedDependencies and some common methods used
by all Admin controllers.
"""
deps: SharedDependencies = Depends(SharedDependencies.public)
translator: Translator = Depends(local_provider)
def __init__(self):
self.t = self.translator.t if self.translator else local_provider().t
...
class BaseUserController(ABC):
class BaseUserController(_BaseController):
"""
This is a base class for all User restricted controllers in the API.
It includes the common SharedDependencies and some common methods used
by all Admin controllers.
"""
deps: SharedDependencies = Depends(SharedDependencies.user)
user: PrivateUser = Depends(get_current_user)
translator: Translator = Depends(local_provider)
def __init__(self):
self.t = self.translator.t if self.translator else local_provider().t
# Manual Cache
_checks: OperationChecks
def registered_exceptions(self, ex: type[Exception]) -> str:
registered = {
**mealie_registered_exceptions(self.deps.t),
**mealie_registered_exceptions(self.translator),
}
return registered.get(ex, "An unexpected error occurred.")
@cached_property
def repos(self):
return AllRepositories(self.deps.session)
@property
def group_id(self) -> UUID4:
return self.deps.acting_user.group_id
@property
def user(self) -> PrivateUser:
return self.deps.acting_user
return self.user.group_id
@property
def group(self) -> GroupInDB:
return self.deps.repos.groups.get_one(self.group_id)
return self.repos.groups.get_one(self.group_id)
@cached_property
@property
def checks(self) -> OperationChecks:
return OperationChecks(self.deps.acting_user)
if not self._checks:
self._checks = OperationChecks(self.user)
return self._checks
class BaseAdminController(BaseUserController):
@ -74,8 +111,4 @@ class BaseAdminController(BaseUserController):
by all Admin controllers.
"""
deps: SharedDependencies = Depends(SharedDependencies.admin)
translator: Translator = Depends(local_provider)
def __init__(self):
self.t = self.translator.t if self.translator else local_provider().t
user: PrivateUser = Depends(get_admin_user)

View File

@ -51,6 +51,7 @@ def _init_cbv(cls: type[Any], instance: Any = None) -> None:
Idempotently modifies the provided `cls`, performing the following modifications:
* The `__init__` function is updated to set any class-annotated dependencies as instance attributes
* The `__signature__` attribute is updated to indicate to FastAPI what arguments should be passed to the initializer
* Variables starting with `_` are NOT included in the `__signature__` and will be set to None.
"""
if getattr(cls, CBV_CLASS_KEY, False): # pragma: no cover
return # Already initialized
@ -61,10 +62,17 @@ def _init_cbv(cls: type[Any], instance: Any = None) -> None:
x for x in old_parameters if x.kind not in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD)
]
private_attributes = []
dependency_names: list[str] = []
for name, hint in get_type_hints(cls).items():
if is_classvar(hint):
continue
if name.startswith("_"):
private_attributes.append(name)
continue
parameter_kwargs = {"default": getattr(cls, name, Ellipsis)}
dependency_names.append(name)
new_parameters.append(
@ -88,6 +96,9 @@ def _init_cbv(cls: type[Any], instance: Any = None) -> None:
setattr(cls, "__init__", new_init)
setattr(cls, CBV_CLASS_KEY, True)
for name in private_attributes:
setattr(cls, name, None)
def _register_endpoints(router: APIRouter, cls: type[Any], *urls: str) -> None:
cbv_router = APIRouter()

View File

@ -1,68 +0,0 @@
from functools import cached_property
from logging import Logger
from fastapi import Depends
from sqlalchemy.orm import Session
from mealie.core.config import get_app_dirs, get_app_settings
from mealie.core.dependencies.dependencies import get_admin_user, get_current_user
from mealie.core.root_logger import get_logger
from mealie.core.settings.directories import AppDirectories
from mealie.core.settings.settings import AppSettings
from mealie.db.db_setup import generate_session
from mealie.lang import Translator, local_provider
from mealie.repos import AllRepositories
from mealie.schema.user.user import PrivateUser
class SharedDependencies:
session: Session
t: Translator
acting_user: PrivateUser | None
def __init__(self, session: Session, acting_user: PrivateUser | None, provider: Translator | None = None) -> None:
self.t = provider or local_provider()
self.session = session
self.acting_user = acting_user
@classmethod
def public(
cls,
session: Session = Depends(generate_session),
translator: Translator = Depends(local_provider),
) -> "SharedDependencies":
return cls(session, None, translator)
@classmethod
def user(
cls,
session: Session = Depends(generate_session),
user: PrivateUser = Depends(get_current_user),
translator: Translator = Depends(local_provider),
) -> "SharedDependencies":
return cls(session, user, translator)
@classmethod
def admin(
cls,
session: Session = Depends(generate_session),
admin: PrivateUser = Depends(get_admin_user),
translator: Translator = Depends(local_provider),
) -> "SharedDependencies":
return cls(session, admin, translator)
@cached_property
def logger(self) -> Logger:
return get_logger()
@cached_property
def settings(self) -> AppSettings:
return get_app_settings()
@cached_property
def folders(self) -> AppDirectories:
return get_app_dirs()
@cached_property
def repos(self) -> AllRepositories:
return AllRepositories(self.session)

View File

@ -20,7 +20,7 @@ class AdminAboutController(BaseAdminController):
def get_app_info(self):
"""Get general application information"""
settings = self.deps.settings
settings = self.settings
return AdminAboutInfo(
production=settings.PRODUCTION,
@ -50,7 +50,7 @@ class AdminAboutController(BaseAdminController):
@router.get("/check", response_model=CheckAppConfig)
def check_app_config(self):
settings = self.deps.settings
settings = self.settings
return CheckAppConfig(
email_ready=settings.SMTP_ENABLE,
@ -61,7 +61,7 @@ class AdminAboutController(BaseAdminController):
@router.get("/docker/validate", response_model=DockerVolumeText)
def validate_docker_volume(self, bg: BackgroundTasks):
validation_dir = self.deps.folders.DATA_DIR / "docker-validation"
validation_dir = self.folders.DATA_DIR / "docker-validation"
validation_dir.mkdir(exist_ok=True)
random_string = "".join(random.choice(string.ascii_uppercase + string.digits) for _ in range(100))
@ -75,7 +75,7 @@ class AdminAboutController(BaseAdminController):
try:
shutil.rmtree(validation_dir)
except Exception as e:
self.deps.logger.error(f"Failed to remove docker validation directory: {e}")
self.logger.error(f"Failed to remove docker validation directory: {e}")
bg.add_task(cleanup)

View File

@ -12,7 +12,7 @@ class AdminEmailController(BaseAdminController):
@router.get("", response_model=EmailReady)
async def check_email_config(self):
"""Get general application information"""
return EmailReady(ready=self.deps.settings.SMTP_ENABLE)
return EmailReady(ready=self.settings.SMTP_ENABLE)
@router.post("", response_model=EmailSuccess)
async def send_test_email(self, data: EmailTest):
@ -23,7 +23,7 @@ class AdminEmailController(BaseAdminController):
try:
status = service.send_test_email(data.email)
except Exception as e:
self.deps.logger.error(e)
self.logger.error(e)
error = str(e)
return EmailSuccess(success=status, error=error)

View File

@ -77,10 +77,10 @@ class AdminMaintenanceController(BaseAdminController):
log_file_size = os.path.getsize(LOGGER_FILE)
return MaintenanceSummary(
data_dir_size=fs_stats.pretty_size(fs_stats.get_dir_size(self.deps.folders.DATA_DIR)),
data_dir_size=fs_stats.pretty_size(fs_stats.get_dir_size(self.folders.DATA_DIR)),
log_file_size=fs_stats.pretty_size(log_file_size),
cleanable_images=clean_images(self.deps.folders.RECIPE_DATA_DIR, dry_run=True),
cleanable_dirs=clean_recipe_folders(self.deps.folders.RECIPE_DATA_DIR, dry_run=True),
cleanable_images=clean_images(self.folders.RECIPE_DATA_DIR, dry_run=True),
cleanable_dirs=clean_recipe_folders(self.folders.RECIPE_DATA_DIR, dry_run=True),
)
@router.get("/logs", response_model=MaintenanceLogs)
@ -91,11 +91,11 @@ class AdminMaintenanceController(BaseAdminController):
@router.get("/storage", response_model=MaintenanceStorageDetails)
def get_storage_details(self):
return MaintenanceStorageDetails(
temp_dir_size=fs_stats.pretty_size(fs_stats.get_dir_size(self.deps.folders.TEMP_DIR)),
backups_dir_size=fs_stats.pretty_size(fs_stats.get_dir_size(self.deps.folders.BACKUP_DIR)),
groups_dir_size=fs_stats.pretty_size(fs_stats.get_dir_size(self.deps.folders.GROUPS_DIR)),
recipes_dir_size=fs_stats.pretty_size(fs_stats.get_dir_size(self.deps.folders.RECIPE_DATA_DIR)),
user_dir_size=fs_stats.pretty_size(fs_stats.get_dir_size(self.deps.folders.USER_DIR)),
temp_dir_size=fs_stats.pretty_size(fs_stats.get_dir_size(self.folders.TEMP_DIR)),
backups_dir_size=fs_stats.pretty_size(fs_stats.get_dir_size(self.folders.BACKUP_DIR)),
groups_dir_size=fs_stats.pretty_size(fs_stats.get_dir_size(self.folders.GROUPS_DIR)),
recipes_dir_size=fs_stats.pretty_size(fs_stats.get_dir_size(self.folders.RECIPE_DATA_DIR)),
user_dir_size=fs_stats.pretty_size(fs_stats.get_dir_size(self.folders.USER_DIR)),
)
@router.post("/clean/images", response_model=SuccessResponse)
@ -104,7 +104,7 @@ class AdminMaintenanceController(BaseAdminController):
Purges all the images from the filesystem that aren't .webp
"""
try:
cleaned_images = clean_images(self.deps.folders.RECIPE_DATA_DIR, dry_run=False)
cleaned_images = clean_images(self.folders.RECIPE_DATA_DIR, dry_run=False)
return SuccessResponse.respond(f"{cleaned_images} Images cleaned")
except Exception as e:
raise HTTPException(status_code=500, detail=ErrorResponse.respond("Failed to clean images")) from e
@ -112,8 +112,8 @@ class AdminMaintenanceController(BaseAdminController):
@router.post("/clean/temp", response_model=SuccessResponse)
def clean_temp(self):
try:
shutil.rmtree(self.deps.folders.TEMP_DIR)
self.deps.folders.TEMP_DIR.mkdir(parents=True, exist_ok=True)
shutil.rmtree(self.folders.TEMP_DIR)
self.folders.TEMP_DIR.mkdir(parents=True, exist_ok=True)
except Exception as e:
raise HTTPException(status_code=500, detail=ErrorResponse.respond("Failed to clean temp")) from e
@ -125,7 +125,7 @@ class AdminMaintenanceController(BaseAdminController):
Deletes all the recipe folders that don't have names that are valid UUIDs
"""
try:
cleaned_dirs = clean_recipe_folders(self.deps.folders.RECIPE_DATA_DIR, dry_run=False)
cleaned_dirs = clean_recipe_folders(self.folders.RECIPE_DATA_DIR, dry_run=False)
return SuccessResponse.respond(f"{cleaned_dirs} Recipe folders removed")
except Exception as e:
raise HTTPException(status_code=500, detail=ErrorResponse.respond("Failed to clean directories")) from e

View File

@ -11,7 +11,6 @@ from mealie.schema.user.user import GroupBase, GroupInDB, GroupPagination
from mealie.services.group_services.group_service import GroupService
from .._base import BaseAdminController, controller
from .._base.dependencies import SharedDependencies
from .._base.mixins import HttpRepo
router = APIRouter(prefix="/groups", tags=["Admin: Groups"])
@ -19,14 +18,12 @@ router = APIRouter(prefix="/groups", tags=["Admin: Groups"])
@controller(router)
class AdminUserManagementRoutes(BaseAdminController):
deps: SharedDependencies = Depends(SharedDependencies.user)
@cached_property
def repo(self):
if not self.deps.acting_user:
if not self.user:
raise Exception("No user is logged in.")
return self.deps.repos.groups
return self.repos.groups
# =======================================================================
# CRUD Operations
@ -35,7 +32,7 @@ class AdminUserManagementRoutes(BaseAdminController):
def mixins(self):
return HttpRepo[GroupBase, GroupInDB, GroupAdminUpdate](
self.repo,
self.deps.logger,
self.logger,
self.registered_exceptions,
)
@ -51,7 +48,7 @@ class AdminUserManagementRoutes(BaseAdminController):
@router.post("", response_model=GroupInDB, status_code=status.HTTP_201_CREATED)
def create_one(self, data: GroupBase):
return GroupService.create_group(self.deps.repos, data)
return GroupService.create_group(self.repos, data)
@router.get("/{item_id}", response_model=GroupInDB)
def get_one(self, item_id: UUID4):

View File

@ -5,7 +5,6 @@ from pydantic import UUID4
from mealie.core import security
from mealie.routes._base import BaseAdminController, controller
from mealie.routes._base.dependencies import SharedDependencies
from mealie.routes._base.mixins import HttpRepo
from mealie.schema.response.pagination import PaginationQuery
from mealie.schema.response.responses import ErrorResponse
@ -16,21 +15,19 @@ router = APIRouter(prefix="/users", tags=["Admin: Users"])
@controller(router)
class AdminUserManagementRoutes(BaseAdminController):
deps: SharedDependencies = Depends(SharedDependencies.user)
@cached_property
def repo(self):
if not self.deps.acting_user:
if not self.user:
raise Exception("No user is logged in.")
return self.deps.repos.users
return self.repos.users
# =======================================================================
# CRUD Operations
@property
def mixins(self):
return HttpRepo[UserIn, UserOut, UserOut](self.repo, self.deps.logger, self.registered_exceptions)
return HttpRepo[UserIn, UserOut, UserOut](self.repo, self.logger, self.registered_exceptions)
@router.get("", response_model=UserPagination)
def get_all(self, q: PaginationQuery = Depends(PaginationQuery)):
@ -54,7 +51,7 @@ class AdminUserManagementRoutes(BaseAdminController):
@router.put("/{item_id}", response_model=UserOut)
def update_one(self, item_id: UUID4, data: UserOut):
# Prevent self demotion
if self.deps.acting_user.id == item_id and self.deps.acting_user.admin != data.admin:
if self.user.id == item_id and self.user.admin != data.admin:
raise HTTPException(status_code=403, detail=ErrorResponse.respond("you cannot demote yourself"))
return self.mixins.update_one(data, item_id)

View File

@ -25,18 +25,18 @@ router = APIRouter(prefix="/comments", tags=["Recipe: Comments"])
class RecipeCommentRoutes(BaseUserController):
@cached_property
def repo(self):
return self.deps.repos.comments
return self.repos.comments
# =======================================================================
# CRUD Operations
@property
def mixins(self) -> HttpRepo:
return HttpRepo(self.repo, self.deps.logger, self.registered_exceptions, "An unexpected error occurred.")
return HttpRepo(self.repo, self.logger, self.registered_exceptions, "An unexpected error occurred.")
def _check_comment_belongs_to_user(self, item_id: UUID4) -> None:
comment = self.repo.get_one(item_id)
if comment.user_id != self.deps.acting_user.id and not self.deps.acting_user.admin:
if comment.user_id != self.user.id and not self.user.admin:
raise HTTPException(
status_code=403,
detail=ErrorResponse(message="Comment does not belong to user"),
@ -54,7 +54,7 @@ class RecipeCommentRoutes(BaseUserController):
@router.post("", response_model=RecipeCommentOut, status_code=201)
def create_one(self, data: RecipeCommentCreate):
save_data = RecipeCommentSave(text=data.text, user_id=self.deps.acting_user.id, recipe_id=data.recipe_id)
save_data = RecipeCommentSave(text=data.text, user_id=self.user.id, recipe_id=data.recipe_id)
return self.mixins.create_one(save_data)
@router.get("/{item_id}", response_model=RecipeCommentOut)

View File

@ -24,11 +24,11 @@ class GroupCookbookController(BaseUserController):
@cached_property
def repo(self):
return self.deps.repos.cookbooks.by_group(self.group_id)
return self.repos.cookbooks.by_group(self.group_id)
def registered_exceptions(self, ex: type[Exception]) -> str:
registered = {
**mealie_registered_exceptions(self.deps.t),
**mealie_registered_exceptions(self.translator),
}
return registered.get(ex, "An unexpected error occurred.")
@ -36,7 +36,7 @@ class GroupCookbookController(BaseUserController):
def mixins(self):
return HttpRepo[CreateCookBook, ReadCookBook, UpdateCookBook](
self.repo,
self.deps.logger,
self.logger,
self.registered_exceptions,
)
@ -57,7 +57,7 @@ class GroupCookbookController(BaseUserController):
if val:
self.event_bus.dispatch(
self.deps.acting_user.group_id,
self.user.group_id,
EventTypes.cookbook_created,
msg=self.t("notifications.generic-created", name=val.name),
event_source=EventSource(event_type="create", item_type="cookbook", item_id=val.id, slug=val.slug),
@ -99,7 +99,7 @@ class GroupCookbookController(BaseUserController):
val = self.mixins.update_one(data, item_id) # type: ignore
if val:
self.event_bus.dispatch(
self.deps.acting_user.group_id,
self.user.group_id,
EventTypes.cookbook_updated,
msg=self.t("notifications.generic-updated", name=val.name),
event_source=EventSource(event_type="update", item_type="cookbook", item_id=val.id, slug=val.slug),
@ -112,7 +112,7 @@ class GroupCookbookController(BaseUserController):
val = self.mixins.delete_one(item_id)
if val:
self.event_bus.dispatch(
self.deps.acting_user.group_id,
self.user.group_id,
EventTypes.cookbook_deleted,
msg=self.t("notifications.generic-deleted", name=val.name),
event_source=EventSource(event_type="delete", item_type="cookbook", item_id=val.id, slug=val.slug),

View File

@ -30,17 +30,17 @@ class GroupEventsNotifierController(BaseUserController):
@cached_property
def repo(self):
if not self.deps.acting_user:
if not self.user:
raise Exception("No user is logged in.")
return self.deps.repos.group_event_notifier.by_group(self.deps.acting_user.group_id)
return self.repos.group_event_notifier.by_group(self.user.group_id)
# =======================================================================
# CRUD Operations
@property
def mixins(self) -> HttpRepo:
return HttpRepo(self.repo, self.deps.logger, self.registered_exceptions, "An unexpected error occurred.")
return HttpRepo(self.repo, self.logger, self.registered_exceptions, "An unexpected error occurred.")
@router.get("", response_model=GroupEventPagination)
def get_all(self, q: PaginationQuery = Depends(PaginationQuery)):
@ -54,7 +54,7 @@ class GroupEventsNotifierController(BaseUserController):
@router.post("", response_model=GroupEventNotifierOut, status_code=201)
def create_one(self, data: GroupEventNotifierCreate):
save_data = cast(data, GroupEventNotifierSave, group_id=self.deps.acting_user.group_id)
save_data = cast(data, GroupEventNotifierSave, group_id=self.user.group_id)
return self.mixins.create_one(save_data)
@router.get("/{item_id}", response_model=GroupEventNotifierOut)

View File

@ -17,18 +17,18 @@ router = APIRouter(prefix="/groups/reports", tags=["Groups: Reports"])
class GroupReportsController(BaseUserController):
@cached_property
def repo(self):
return self.deps.repos.group_reports.by_group(self.deps.acting_user.group_id)
return self.repos.group_reports.by_group(self.user.group_id)
def registered_exceptions(self, ex: type[Exception]) -> str:
return {
**mealie_registered_exceptions(self.deps.t),
**mealie_registered_exceptions(self.translator),
}.get(ex, "An unexpected error occurred.")
@cached_property
def mixins(self):
return HttpRepo[ReportCreate, ReportOut, ReportCreate](
self.repo,
self.deps.logger,
self.logger,
self.registered_exceptions,
)

View File

@ -22,7 +22,7 @@ class GroupInvitationsController(BaseUserController):
@router.post("", response_model=ReadInviteToken, status_code=status.HTTP_201_CREATED)
def create_invite_token(self, uses: CreateInviteToken):
if not self.deps.acting_user.can_invite:
if not self.user.can_invite:
raise HTTPException(status.HTTP_403_FORBIDDEN, detail="User is not allowed to create invite tokens")
token = SaveInviteToken(uses_left=uses.uses, group_id=self.group_id, token=url_safe_token())
@ -31,7 +31,7 @@ class GroupInvitationsController(BaseUserController):
@router.post("/email", response_model=EmailInitationResponse)
def email_invitation(self, invite: EmailInvitation):
email_service = EmailService()
url = f"{self.deps.settings.BASE_URL}/register?token={invite.token}"
url = f"{self.settings.BASE_URL}/register?token={invite.token}"
success = False
error = None

View File

@ -25,17 +25,17 @@ router = APIRouter(prefix="/groups/labels", tags=["Group: Multi Purpose Labels"]
class MultiPurposeLabelsController(BaseUserController):
@cached_property
def repo(self):
if not self.deps.acting_user:
if not self.user:
raise Exception("No user is logged in.")
return self.deps.repos.group_multi_purpose_labels.by_group(self.deps.acting_user.group_id)
return self.repos.group_multi_purpose_labels.by_group(self.user.group_id)
# =======================================================================
# CRUD Operations
@property
def mixins(self) -> HttpRepo:
return HttpRepo(self.repo, self.deps.logger, self.registered_exceptions, "An unexpected error occurred.")
return HttpRepo(self.repo, self.logger, self.registered_exceptions, "An unexpected error occurred.")
@router.get("", response_model=MultiPurposeLabelPagination)
def get_all(self, q: PaginationQuery = Depends(PaginationQuery)):
@ -49,7 +49,7 @@ class MultiPurposeLabelsController(BaseUserController):
@router.post("", response_model=MultiPurposeLabelOut)
def create_one(self, data: MultiPurposeLabelCreate):
save_data = cast(data, MultiPurposeLabelSave, group_id=self.deps.acting_user.group_id)
save_data = cast(data, MultiPurposeLabelSave, group_id=self.user.group_id)
return self.mixins.create_one(save_data)
@router.get("/{item_id}", response_model=MultiPurposeLabelOut)

View File

@ -27,7 +27,7 @@ class GroupMealplanController(BaseUserController):
def registered_exceptions(self, ex: type[Exception]) -> str:
registered = {
**mealie_registered_exceptions(self.deps.t),
**mealie_registered_exceptions(self.translator),
}
return registered.get(ex, "An unexpected error occurred.")
@ -35,7 +35,7 @@ class GroupMealplanController(BaseUserController):
def mixins(self):
return HttpRepo[CreatePlanEntry, ReadPlanEntry, UpdatePlanEntry](
self.repo,
self.deps.logger,
self.logger,
self.registered_exceptions,
)

View File

@ -12,15 +12,15 @@ router = UserAPIRouter(prefix="/groups/categories", tags=["Groups: Mealplan Cate
class GroupMealplanConfigController(BaseUserController):
@property
def mixins(self):
return HttpRepo[GroupInDB, GroupInDB, GroupInDB](self.repos.groups, self.deps.logger)
return HttpRepo[GroupInDB, GroupInDB, GroupInDB](self.repos.groups, self.logger)
@router.get("", response_model=list[CategoryBase])
def get_mealplan_categories(self):
data = self.mixins.get_one(self.deps.acting_user.group_id)
data = self.mixins.get_one(self.user.group_id)
return data.categories
@router.put("", response_model=list[CategoryBase])
def update_mealplan_categories(self, new_categories: list[CategoryBase]):
data = self.mixins.get_one(self.deps.acting_user.group_id)
data = self.mixins.get_one(self.user.group_id)
data.categories = new_categories
return self.mixins.update_one(data, data.id).categories

View File

@ -22,7 +22,7 @@ class GroupMealplanConfigController(BaseUserController):
@cached_property
def mixins(self):
return HttpRepo[PlanRulesCreate, PlanRulesOut, PlanRulesOut](self.repo, self.deps.logger)
return HttpRepo[PlanRulesCreate, PlanRulesOut, PlanRulesOut](self.repo, self.logger)
@router.get("", response_model=PlanRulesPagination)
def get_all(self, q: PaginationQuery = Depends(PaginationQuery)):

View File

@ -37,7 +37,7 @@ class GroupMigrationController(BaseUserController):
args = {
"archive": temp_path,
"db": self.repos,
"session": self.deps.session,
"session": self.session,
"user_id": self.user.id,
"group_id": self.group_id,
"add_migration_tag": add_migration_tag,

View File

@ -38,13 +38,13 @@ class ShoppingListItemController(BaseUserController):
@cached_property
def repo(self):
return self.deps.repos.group_shopping_list_item
return self.repos.group_shopping_list_item
@cached_property
def mixins(self):
return HttpRepo[ShoppingListItemCreate, ShoppingListItemOut, ShoppingListItemCreate](
self.repo,
self.deps.logger,
self.logger,
)
@item_router.put("", response_model=list[ShoppingListItemOut])
@ -80,7 +80,7 @@ class ShoppingListItemController(BaseUserController):
if shopping_list_item:
self.event_bus.dispatch(
self.deps.acting_user.group_id,
self.user.group_id,
EventTypes.shopping_list_updated,
msg=self.t(
"notifications.generic-created",
@ -106,7 +106,7 @@ class ShoppingListItemController(BaseUserController):
if shopping_list_item:
self.event_bus.dispatch(
self.deps.acting_user.group_id,
self.user.group_id,
EventTypes.shopping_list_updated,
msg=self.t(
"notifications.generic-updated",
@ -128,7 +128,7 @@ class ShoppingListItemController(BaseUserController):
if shopping_list_item:
self.event_bus.dispatch(
self.deps.acting_user.group_id,
self.user.group_id,
EventTypes.shopping_list_updated,
msg=self.t(
"notifications.generic-deleted",
@ -158,14 +158,14 @@ class ShoppingListController(BaseUserController):
@cached_property
def repo(self):
return self.deps.repos.group_shopping_lists.by_group(self.deps.acting_user.group_id)
return self.repos.group_shopping_lists.by_group(self.user.group_id)
# =======================================================================
# CRUD Operations
@cached_property
def mixins(self) -> HttpRepo[ShoppingListCreate, ShoppingListOut, ShoppingListSave]:
return HttpRepo(self.repo, self.deps.logger, self.registered_exceptions, "An unexpected error occurred.")
return HttpRepo(self.repo, self.logger, self.registered_exceptions, "An unexpected error occurred.")
@router.get("", response_model=ShoppingListPagination)
def get_all(self, q: PaginationQuery = Depends(PaginationQuery)):
@ -179,12 +179,12 @@ class ShoppingListController(BaseUserController):
@router.post("", response_model=ShoppingListOut, status_code=201)
def create_one(self, data: ShoppingListCreate):
save_data = cast(data, ShoppingListSave, group_id=self.deps.acting_user.group_id)
save_data = cast(data, ShoppingListSave, group_id=self.user.group_id)
val = self.mixins.create_one(save_data)
if val:
self.event_bus.dispatch(
self.deps.acting_user.group_id,
self.user.group_id,
EventTypes.shopping_list_created,
msg=self.t("notifications.generic-created", name=val.name),
event_source=EventSource(
@ -205,7 +205,7 @@ class ShoppingListController(BaseUserController):
data = self.mixins.update_one(data, item_id) # type: ignore
if data:
self.event_bus.dispatch(
self.deps.acting_user.group_id,
self.user.group_id,
EventTypes.shopping_list_updated,
msg=self.t("notifications.generic-updated", name=data.name),
event_source=EventSource(
@ -221,7 +221,7 @@ class ShoppingListController(BaseUserController):
data = self.mixins.delete_one(item_id) # type: ignore
if data:
self.event_bus.dispatch(
self.deps.acting_user.group_id,
self.user.group_id,
EventTypes.shopping_list_deleted,
msg=self.t("notifications.generic-deleted", name=data.name),
event_source=EventSource(
@ -240,7 +240,7 @@ class ShoppingListController(BaseUserController):
shopping_list = self.service.add_recipe_ingredients_to_list(item_id, recipe_id)
if shopping_list:
self.event_bus.dispatch(
self.deps.acting_user.group_id,
self.user.group_id,
EventTypes.shopping_list_updated,
msg=self.t(
"notifications.generic-updated",
@ -260,7 +260,7 @@ class ShoppingListController(BaseUserController):
shopping_list = self.service.remove_recipe_ingredients_from_list(item_id, recipe_id)
if shopping_list:
self.event_bus.dispatch(
self.deps.acting_user.group_id,
self.user.group_id,
EventTypes.shopping_list_updated,
msg=self.t(
"notifications.generic-updated",

View File

@ -21,7 +21,7 @@ class ReadWebhookController(BaseUserController):
@property
def mixins(self) -> HttpRepo:
return HttpRepo[CreateWebhook, SaveWebhook, CreateWebhook](self.repo, self.deps.logger)
return HttpRepo[CreateWebhook, SaveWebhook, CreateWebhook](self.repo, self.logger)
@router.get("", response_model=WebhookPagination)
def get_all(self, q: PaginationQuery = Depends(PaginationQuery)):

View File

@ -39,7 +39,7 @@ class RecipeCategoryController(BaseUserController):
@cached_property
def mixins(self):
return HttpRepo(self.repo, self.deps.logger)
return HttpRepo(self.repo, self.logger)
@router.get("", response_model=RecipeCategoryPagination)
def get_all(self, q: PaginationQuery = Depends(PaginationQuery)):
@ -59,12 +59,12 @@ class RecipeCategoryController(BaseUserController):
data = self.mixins.create_one(save_data)
if data:
self.event_bus.dispatch(
self.deps.acting_user.group_id,
self.user.group_id,
EventTypes.category_created,
msg=self.t(
"notifications.generic-created-with-url",
name=data.name,
url=urls.category_url(data.slug, self.deps.settings.BASE_URL),
url=urls.category_url(data.slug, self.settings.BASE_URL),
),
event_source=EventSource(event_type="create", item_type="category", item_id=data.id, slug=data.slug),
)
@ -85,12 +85,12 @@ class RecipeCategoryController(BaseUserController):
if data:
self.event_bus.dispatch(
self.deps.acting_user.group_id,
self.user.group_id,
EventTypes.category_updated,
msg=self.t(
"notifications.generic-updated-with-url",
name=data.name,
url=urls.category_url(data.slug, self.deps.settings.BASE_URL),
url=urls.category_url(data.slug, self.settings.BASE_URL),
),
event_source=EventSource(event_type="update", item_type="category", item_id=data.id, slug=data.slug),
)
@ -105,7 +105,7 @@ class RecipeCategoryController(BaseUserController):
"""
if data := self.mixins.delete_one(item_id):
self.event_bus.dispatch(
self.deps.acting_user.group_id,
self.user.group_id,
EventTypes.category_deleted,
msg=self.t("notifications.generic-deleted", name=data.name),
event_source=EventSource(event_type="delete", item_type="category", item_id=data.id, slug=data.slug),

View File

@ -28,7 +28,7 @@ class TagController(BaseUserController):
@cached_property
def mixins(self):
return HttpRepo(self.repo, self.deps.logger)
return HttpRepo(self.repo, self.logger)
@router.get("", response_model=RecipeTagPagination)
async def get_all(self, q: PaginationQuery = Depends(PaginationQuery)):
@ -58,12 +58,12 @@ class TagController(BaseUserController):
data = self.repo.create(save_data)
if data:
self.event_bus.dispatch(
self.deps.acting_user.group_id,
self.user.group_id,
EventTypes.tag_created,
msg=self.t(
"notifications.generic-created-with-url",
name=data.name,
url=urls.tag_url(data.slug, self.deps.settings.BASE_URL),
url=urls.tag_url(data.slug, self.settings.BASE_URL),
),
event_source=EventSource(event_type="create", item_type="tag", item_id=data.id, slug=data.slug),
)
@ -76,12 +76,12 @@ class TagController(BaseUserController):
data = self.repo.update(item_id, save_data)
if data:
self.event_bus.dispatch(
self.deps.acting_user.group_id,
self.user.group_id,
EventTypes.tag_updated,
msg=self.t(
"notifications.generic-updated-with-url",
name=data.name,
url=urls.tag_url(data.slug, self.deps.settings.BASE_URL),
url=urls.tag_url(data.slug, self.settings.BASE_URL),
),
event_source=EventSource(event_type="update", item_type="tag", item_id=data.id, slug=data.slug),
)
@ -100,7 +100,7 @@ class TagController(BaseUserController):
if data:
self.event_bus.dispatch(
self.deps.acting_user.group_id,
self.user.group_id,
EventTypes.tag_deleted,
msg=self.t("notifications.generic-deleted", name=data.name),
event_source=EventSource(event_type="delete", item_type="tag", item_id=data.id, slug=data.slug),

View File

@ -22,7 +22,7 @@ class RecipeToolController(BaseUserController):
@property
def mixins(self) -> HttpRepo:
return HttpRepo[RecipeToolCreate, RecipeTool, RecipeToolCreate](self.repo, self.deps.logger)
return HttpRepo[RecipeToolCreate, RecipeTool, RecipeToolCreate](self.repo, self.logger)
@router.get("", response_model=RecipeToolPagination)
def get_all(self, q: PaginationQuery = Depends(PaginationQuery)):

View File

@ -56,7 +56,7 @@ class BaseRecipeController(BaseUserController):
@cached_property
def mixins(self):
return HttpRepo[CreateRecipe, Recipe, Recipe](self.repo, self.deps.logger)
return HttpRepo[CreateRecipe, Recipe, Recipe](self.repo, self.logger)
class FormatResponse(BaseModel):
@ -124,18 +124,18 @@ class RecipeController(BaseRecipeController):
def handle_exceptions(self, ex: Exception) -> None:
match type(ex):
case exceptions.PermissionDenied:
self.deps.logger.error("Permission Denied on recipe controller action")
self.logger.error("Permission Denied on recipe controller action")
raise HTTPException(status_code=403, detail=ErrorResponse.respond(message="Permission Denied"))
case exceptions.NoEntryFound:
self.deps.logger.error("No Entry Found on recipe controller action")
self.logger.error("No Entry Found on recipe controller action")
raise HTTPException(status_code=404, detail=ErrorResponse.respond(message="No Entry Found"))
case sqlalchemy.exc.IntegrityError:
self.deps.logger.error("SQL Integrity Error on recipe controller action")
self.logger.error("SQL Integrity Error on recipe controller action")
raise HTTPException(status_code=400, detail=ErrorResponse.respond(message="Recipe already exists"))
case _:
self.deps.logger.error("Unknown Error on recipe controller action")
self.deps.logger.exception(ex)
self.logger.error("Unknown Error on recipe controller action")
self.logger.exception(ex)
raise HTTPException(
status_code=500, detail=ErrorResponse.respond(message="Unknown Error", exception=str(ex))
)
@ -162,12 +162,12 @@ class RecipeController(BaseRecipeController):
if new_recipe:
self.event_bus.dispatch(
self.deps.acting_user.group_id,
self.user.group_id,
EventTypes.recipe_created,
msg=self.t(
"notifications.generic-created-with-url",
name=new_recipe.name,
url=urls.recipe_url(new_recipe.slug, self.deps.settings.BASE_URL),
url=urls.recipe_url(new_recipe.slug, self.settings.BASE_URL),
),
event_source=EventSource(
event_type="create", item_type="recipe", item_id=new_recipe.id, slug=new_recipe.slug
@ -255,12 +255,12 @@ class RecipeController(BaseRecipeController):
if new_recipe:
self.event_bus.dispatch(
self.deps.acting_user.group_id,
self.user.group_id,
EventTypes.recipe_created,
msg=self.t(
"notifications.generic-created-with-url",
name=new_recipe.name,
url=urls.recipe_url(new_recipe.slug, self.deps.settings.BASE_URL),
url=urls.recipe_url(new_recipe.slug, self.settings.BASE_URL),
),
event_source=EventSource(
event_type="create", item_type="recipe", item_id=new_recipe.id, slug=new_recipe.slug
@ -279,12 +279,12 @@ class RecipeController(BaseRecipeController):
if data:
self.event_bus.dispatch(
self.deps.acting_user.group_id,
self.user.group_id,
EventTypes.recipe_updated,
msg=self.t(
"notifications.generic-updated-with-url",
name=data.name,
url=urls.recipe_url(data.slug, self.deps.settings.BASE_URL),
url=urls.recipe_url(data.slug, self.settings.BASE_URL),
),
event_source=EventSource(event_type="update", item_type="recipe", item_id=data.id, slug=data.slug),
)
@ -301,12 +301,12 @@ class RecipeController(BaseRecipeController):
if data:
self.event_bus.dispatch(
self.deps.acting_user.group_id,
self.user.group_id,
EventTypes.recipe_updated,
msg=self.t(
"notifications.generic-updated-with-url",
name=data.name,
url=urls.recipe_url(data.slug, self.deps.settings.BASE_URL),
url=urls.recipe_url(data.slug, self.settings.BASE_URL),
),
event_source=EventSource(event_type="update", item_type="recipe", item_id=data.id, slug=data.slug),
)
@ -323,7 +323,7 @@ class RecipeController(BaseRecipeController):
if data:
self.event_bus.dispatch(
self.deps.acting_user.group_id,
self.user.group_id,
EventTypes.recipe_deleted,
msg=self.t("notifications.generic-deleted", name=data.name),
event_source=EventSource(event_type="delete", item_type="recipe", item_id=data.id, slug=data.slug),

View File

@ -19,7 +19,7 @@ class RecipeSharedController(BaseUserController):
@cached_property
def mixins(self):
return HttpRepo[RecipeShareTokenSave, RecipeShareToken, RecipeShareTokenCreate](self.repo, self.deps.logger)
return HttpRepo[RecipeShareTokenSave, RecipeShareToken, RecipeShareTokenCreate](self.repo, self.logger)
@router.get("", response_model=list[RecipeShareTokenSummary])
def get_all(self, recipe_id: UUID4 = None):

View File

@ -25,13 +25,13 @@ router = APIRouter(prefix="/foods", tags=["Recipes: Foods"], route_class=MealieC
class IngredientFoodsController(BaseUserController):
@cached_property
def repo(self):
return self.deps.repos.ingredient_foods.by_group(self.group_id)
return self.repos.ingredient_foods.by_group(self.group_id)
@cached_property
def mixins(self):
return HttpRepo[SaveIngredientFood, IngredientFood, CreateIngredientFood](
self.repo,
self.deps.logger,
self.logger,
self.registered_exceptions,
)
@ -41,7 +41,7 @@ class IngredientFoodsController(BaseUserController):
self.repo.merge(data.from_food, data.to_food)
return SuccessResponse.respond("Successfully merged foods")
except Exception as e:
self.deps.logger.error(e)
self.logger.error(e)
raise HTTPException(500, "Failed to merge foods") from e
@router.get("", response_model=IngredientFoodPagination)

View File

@ -25,13 +25,13 @@ router = APIRouter(prefix="/units", tags=["Recipes: Units"], route_class=MealieC
class IngredientUnitsController(BaseUserController):
@cached_property
def repo(self):
return self.deps.repos.ingredient_units.by_group(self.group_id)
return self.repos.ingredient_units.by_group(self.group_id)
@cached_property
def mixins(self):
return HttpRepo[CreateIngredientUnit, IngredientUnit, CreateIngredientUnit](
self.repo,
self.deps.logger,
self.logger,
self.registered_exceptions,
)
@ -41,7 +41,7 @@ class IngredientUnitsController(BaseUserController):
self.repo.merge(data.from_unit, data.to_unit)
return SuccessResponse.respond("Successfully merged units")
except Exception as e:
self.deps.logger.error(e)
self.logger.error(e)
raise HTTPException(500, "Failed to merge units") from e
@router.get("", response_model=IngredientUnitPagination)

View File

@ -2,8 +2,7 @@ from fastapi import Depends, HTTPException, status
from pydantic import UUID4
from mealie.core.security import hash_password, verify_password
from mealie.routes._base import BaseAdminController, controller
from mealie.routes._base.base_controllers import BaseUserController
from mealie.routes._base import BaseAdminController, BaseUserController, controller
from mealie.routes._base.mixins import HttpRepo
from mealie.routes._base.routers import AdminAPIRouter, UserAPIRouter
from mealie.routes.users._helpers import assert_user_change_allowed
@ -20,7 +19,7 @@ admin_router = AdminAPIRouter(prefix="/users", tags=["Users: Admin CRUD"])
class AdminUserController(BaseAdminController):
@property
def mixins(self) -> HttpRepo:
return HttpRepo[UserIn, UserOut, UserBase](self.repos.users, self.deps.logger)
return HttpRepo[UserIn, UserOut, UserBase](self.repos.users, self.logger)
@admin_router.get("", response_model=UserPagination)
def get_all(self, q: PaginationQuery = Depends(PaginationQuery)):

View File

@ -24,8 +24,8 @@ class RegistrationController(BasePublicController):
)
registration_service = RegistrationService(
self.deps.logger,
get_repositories(self.deps.session),
self.logger,
get_repositories(self.session),
self.translator,
)