chore: refactor base schema (#1098)
* remove dead backup code * implmenet own base model * refactor to use MealieModel instead of CamelModel * cleanup deps
This commit is contained in:
parent
bcd98cba2f
commit
11b4d2389a
50 changed files with 253 additions and 623 deletions
|
@ -96,7 +96,7 @@ def generate_typescript_types() -> None:
|
|||
|
||||
try:
|
||||
path_as_module = path_to_module(module)
|
||||
generate_typescript_defs(path_as_module, str(out_path), exclude=("CamelModel")) # type: ignore
|
||||
generate_typescript_defs(path_as_module, str(out_path), exclude=("MealieModel")) # type: ignore
|
||||
except Exception as e:
|
||||
failed_modules.append(module)
|
||||
print("\nModule Errors:", module, "-----------------") # noqa
|
||||
|
|
|
@ -3,11 +3,12 @@ import pathlib
|
|||
import _static
|
||||
import dotenv
|
||||
import requests
|
||||
from fastapi_camelcase import CamelModel
|
||||
from jinja2 import Template
|
||||
from requests import Response
|
||||
from rich import print
|
||||
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
BASE = pathlib.Path(__file__).parent.parent.parent
|
||||
|
||||
API_KEY = dotenv.get_key(BASE / ".env", "CROWDIN_API_KEY")
|
||||
|
@ -57,7 +58,7 @@ export const LOCALES = [{% for locale in locales %}
|
|||
"""
|
||||
|
||||
|
||||
class TargetLanguage(CamelModel):
|
||||
class TargetLanguage(MealieModel):
|
||||
id: str
|
||||
name: str
|
||||
locale: str
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
from fastapi import APIRouter
|
||||
from fastapi_camelcase import CamelModel
|
||||
|
||||
from mealie.routes._base import BaseAdminController, controller
|
||||
from mealie.schema._mealie import MealieModel
|
||||
from mealie.services.email import EmailService
|
||||
|
||||
router = APIRouter(prefix="/email")
|
||||
|
||||
|
||||
class EmailReady(CamelModel):
|
||||
class EmailReady(MealieModel):
|
||||
ready: bool
|
||||
|
||||
|
||||
class EmailSuccess(CamelModel):
|
||||
class EmailSuccess(MealieModel):
|
||||
success: bool
|
||||
error: str = None
|
||||
|
||||
|
||||
class EmailTest(CamelModel):
|
||||
class EmailTest(MealieModel):
|
||||
email: str
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
from .mealie_model import *
|
||||
from .types import *
|
44
mealie/schema/_mealie/mealie_model.py
Normal file
44
mealie/schema/_mealie/mealie_model.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TypeVar
|
||||
|
||||
from humps.main import camelize
|
||||
from pydantic import BaseModel
|
||||
|
||||
T = TypeVar("T", bound=BaseModel)
|
||||
|
||||
|
||||
class MealieModel(BaseModel):
|
||||
class Config:
|
||||
alias_generator = camelize
|
||||
allow_population_by_field_name = True
|
||||
|
||||
def cast(self, cls: type[T], **kwargs) -> T:
|
||||
"""
|
||||
Cast the current model to another with additional arguments. Useful for
|
||||
transforming DTOs into models that are saved to a database
|
||||
"""
|
||||
create_data = {field: getattr(self, field) for field in self.__fields__ if field in cls.__fields__}
|
||||
create_data.update(kwargs or {})
|
||||
return cls(**create_data)
|
||||
|
||||
def map_to(self, dest: T) -> T:
|
||||
"""
|
||||
Map matching values from the current model to another model. Model returned
|
||||
for method chaining.
|
||||
"""
|
||||
|
||||
for field in self.__fields__:
|
||||
if field in dest.__fields__:
|
||||
setattr(dest, field, getattr(self, field))
|
||||
|
||||
return dest
|
||||
|
||||
def map_from(self, src: BaseModel):
|
||||
"""
|
||||
Map matching values from another model to the current model.
|
||||
"""
|
||||
|
||||
for field in src.__fields__:
|
||||
if field in self.__fields__:
|
||||
setattr(self, field, getattr(src, field))
|
|
@ -1,7 +1,7 @@
|
|||
from fastapi_camelcase import CamelModel
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
|
||||
class AppStatistics(CamelModel):
|
||||
class AppStatistics(MealieModel):
|
||||
total_recipes: int
|
||||
total_users: int
|
||||
total_groups: int
|
||||
|
@ -9,7 +9,7 @@ class AppStatistics(CamelModel):
|
|||
untagged_recipes: int
|
||||
|
||||
|
||||
class AppInfo(CamelModel):
|
||||
class AppInfo(MealieModel):
|
||||
production: bool
|
||||
version: str
|
||||
demo_status: bool
|
||||
|
@ -26,7 +26,7 @@ class AdminAboutInfo(AppInfo):
|
|||
build_id: str
|
||||
|
||||
|
||||
class CheckAppConfig(CamelModel):
|
||||
class CheckAppConfig(MealieModel):
|
||||
email_ready: bool = False
|
||||
ldap_ready: bool = False
|
||||
base_url_set: bool = False
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from fastapi_camelcase import CamelModel
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
|
||||
class MaintenanceSummary(CamelModel):
|
||||
class MaintenanceSummary(MealieModel):
|
||||
data_dir_size: str
|
||||
log_file_size: str
|
||||
cleanable_images: int
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
from typing import Optional
|
||||
|
||||
from fastapi_camelcase import CamelModel
|
||||
from pydantic import validator
|
||||
from slugify import slugify
|
||||
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
from ..recipe.recipe_category import RecipeCategoryResponse
|
||||
|
||||
|
||||
class CustomPageBase(CamelModel):
|
||||
class CustomPageBase(MealieModel):
|
||||
name: str
|
||||
slug: Optional[str]
|
||||
position: int
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
from fastapi_camelcase import CamelModel
|
||||
from pydantic import UUID4, validator
|
||||
from slugify import slugify
|
||||
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
from ..recipe.recipe_category import CategoryBase, RecipeCategoryResponse
|
||||
|
||||
|
||||
class CreateCookBook(CamelModel):
|
||||
class CreateCookBook(MealieModel):
|
||||
name: str
|
||||
description: str = ""
|
||||
slug: str = None
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
from fastapi_camelcase import CamelModel
|
||||
from pydantic import UUID4
|
||||
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
from .group_preferences import UpdateGroupPreferences
|
||||
|
||||
|
||||
class GroupAdminUpdate(CamelModel):
|
||||
class GroupAdminUpdate(MealieModel):
|
||||
id: UUID4
|
||||
name: str
|
||||
preferences: UpdateGroupPreferences
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
from fastapi_camelcase import CamelModel
|
||||
from pydantic import UUID4, NoneStr
|
||||
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
# =============================================================================
|
||||
# Group Events Notifier Options
|
||||
|
||||
|
||||
class GroupEventNotifierOptions(CamelModel):
|
||||
class GroupEventNotifierOptions(MealieModel):
|
||||
"""
|
||||
These events are in-sync with the EventTypes found in the EventBusService.
|
||||
If you modify this, make sure to update the EventBusService as well.
|
||||
|
@ -55,7 +56,7 @@ class GroupEventNotifierOptionsOut(GroupEventNotifierOptions):
|
|||
# Notifiers
|
||||
|
||||
|
||||
class GroupEventNotifierCreate(CamelModel):
|
||||
class GroupEventNotifierCreate(MealieModel):
|
||||
name: str
|
||||
apprise_url: str
|
||||
|
||||
|
@ -71,7 +72,7 @@ class GroupEventNotifierUpdate(GroupEventNotifierSave):
|
|||
apprise_url: NoneStr = None
|
||||
|
||||
|
||||
class GroupEventNotifierOut(CamelModel):
|
||||
class GroupEventNotifierOut(MealieModel):
|
||||
id: UUID4
|
||||
name: str
|
||||
enabled: bool
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
from datetime import datetime
|
||||
|
||||
from fastapi_camelcase import CamelModel
|
||||
from pydantic import UUID4
|
||||
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
class GroupDataExport(CamelModel):
|
||||
|
||||
class GroupDataExport(MealieModel):
|
||||
id: UUID4
|
||||
group_id: UUID4
|
||||
name: str
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import enum
|
||||
|
||||
from fastapi_camelcase import CamelModel
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
|
||||
class SupportedMigrations(str, enum.Enum):
|
||||
|
@ -10,5 +10,5 @@ class SupportedMigrations(str, enum.Enum):
|
|||
mealie_alpha = "mealie_alpha"
|
||||
|
||||
|
||||
class DataMigrationCreate(CamelModel):
|
||||
class DataMigrationCreate(MealieModel):
|
||||
source_type: SupportedMigrations
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
from fastapi_camelcase import CamelModel
|
||||
from pydantic import UUID4
|
||||
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
class SetPermissions(CamelModel):
|
||||
|
||||
class SetPermissions(MealieModel):
|
||||
user_id: UUID4
|
||||
can_manage: bool = False
|
||||
can_invite: bool = False
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
from uuid import UUID
|
||||
|
||||
from fastapi_camelcase import CamelModel
|
||||
from pydantic import UUID4
|
||||
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
class UpdateGroupPreferences(CamelModel):
|
||||
|
||||
class UpdateGroupPreferences(MealieModel):
|
||||
private_group: bool = False
|
||||
first_day_of_week: int = 0
|
||||
|
||||
|
|
|
@ -2,13 +2,13 @@ from __future__ import annotations
|
|||
|
||||
from typing import Optional
|
||||
|
||||
from fastapi_camelcase import CamelModel
|
||||
from pydantic import UUID4
|
||||
|
||||
from mealie.schema._mealie import MealieModel
|
||||
from mealie.schema.recipe.recipe_ingredient import IngredientFood, IngredientUnit
|
||||
|
||||
|
||||
class ShoppingListItemRecipeRef(CamelModel):
|
||||
class ShoppingListItemRecipeRef(MealieModel):
|
||||
recipe_id: UUID4
|
||||
recipe_quantity: float
|
||||
|
||||
|
@ -21,7 +21,7 @@ class ShoppingListItemRecipeRefOut(ShoppingListItemRecipeRef):
|
|||
orm_mode = True
|
||||
|
||||
|
||||
class ShoppingListItemCreate(CamelModel):
|
||||
class ShoppingListItemCreate(MealieModel):
|
||||
shopping_list_id: UUID4
|
||||
checked: bool = False
|
||||
position: int = 0
|
||||
|
@ -51,11 +51,11 @@ class ShoppingListItemOut(ShoppingListItemUpdate):
|
|||
orm_mode = True
|
||||
|
||||
|
||||
class ShoppingListCreate(CamelModel):
|
||||
class ShoppingListCreate(MealieModel):
|
||||
name: str = None
|
||||
|
||||
|
||||
class ShoppingListRecipeRefOut(CamelModel):
|
||||
class ShoppingListRecipeRefOut(MealieModel):
|
||||
id: UUID4
|
||||
shopping_list_id: UUID4
|
||||
recipe_id: UUID4
|
||||
|
|
|
@ -1,20 +1,21 @@
|
|||
from uuid import UUID
|
||||
|
||||
from fastapi_camelcase import CamelModel
|
||||
from pydantic import NoneStr
|
||||
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
class CreateInviteToken(CamelModel):
|
||||
|
||||
class CreateInviteToken(MealieModel):
|
||||
uses: int
|
||||
|
||||
|
||||
class SaveInviteToken(CamelModel):
|
||||
class SaveInviteToken(MealieModel):
|
||||
uses_left: int
|
||||
group_id: UUID
|
||||
token: str
|
||||
|
||||
|
||||
class ReadInviteToken(CamelModel):
|
||||
class ReadInviteToken(MealieModel):
|
||||
token: str
|
||||
uses_left: int
|
||||
group_id: UUID
|
||||
|
@ -23,11 +24,11 @@ class ReadInviteToken(CamelModel):
|
|||
orm_mode = True
|
||||
|
||||
|
||||
class EmailInvitation(CamelModel):
|
||||
class EmailInvitation(MealieModel):
|
||||
email: str
|
||||
token: str
|
||||
|
||||
|
||||
class EmailInitationResponse(CamelModel):
|
||||
class EmailInitationResponse(MealieModel):
|
||||
success: bool
|
||||
error: NoneStr = None
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
from uuid import UUID
|
||||
|
||||
from fastapi_camelcase import CamelModel
|
||||
from pydantic import UUID4
|
||||
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
class CreateWebhook(CamelModel):
|
||||
|
||||
class CreateWebhook(MealieModel):
|
||||
enabled: bool = True
|
||||
name: str = ""
|
||||
url: str = ""
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from fastapi_camelcase import CamelModel
|
||||
from pydantic import UUID4
|
||||
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
class MultiPurposeLabelCreate(CamelModel):
|
||||
|
||||
class MultiPurposeLabelCreate(MealieModel):
|
||||
name: str
|
||||
color: str = "#E0E0E0"
|
||||
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
from datetime import date
|
||||
from typing import Optional
|
||||
|
||||
from fastapi_camelcase import CamelModel
|
||||
from pydantic import validator
|
||||
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
class MealIn(CamelModel):
|
||||
|
||||
class MealIn(MealieModel):
|
||||
slug: Optional[str]
|
||||
name: Optional[str]
|
||||
description: Optional[str]
|
||||
|
@ -14,7 +15,7 @@ class MealIn(CamelModel):
|
|||
orm_mode = True
|
||||
|
||||
|
||||
class MealDayIn(CamelModel):
|
||||
class MealDayIn(MealieModel):
|
||||
date: Optional[date]
|
||||
meals: list[MealIn]
|
||||
|
||||
|
@ -29,7 +30,7 @@ class MealDayOut(MealDayIn):
|
|||
orm_mode = True
|
||||
|
||||
|
||||
class MealPlanIn(CamelModel):
|
||||
class MealPlanIn(MealieModel):
|
||||
group: str
|
||||
start_date: date
|
||||
end_date: date
|
||||
|
|
|
@ -3,9 +3,9 @@ from enum import Enum
|
|||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi_camelcase import CamelModel
|
||||
from pydantic import validator
|
||||
|
||||
from mealie.schema._mealie import MealieModel
|
||||
from mealie.schema.recipe.recipe import RecipeSummary
|
||||
|
||||
|
||||
|
@ -16,12 +16,12 @@ class PlanEntryType(str, Enum):
|
|||
side = "side"
|
||||
|
||||
|
||||
class CreatRandomEntry(CamelModel):
|
||||
class CreatRandomEntry(MealieModel):
|
||||
date: date
|
||||
entry_type: PlanEntryType = PlanEntryType.dinner
|
||||
|
||||
|
||||
class CreatePlanEntry(CamelModel):
|
||||
class CreatePlanEntry(MealieModel):
|
||||
date: date
|
||||
entry_type: PlanEntryType = PlanEntryType.breakfast
|
||||
title: str = ""
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import datetime
|
||||
from enum import Enum
|
||||
|
||||
from fastapi_camelcase import CamelModel
|
||||
from pydantic import UUID4
|
||||
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
class Category(CamelModel):
|
||||
|
||||
class Category(MealieModel):
|
||||
id: UUID4
|
||||
name: str
|
||||
slug: str
|
||||
|
@ -46,7 +47,7 @@ class PlanRulesType(str, Enum):
|
|||
unset = "unset"
|
||||
|
||||
|
||||
class PlanRulesCreate(CamelModel):
|
||||
class PlanRulesCreate(MealieModel):
|
||||
day: PlanRulesDay = PlanRulesDay.unset
|
||||
entry_type: PlanRulesType = PlanRulesType.unset
|
||||
categories: list[Category] = []
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
from typing import Optional
|
||||
|
||||
from fastapi_camelcase import CamelModel
|
||||
from pydantic.utils import GetterDict
|
||||
|
||||
from mealie.db.models.group.shopping_list import ShoppingList
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
|
||||
class ListItem(CamelModel):
|
||||
class ListItem(MealieModel):
|
||||
title: Optional[str]
|
||||
text: str = ""
|
||||
quantity: int = 1
|
||||
|
@ -16,7 +16,7 @@ class ListItem(CamelModel):
|
|||
orm_mode = True
|
||||
|
||||
|
||||
class ShoppingListIn(CamelModel):
|
||||
class ShoppingListIn(MealieModel):
|
||||
name: str
|
||||
group: Optional[str]
|
||||
items: list[ListItem]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from fastapi_camelcase import CamelModel
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
|
||||
class GetAll(CamelModel):
|
||||
class GetAll(MealieModel):
|
||||
start: int = 0
|
||||
limit: int = 999
|
||||
|
|
|
@ -5,13 +5,13 @@ from pathlib import Path
|
|||
from typing import Any, Optional
|
||||
from uuid import uuid4
|
||||
|
||||
from fastapi_camelcase import CamelModel
|
||||
from pydantic import UUID4, BaseModel, Field, validator
|
||||
from pydantic.utils import GetterDict
|
||||
from slugify import slugify
|
||||
|
||||
from mealie.core.config import get_app_dirs
|
||||
from mealie.db.models.recipe.recipe import RecipeModel
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
from .recipe_asset import RecipeAsset
|
||||
from .recipe_comments import RecipeCommentOut
|
||||
|
@ -23,7 +23,7 @@ from .recipe_step import RecipeStep
|
|||
app_dirs = get_app_dirs()
|
||||
|
||||
|
||||
class RecipeTag(CamelModel):
|
||||
class RecipeTag(MealieModel):
|
||||
id: UUID4 = None
|
||||
name: str
|
||||
slug: str
|
||||
|
@ -58,11 +58,11 @@ class CreateRecipeByUrlBulk(BaseModel):
|
|||
imports: list[CreateRecipeBulk]
|
||||
|
||||
|
||||
class CreateRecipe(CamelModel):
|
||||
class CreateRecipe(MealieModel):
|
||||
name: str
|
||||
|
||||
|
||||
class RecipeSummary(CamelModel):
|
||||
class RecipeSummary(MealieModel):
|
||||
id: Optional[UUID4]
|
||||
|
||||
user_id: UUID4 = Field(default_factory=uuid4)
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
from typing import Optional
|
||||
|
||||
from fastapi_camelcase import CamelModel
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
|
||||
class RecipeAsset(CamelModel):
|
||||
class RecipeAsset(MealieModel):
|
||||
name: str
|
||||
icon: str
|
||||
file_name: Optional[str]
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import enum
|
||||
|
||||
from fastapi_camelcase import CamelModel
|
||||
|
||||
from mealie.schema._mealie import MealieModel
|
||||
from mealie.schema.recipe.recipe_category import CategoryBase, TagBase
|
||||
|
||||
|
||||
|
@ -9,7 +8,7 @@ class ExportTypes(str, enum.Enum):
|
|||
JSON = "json"
|
||||
|
||||
|
||||
class ExportBase(CamelModel):
|
||||
class ExportBase(MealieModel):
|
||||
recipes: list[str]
|
||||
|
||||
|
||||
|
@ -29,12 +28,12 @@ class DeleteRecipes(ExportBase):
|
|||
pass
|
||||
|
||||
|
||||
class BulkActionError(CamelModel):
|
||||
class BulkActionError(MealieModel):
|
||||
recipe: str
|
||||
error: str
|
||||
|
||||
|
||||
class BulkActionsResponse(CamelModel):
|
||||
class BulkActionsResponse(MealieModel):
|
||||
success: bool
|
||||
message: str
|
||||
errors: list[BulkActionError] = []
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
from fastapi_camelcase import CamelModel
|
||||
from pydantic import UUID4
|
||||
from pydantic.utils import GetterDict
|
||||
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
class CategoryIn(CamelModel):
|
||||
|
||||
class CategoryIn(MealieModel):
|
||||
name: str
|
||||
|
||||
|
||||
|
|
|
@ -2,11 +2,12 @@ from datetime import datetime
|
|||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi_camelcase import CamelModel
|
||||
from pydantic import UUID4
|
||||
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
class UserBase(CamelModel):
|
||||
|
||||
class UserBase(MealieModel):
|
||||
id: int
|
||||
username: Optional[str]
|
||||
admin: bool
|
||||
|
@ -15,7 +16,7 @@ class UserBase(CamelModel):
|
|||
orm_mode = True
|
||||
|
||||
|
||||
class RecipeCommentCreate(CamelModel):
|
||||
class RecipeCommentCreate(MealieModel):
|
||||
recipe_id: UUID4
|
||||
text: str
|
||||
|
||||
|
@ -24,7 +25,7 @@ class RecipeCommentSave(RecipeCommentCreate):
|
|||
user_id: UUID4
|
||||
|
||||
|
||||
class RecipeCommentUpdate(CamelModel):
|
||||
class RecipeCommentUpdate(MealieModel):
|
||||
id: UUID
|
||||
text: str
|
||||
|
||||
|
|
|
@ -4,13 +4,13 @@ import enum
|
|||
from typing import Optional, Union
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
from fastapi_camelcase import CamelModel
|
||||
from pydantic import UUID4, Field
|
||||
|
||||
from mealie.schema._mealie import MealieModel
|
||||
from mealie.schema._mealie.types import NoneFloat
|
||||
|
||||
|
||||
class UnitFoodBase(CamelModel):
|
||||
class UnitFoodBase(MealieModel):
|
||||
name: str
|
||||
description: str = ""
|
||||
|
||||
|
@ -47,7 +47,7 @@ class IngredientUnit(CreateIngredientUnit):
|
|||
orm_mode = True
|
||||
|
||||
|
||||
class RecipeIngredient(CamelModel):
|
||||
class RecipeIngredient(MealieModel):
|
||||
title: Optional[str]
|
||||
note: Optional[str]
|
||||
unit: Optional[Union[IngredientUnit, CreateIngredientUnit]]
|
||||
|
@ -64,7 +64,7 @@ class RecipeIngredient(CamelModel):
|
|||
orm_mode = True
|
||||
|
||||
|
||||
class IngredientConfidence(CamelModel):
|
||||
class IngredientConfidence(MealieModel):
|
||||
average: NoneFloat = None
|
||||
comment: NoneFloat = None
|
||||
name: NoneFloat = None
|
||||
|
@ -73,7 +73,7 @@ class IngredientConfidence(CamelModel):
|
|||
food: NoneFloat = None
|
||||
|
||||
|
||||
class ParsedIngredient(CamelModel):
|
||||
class ParsedIngredient(MealieModel):
|
||||
input: Optional[str]
|
||||
confidence: IngredientConfidence = IngredientConfidence()
|
||||
ingredient: RecipeIngredient
|
||||
|
@ -84,12 +84,12 @@ class RegisteredParser(str, enum.Enum):
|
|||
brute = "brute"
|
||||
|
||||
|
||||
class IngredientsRequest(CamelModel):
|
||||
class IngredientsRequest(MealieModel):
|
||||
parser: RegisteredParser = RegisteredParser.nlp
|
||||
ingredients: list[str]
|
||||
|
||||
|
||||
class IngredientRequest(CamelModel):
|
||||
class IngredientRequest(MealieModel):
|
||||
parser: RegisteredParser = RegisteredParser.nlp
|
||||
ingredient: str
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
from typing import Optional
|
||||
|
||||
from fastapi_camelcase import CamelModel
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
|
||||
class Nutrition(CamelModel):
|
||||
class Nutrition(MealieModel):
|
||||
calories: Optional[str]
|
||||
fat_content: Optional[str]
|
||||
protein_content: Optional[str]
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from fastapi_camelcase import CamelModel
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
|
||||
class RecipeSettings(CamelModel):
|
||||
class RecipeSettings(MealieModel):
|
||||
public: bool = False
|
||||
show_nutrition: bool = False
|
||||
show_assets: bool = False
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
from datetime import datetime, timedelta
|
||||
|
||||
from fastapi_camelcase import CamelModel
|
||||
from pydantic import UUID4, Field
|
||||
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
from .recipe import Recipe
|
||||
|
||||
|
||||
|
@ -10,7 +11,7 @@ def defaut_expires_at_time() -> datetime:
|
|||
return datetime.utcnow() + timedelta(days=30)
|
||||
|
||||
|
||||
class RecipeShareTokenCreate(CamelModel):
|
||||
class RecipeShareTokenCreate(MealieModel):
|
||||
recipe_id: UUID4
|
||||
expires_at: datetime = Field(default_factory=defaut_expires_at_time)
|
||||
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
from typing import Optional
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
from fastapi_camelcase import CamelModel
|
||||
from pydantic import UUID4, Field
|
||||
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
class IngredientReferences(CamelModel):
|
||||
|
||||
class IngredientReferences(MealieModel):
|
||||
"""
|
||||
A list of ingredient references.
|
||||
"""
|
||||
|
@ -16,7 +17,7 @@ class IngredientReferences(CamelModel):
|
|||
orm_mode = True
|
||||
|
||||
|
||||
class RecipeStep(CamelModel):
|
||||
class RecipeStep(MealieModel):
|
||||
id: Optional[UUID] = Field(default_factory=uuid4)
|
||||
title: Optional[str] = ""
|
||||
text: str
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import typing
|
||||
|
||||
from fastapi_camelcase import CamelModel
|
||||
from pydantic import UUID4
|
||||
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
class RecipeToolCreate(CamelModel):
|
||||
|
||||
class RecipeToolCreate(MealieModel):
|
||||
name: str
|
||||
on_hand: bool = False
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
from fastapi_camelcase import CamelModel
|
||||
from pydantic import BaseModel
|
||||
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
# TODO: Should these exist?!?!?!?!?
|
||||
|
||||
|
||||
class RecipeSlug(CamelModel):
|
||||
class RecipeSlug(MealieModel):
|
||||
slug: str
|
||||
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import datetime
|
||||
import enum
|
||||
|
||||
from fastapi_camelcase import CamelModel
|
||||
from pydantic import Field
|
||||
from pydantic.types import UUID4
|
||||
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
|
||||
class ReportCategory(str, enum.Enum):
|
||||
backup = "backup"
|
||||
|
@ -19,7 +20,7 @@ class ReportSummaryStatus(str, enum.Enum):
|
|||
partial = "partial"
|
||||
|
||||
|
||||
class ReportEntryCreate(CamelModel):
|
||||
class ReportEntryCreate(MealieModel):
|
||||
report_id: UUID4
|
||||
timestamp: datetime.datetime = Field(default_factory=datetime.datetime.utcnow)
|
||||
success: bool = True
|
||||
|
@ -34,7 +35,7 @@ class ReportEntryOut(ReportEntryCreate):
|
|||
orm_mode = True
|
||||
|
||||
|
||||
class ReportCreate(CamelModel):
|
||||
class ReportCreate(MealieModel):
|
||||
timestamp: datetime.datetime = Field(default_factory=datetime.datetime.utcnow)
|
||||
category: ReportCategory
|
||||
group_id: UUID4
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
from typing import Optional
|
||||
|
||||
from fastapi_camelcase import CamelModel
|
||||
from pydantic import BaseModel
|
||||
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
|
||||
class ErrorResponse(BaseModel):
|
||||
message: str
|
||||
|
@ -31,7 +32,7 @@ class SuccessResponse(BaseModel):
|
|||
return cls(message=message).dict()
|
||||
|
||||
|
||||
class FileTokenResponse(CamelModel):
|
||||
class FileTokenResponse(MealieModel):
|
||||
file_token: str
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -2,9 +2,10 @@ import datetime
|
|||
import enum
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi_camelcase import CamelModel
|
||||
from pydantic import Field
|
||||
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
|
||||
class ServerTaskNames(str, enum.Enum):
|
||||
default = "Background Task"
|
||||
|
@ -18,7 +19,7 @@ class ServerTaskStatus(str, enum.Enum):
|
|||
failed = "failed"
|
||||
|
||||
|
||||
class ServerTaskCreate(CamelModel):
|
||||
class ServerTaskCreate(MealieModel):
|
||||
group_id: UUID
|
||||
name: ServerTaskNames = ServerTaskNames.default
|
||||
created_at: datetime.datetime = Field(default_factory=datetime.datetime.now)
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
from fastapi_camelcase import CamelModel
|
||||
from pydantic import validator
|
||||
from pydantic.types import NoneStr, constr
|
||||
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
class CreateUserRegistration(CamelModel):
|
||||
|
||||
class CreateUserRegistration(MealieModel):
|
||||
group: NoneStr = None
|
||||
group_token: NoneStr = None
|
||||
email: constr(to_lower=True, strip_whitespace=True) # type: ignore
|
||||
|
|
|
@ -3,13 +3,13 @@ from pathlib import Path
|
|||
from typing import Any, Optional
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi_camelcase import CamelModel
|
||||
from pydantic import UUID4
|
||||
from pydantic.types import constr
|
||||
from pydantic.utils import GetterDict
|
||||
|
||||
from mealie.core.config import get_app_dirs, get_app_settings
|
||||
from mealie.db.models.users import User
|
||||
from mealie.schema._mealie import MealieModel
|
||||
from mealie.schema.group.group_preferences import ReadGroupPreferences
|
||||
from mealie.schema.recipe import RecipeSummary
|
||||
|
||||
|
@ -18,7 +18,7 @@ from ..recipe import CategoryBase
|
|||
settings = get_app_settings()
|
||||
|
||||
|
||||
class LoingLiveTokenIn(CamelModel):
|
||||
class LoingLiveTokenIn(MealieModel):
|
||||
name: str
|
||||
|
||||
|
||||
|
@ -38,19 +38,19 @@ class CreateToken(LoingLiveTokenIn):
|
|||
orm_mode = True
|
||||
|
||||
|
||||
class ChangePassword(CamelModel):
|
||||
class ChangePassword(MealieModel):
|
||||
current_password: str
|
||||
new_password: str
|
||||
|
||||
|
||||
class GroupBase(CamelModel):
|
||||
class GroupBase(MealieModel):
|
||||
name: str
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class UserBase(CamelModel):
|
||||
class UserBase(MealieModel):
|
||||
username: Optional[str]
|
||||
full_name: Optional[str] = None
|
||||
email: constr(to_lower=True, strip_whitespace=True) # type: ignore
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
from fastapi_camelcase import CamelModel
|
||||
from pydantic import UUID4
|
||||
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
from .user import PrivateUser
|
||||
|
||||
|
||||
class ForgotPassword(CamelModel):
|
||||
class ForgotPassword(MealieModel):
|
||||
email: str
|
||||
|
||||
|
||||
class ValidateResetToken(CamelModel):
|
||||
class ValidateResetToken(MealieModel):
|
||||
token: str
|
||||
|
||||
|
||||
|
@ -18,7 +19,7 @@ class ResetPassword(ValidateResetToken):
|
|||
passwordConfirm: str
|
||||
|
||||
|
||||
class SavePasswordResetToken(CamelModel):
|
||||
class SavePasswordResetToken(MealieModel):
|
||||
user_id: UUID4
|
||||
token: str
|
||||
|
||||
|
|
|
@ -1,143 +0,0 @@
|
|||
import json
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Union
|
||||
|
||||
from jinja2 import Template
|
||||
from pathvalidate import sanitize_filename
|
||||
from pydantic.main import BaseModel
|
||||
|
||||
from mealie.core import root_logger
|
||||
from mealie.core.config import get_app_dirs
|
||||
|
||||
app_dirs = get_app_dirs()
|
||||
from mealie.repos.all_repositories import get_repositories
|
||||
|
||||
logger = root_logger.get_logger()
|
||||
|
||||
|
||||
class ExportDatabase:
|
||||
def __init__(self, tag=None, templates=None) -> None:
|
||||
"""Export a Mealie database. Export interacts directly with class objects and can be used
|
||||
with any supported backend database platform. By default tags are timestamps, and no
|
||||
Jinja2 templates are rendered
|
||||
|
||||
Args:
|
||||
tag ([str], optional): A str to be used as a file tag. Defaults to None.
|
||||
templates (list, optional): A list of template file names. Defaults to None.
|
||||
"""
|
||||
if tag:
|
||||
export_tag = tag + "_" + datetime.now().strftime("%Y-%b-%d")
|
||||
else:
|
||||
export_tag = datetime.now().strftime("%Y-%b-%d")
|
||||
|
||||
self.main_dir = app_dirs.TEMP_DIR.joinpath(export_tag)
|
||||
self.recipes = self.main_dir.joinpath("recipes")
|
||||
self.templates_dir = self.main_dir.joinpath("templates")
|
||||
|
||||
try:
|
||||
self.templates = [app_dirs.TEMPLATE_DIR.joinpath(x) for x in templates]
|
||||
except Exception:
|
||||
self.templates = []
|
||||
logger.info("No Jinja2 Templates Registered for Export")
|
||||
|
||||
required_dirs = [
|
||||
self.main_dir,
|
||||
self.recipes,
|
||||
self.templates_dir,
|
||||
]
|
||||
|
||||
for dir in required_dirs:
|
||||
dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def export_templates(self, recipe_list: list[BaseModel]):
|
||||
if self.templates:
|
||||
for template_path in self.templates:
|
||||
out_dir = self.templates_dir.joinpath(template_path.name)
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with open(template_path, "r") as f:
|
||||
template = Template(f.read())
|
||||
|
||||
for recipe in recipe_list:
|
||||
filename = recipe.slug + template_path.suffix
|
||||
out_file = out_dir.joinpath(filename)
|
||||
|
||||
content = template.render(recipe=recipe)
|
||||
|
||||
with open(out_file, "w") as f:
|
||||
f.write(content)
|
||||
|
||||
def export_recipe_dirs(self):
|
||||
shutil.copytree(app_dirs.RECIPE_DATA_DIR, self.recipes, dirs_exist_ok=True)
|
||||
|
||||
def export_items(self, items: list[BaseModel], folder_name: str, export_list=True, slug_folder=False):
|
||||
items = [x.dict() for x in items]
|
||||
out_dir = self.main_dir.joinpath(folder_name)
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if export_list:
|
||||
ExportDatabase._write_json_file(items, out_dir.joinpath(f"{folder_name}.json"))
|
||||
else:
|
||||
for item in items:
|
||||
final_dest = out_dir if not slug_folder else out_dir.joinpath(item.get("slug"))
|
||||
final_dest.mkdir(exist_ok=True)
|
||||
filename = sanitize_filename(f"{item.get('slug')}.json")
|
||||
ExportDatabase._write_json_file(item, final_dest.joinpath(filename))
|
||||
|
||||
@staticmethod
|
||||
def _write_json_file(data: Union[dict, list], out_file: Path):
|
||||
json_data = json.dumps(data, indent=4, default=str)
|
||||
|
||||
with open(out_file, "w") as f:
|
||||
f.write(json_data)
|
||||
|
||||
def finish_export(self):
|
||||
zip_path = app_dirs.BACKUP_DIR.joinpath(f"{self.main_dir.name}")
|
||||
shutil.make_archive(zip_path, "zip", self.main_dir)
|
||||
|
||||
shutil.rmtree(app_dirs.TEMP_DIR, ignore_errors=True)
|
||||
return str(zip_path.absolute()) + ".zip"
|
||||
|
||||
|
||||
def backup_all(
|
||||
session,
|
||||
tag=None,
|
||||
templates=None,
|
||||
export_recipes=True,
|
||||
export_settings=False,
|
||||
export_users=True,
|
||||
export_groups=True,
|
||||
export_notifications=True,
|
||||
):
|
||||
db_export = ExportDatabase(tag=tag, templates=templates)
|
||||
|
||||
db = get_repositories(session)
|
||||
|
||||
if export_users:
|
||||
all_users = db.users.get_all()
|
||||
db_export.export_items(all_users, "users")
|
||||
|
||||
if export_groups:
|
||||
all_groups = db.groups.get_all()
|
||||
db_export.export_items(all_groups, "groups")
|
||||
|
||||
if export_recipes:
|
||||
all_recipes = db.recipes.get_all()
|
||||
db_export.export_recipe_dirs()
|
||||
db_export.export_items(all_recipes, "recipes", export_list=False, slug_folder=True)
|
||||
db_export.export_templates(all_recipes)
|
||||
|
||||
all_comments = db.comments.get_all()
|
||||
db_export.export_items(all_comments, "comments")
|
||||
|
||||
if export_settings:
|
||||
all_settings = db.settings.get_all()
|
||||
db_export.export_items(all_settings, "settings")
|
||||
|
||||
if export_notifications:
|
||||
all_notifications = db.event_notifications.get_all()
|
||||
db_export.export_items(all_notifications, "notifications")
|
||||
|
||||
return db_export.finish_export()
|
|
@ -1,307 +0,0 @@
|
|||
import json
|
||||
import shutil
|
||||
import zipfile
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
from pydantic.main import BaseModel
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from mealie.core.config import get_app_dirs
|
||||
from mealie.repos.all_repositories import get_repositories
|
||||
from mealie.schema.admin import CommentImport, GroupImport, RecipeImport, UserImport
|
||||
from mealie.schema.recipe import Recipe, RecipeCommentOut
|
||||
from mealie.schema.user import PrivateUser, UpdateGroup
|
||||
|
||||
app_dirs = get_app_dirs()
|
||||
|
||||
|
||||
class ImportDatabase:
|
||||
def __init__(
|
||||
self,
|
||||
user: PrivateUser,
|
||||
session: Session,
|
||||
zip_archive: str,
|
||||
force_import: bool = False,
|
||||
) -> None:
|
||||
"""Import a database.zip file exported from mealie.
|
||||
|
||||
Args:
|
||||
session (Session): SqlAlchemy Session
|
||||
zip_archive (str): The filename contained in the backups directory
|
||||
force_import (bool, optional): Force import will update all existing recipes. If False existing recipes are skipped. Defaults to False.
|
||||
|
||||
Raises:
|
||||
Exception: If the zip file does not exists an exception raise.
|
||||
"""
|
||||
self.user = user
|
||||
self.session = session
|
||||
self.db = get_repositories(session)
|
||||
self.archive = app_dirs.BACKUP_DIR.joinpath(zip_archive)
|
||||
self.force_imports = force_import
|
||||
|
||||
if self.archive.is_file():
|
||||
self.import_dir = app_dirs.TEMP_DIR.joinpath("active_import")
|
||||
self.import_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with zipfile.ZipFile(self.archive, "r") as zip_ref:
|
||||
zip_ref.extractall(self.import_dir)
|
||||
else:
|
||||
raise Exception("Import file does not exist")
|
||||
|
||||
def import_recipes(self):
|
||||
recipe_dir: Path = self.import_dir.joinpath("recipes")
|
||||
imports = []
|
||||
successful_imports = {}
|
||||
|
||||
recipes = ImportDatabase.read_models_file(
|
||||
file_path=recipe_dir,
|
||||
model=Recipe,
|
||||
single_file=False,
|
||||
migrate=ImportDatabase._recipe_migration,
|
||||
)
|
||||
|
||||
for recipe in recipes:
|
||||
recipe: Recipe
|
||||
|
||||
recipe.group_id = self.user.group_id
|
||||
recipe.user_id = self.user.id
|
||||
|
||||
import_status = self.import_model(
|
||||
db_table=self.db.recipes,
|
||||
model=recipe,
|
||||
return_model=RecipeImport,
|
||||
name_attr="name",
|
||||
search_key="slug",
|
||||
slug=recipe.slug,
|
||||
)
|
||||
|
||||
if import_status.status:
|
||||
successful_imports[recipe.slug] = recipe
|
||||
|
||||
imports.append(import_status)
|
||||
|
||||
self._import_images(successful_imports)
|
||||
|
||||
return imports
|
||||
|
||||
def import_comments(self):
|
||||
comment_dir: Path = self.import_dir.joinpath("comments", "comments.json")
|
||||
|
||||
if not comment_dir.exists():
|
||||
return
|
||||
|
||||
comments = ImportDatabase.read_models_file(file_path=comment_dir, model=RecipeCommentOut)
|
||||
|
||||
for comment in comments:
|
||||
comment: RecipeCommentOut
|
||||
|
||||
self.import_model(
|
||||
db_table=self.db.comments,
|
||||
model=comment,
|
||||
return_model=CommentImport,
|
||||
name_attr="uuid",
|
||||
search_key="uuid",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _recipe_migration(recipe_dict: dict) -> dict:
|
||||
if recipe_dict.get("categories", False):
|
||||
recipe_dict["recipeCategory"] = recipe_dict.get("categories")
|
||||
del recipe_dict["categories"]
|
||||
try:
|
||||
del recipe_dict["_id"]
|
||||
del recipe_dict["date_added"]
|
||||
except Exception:
|
||||
pass
|
||||
# Migration from list to Object Type Data
|
||||
try:
|
||||
if "" in recipe_dict["tags"]:
|
||||
recipe_dict["tags"] = [tag for tag in recipe_dict["tags"] if tag != ""]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
if "" in recipe_dict["categories"]:
|
||||
recipe_dict["categories"] = [cat for cat in recipe_dict["categories"] if cat != ""]
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if type(recipe_dict["extras"]) == list:
|
||||
recipe_dict["extras"] = {}
|
||||
|
||||
recipe_dict["comments"] = []
|
||||
|
||||
return recipe_dict
|
||||
|
||||
def _import_images(self, successful_imports: list[Recipe]):
|
||||
image_dir = self.import_dir.joinpath("images")
|
||||
|
||||
if image_dir.exists(): # Migrate from before v0.5.0
|
||||
for image in image_dir.iterdir():
|
||||
item: Recipe = successful_imports.get(image.stem) # type: ignore
|
||||
|
||||
if item:
|
||||
dest_dir = item.image_dir
|
||||
|
||||
if image.is_dir():
|
||||
shutil.copytree(image, dest_dir, dirs_exist_ok=True)
|
||||
|
||||
if image.is_file():
|
||||
shutil.copy(image, dest_dir)
|
||||
|
||||
else:
|
||||
recipe_dir = self.import_dir.joinpath("recipes")
|
||||
shutil.copytree(recipe_dir, app_dirs.RECIPE_DATA_DIR, dirs_exist_ok=True)
|
||||
|
||||
def import_settings(self):
|
||||
return []
|
||||
|
||||
def import_groups(self):
|
||||
groups_file = self.import_dir.joinpath("groups", "groups.json")
|
||||
groups = ImportDatabase.read_models_file(groups_file, UpdateGroup)
|
||||
group_imports = []
|
||||
|
||||
for group in groups:
|
||||
import_status = self.import_model(self.db.groups, group, GroupImport, search_key="name")
|
||||
group_imports.append(import_status)
|
||||
|
||||
return group_imports
|
||||
|
||||
def import_users(self):
|
||||
users_file = self.import_dir.joinpath("users", "users.json")
|
||||
users = ImportDatabase.read_models_file(users_file, PrivateUser)
|
||||
user_imports = []
|
||||
for user in users:
|
||||
if user.id == 1: # Update Default User
|
||||
self.db.users.update(1, user.dict())
|
||||
import_status = UserImport(name=user.full_name, status=True)
|
||||
user_imports.append(import_status)
|
||||
continue
|
||||
|
||||
import_status = self.import_model(
|
||||
db_table=self.db.users,
|
||||
model=user,
|
||||
return_model=UserImport,
|
||||
name_attr="full_name",
|
||||
search_key="email",
|
||||
)
|
||||
|
||||
user_imports.append(import_status)
|
||||
|
||||
return user_imports
|
||||
|
||||
@staticmethod
|
||||
def read_models_file(file_path: Path, model: BaseModel, single_file=True, migrate: Callable = None):
|
||||
"""A general purpose function that is used to process a backup `.json` file created by mealie
|
||||
note that if the file doesn't not exists the function will return any empty list
|
||||
|
||||
Args:
|
||||
file_path (Path): The path to the .json file or directory
|
||||
model (BaseModel): The pydantic model that will be created from the .json file entries
|
||||
single_file (bool, optional): If true, the json data will be treated as list, if false it will use glob style matches and treat each file as its own entry. Defaults to True.
|
||||
migrate (Callable, optional): A migrate function that will be called on the data prior to creating a model. Defaults to None.
|
||||
|
||||
Returns:
|
||||
[type]: [description]
|
||||
"""
|
||||
if not file_path.exists():
|
||||
return []
|
||||
|
||||
if single_file:
|
||||
with open(file_path, "r") as f:
|
||||
file_data = json.loads(f.read())
|
||||
|
||||
if migrate:
|
||||
file_data = [migrate(x) for x in file_data]
|
||||
|
||||
return [model(**g) for g in file_data]
|
||||
|
||||
all_models = []
|
||||
for file in file_path.glob("**/*.json"):
|
||||
with open(file, "r") as f:
|
||||
file_data = json.loads(f.read())
|
||||
|
||||
if migrate:
|
||||
file_data = migrate(file_data)
|
||||
|
||||
all_models.append(model(**file_data))
|
||||
|
||||
return all_models
|
||||
|
||||
def import_model(self, db_table, model, return_model, name_attr="name", search_key="id", **kwargs):
|
||||
"""A general purpose function used to insert a list of pydantic modelsi into the database.
|
||||
The assumption at this point is that the models that are inserted. If self.force_imports is true
|
||||
any existing entries will be removed prior to creation
|
||||
|
||||
Args:
|
||||
db_table ([type]): A database table like `db.users`
|
||||
model ([type]): The Pydantic model that matches the database
|
||||
return_model ([type]): The return model that will be used for the 'report'
|
||||
name_attr (str, optional): The name property on the return model. Defaults to "name".
|
||||
search_key (str, optional): The key used to identify if an the entry already exists. Defaults to "id"
|
||||
**kwargs (): Any kwargs passed will be used to set attributes on the `return_model`
|
||||
|
||||
Returns:
|
||||
[type]: Returns the `return_model` specified.
|
||||
"""
|
||||
model_name = getattr(model, name_attr)
|
||||
search_value = getattr(model, search_key)
|
||||
|
||||
item = db_table.get(search_value, search_key)
|
||||
if item:
|
||||
if not self.force_imports:
|
||||
return return_model(
|
||||
name=model_name,
|
||||
status=False,
|
||||
exception=f"Table entry with matching '{search_key}': '{search_value}' exists",
|
||||
)
|
||||
|
||||
primary_key = getattr(item, db_table.primary_key)
|
||||
db_table.delete(primary_key)
|
||||
try:
|
||||
db_table.create(model.dict())
|
||||
import_status = return_model(name=model_name, status=True)
|
||||
|
||||
except Exception as inst:
|
||||
self.session.rollback()
|
||||
import_status = return_model(name=model_name, status=False, exception=str(inst))
|
||||
|
||||
for key, value in kwargs.items():
|
||||
setattr(return_model, key, value)
|
||||
|
||||
return import_status
|
||||
|
||||
def clean_up(self):
|
||||
shutil.rmtree(app_dirs.TEMP_DIR)
|
||||
|
||||
|
||||
def import_database(
|
||||
session: Session,
|
||||
user: PrivateUser,
|
||||
archive,
|
||||
import_recipes=True,
|
||||
import_settings=True,
|
||||
import_users=True,
|
||||
import_groups=True,
|
||||
force_import: bool = False,
|
||||
**_,
|
||||
):
|
||||
import_session = ImportDatabase(user, session, archive, force_import)
|
||||
|
||||
recipe_report = import_session.import_recipes() if import_recipes else []
|
||||
settings_report = import_session.import_settings() if import_settings else []
|
||||
group_report = import_session.import_groups() if import_groups else []
|
||||
user_report = import_session.import_users() if import_users else []
|
||||
notification_report: list = []
|
||||
|
||||
import_session.clean_up()
|
||||
|
||||
return {
|
||||
"recipeImports": recipe_report,
|
||||
"settingsImports": settings_report,
|
||||
"groupImports": group_report,
|
||||
"userImports": user_report,
|
||||
"notificationImports": notification_report,
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
from .auto_backup import *
|
||||
from .purge_group_exports import *
|
||||
from .purge_password_reset import *
|
||||
from .purge_registration import *
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
from mealie.core import root_logger
|
||||
from mealie.core.config import get_app_dirs
|
||||
|
||||
app_dirs = get_app_dirs()
|
||||
from mealie.db.db_setup import create_session
|
||||
from mealie.services.backups.exports import backup_all
|
||||
|
||||
logger = root_logger.get_logger()
|
||||
|
||||
|
||||
def auto_backup():
|
||||
for backup in app_dirs.BACKUP_DIR.glob("Auto*.zip"):
|
||||
backup.unlink()
|
||||
|
||||
templates = [template for template in app_dirs.TEMPLATE_DIR.iterdir()]
|
||||
session = create_session()
|
||||
backup_all(session=session, tag="Auto", templates=templates)
|
||||
logger.info("generating automated backup")
|
||||
session.close()
|
||||
logger.info("automated backup generated")
|
38
poetry.lock
generated
38
poetry.lock
generated
|
@ -390,18 +390,6 @@ dev = ["python-jose[cryptography] (>=3.3.0,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<
|
|||
doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "typer-cli (>=0.0.12,<0.0.13)", "pyyaml (>=5.3.1,<6.0.0)"]
|
||||
test = ["pytest (>=6.2.4,<7.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "mypy (==0.910)", "flake8 (>=3.8.3,<4.0.0)", "black (==21.9b0)", "isort (>=5.0.6,<6.0.0)", "requests (>=2.24.0,<3.0.0)", "httpx (>=0.14.0,<0.19.0)", "email_validator (>=1.1.1,<2.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "peewee (>=3.13.3,<4.0.0)", "databases[sqlite] (>=0.3.2,<0.6.0)", "orjson (>=3.2.1,<4.0.0)", "ujson (>=4.0.1,<5.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "flask (>=1.1.2,<3.0.0)", "anyio[trio] (>=3.2.1,<4.0.0)", "types-ujson (==0.1.1)", "types-orjson (==3.6.0)", "types-dataclasses (==0.1.7)"]
|
||||
|
||||
[[package]]
|
||||
name = "fastapi-camelcase"
|
||||
version = "1.0.5"
|
||||
description = "Package provides an easy way to have camelcase request/response bodies for Pydantic"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
pydantic = "*"
|
||||
pyhumps = "*"
|
||||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
version = "3.6.0"
|
||||
|
@ -829,17 +817,6 @@ category = "dev"
|
|||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
|
||||
|
||||
[[package]]
|
||||
name = "pathvalidate"
|
||||
version = "2.5.0"
|
||||
description = "pathvalidate is a Python library to sanitize/validate a string such as filenames/file-paths/etc."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.extras]
|
||||
test = ["allpairspy", "click", "faker", "pytest (>=6.0.1)", "pytest-discord (>=0.0.6)", "pytest-md-report (>=0.0.12)"]
|
||||
|
||||
[[package]]
|
||||
name = "pillow"
|
||||
version = "8.4.0"
|
||||
|
@ -1003,7 +980,7 @@ python-versions = ">=3.5"
|
|||
|
||||
[[package]]
|
||||
name = "pyhumps"
|
||||
version = "3.5.0"
|
||||
version = "3.5.3"
|
||||
description = "🐫 Convert strings (and dictionary keys) between snake case, camel case and pascal case in Python. Inspired by Humps for Node"
|
||||
category = "main"
|
||||
optional = false
|
||||
|
@ -1622,7 +1599,7 @@ pgsql = ["psycopg2-binary"]
|
|||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.10"
|
||||
content-hash = "4fba071019a62f5d75e7c9a297a7815b2fed6486bb3616b5029a6fb08001761f"
|
||||
content-hash = "84c1d9352c058da5cc0f50ca195cbe0897ce64abfbe01d08b9da317b6dd70a70"
|
||||
|
||||
[metadata.files]
|
||||
aiofiles = [
|
||||
|
@ -1852,9 +1829,6 @@ fastapi = [
|
|||
{file = "fastapi-0.74.1-py3-none-any.whl", hash = "sha256:b8ec8400623ef0b2ff558ebe06753b349f8e3a5dd38afea650800f2644ddba34"},
|
||||
{file = "fastapi-0.74.1.tar.gz", hash = "sha256:b58a2c46df14f62ebe6f24a9439927539ba1959b9be55ba0e2f516a683e5b9d4"},
|
||||
]
|
||||
fastapi-camelcase = [
|
||||
{file = "fastapi_camelcase-1.0.5.tar.gz", hash = "sha256:2cee005fb1b75649491b9f7cfccc640b12f028eb88084565f7d8cf415192026a"},
|
||||
]
|
||||
filelock = [
|
||||
{file = "filelock-3.6.0-py3-none-any.whl", hash = "sha256:f8314284bfffbdcfa0ff3d7992b023d4c628ced6feb957351d4c48d059f56bc0"},
|
||||
{file = "filelock-3.6.0.tar.gz", hash = "sha256:9cd540a9352e432c7246a48fe4e8712b10acb1df2ad1f30e8c070b82ae1fed85"},
|
||||
|
@ -2239,10 +2213,6 @@ pathspec = [
|
|||
{file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
|
||||
{file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
|
||||
]
|
||||
pathvalidate = [
|
||||
{file = "pathvalidate-2.5.0-py3-none-any.whl", hash = "sha256:e5b2747ad557363e8f4124f0553d68878b12ecabd77bcca7e7312d5346d20262"},
|
||||
{file = "pathvalidate-2.5.0.tar.gz", hash = "sha256:119ba36be7e9a405d704c7b7aea4b871c757c53c9adc0ed64f40be1ed8da2781"},
|
||||
]
|
||||
pillow = [
|
||||
{file = "Pillow-8.4.0-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:81f8d5c81e483a9442d72d182e1fb6dcb9723f289a57e8030811bac9ea3fef8d"},
|
||||
{file = "Pillow-8.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3f97cfb1e5a392d75dd8b9fd274d205404729923840ca94ca45a0af57e13dbe6"},
|
||||
|
@ -2453,8 +2423,8 @@ pygments = [
|
|||
{file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"},
|
||||
]
|
||||
pyhumps = [
|
||||
{file = "pyhumps-3.5.0-py3-none-any.whl", hash = "sha256:2433eef13d1c258227a0bd5de9660ba17dd6a307e1255d2d20ec9287f8626d96"},
|
||||
{file = "pyhumps-3.5.0.tar.gz", hash = "sha256:55e37f16846eaab26057200924cbdadd2152bf0a5d49175a42358464fa881c73"},
|
||||
{file = "pyhumps-3.5.3-py3-none-any.whl", hash = "sha256:8d7e9865d6ddb6e64a2e97d951b78b5cc827d3d66cda1297310fc83b2ddf51dc"},
|
||||
{file = "pyhumps-3.5.3.tar.gz", hash = "sha256:0ecf7fee84503b45afdd3841ec769b529d32dfaed855e07046ff8babcc0ab831"},
|
||||
]
|
||||
pylint = [
|
||||
{file = "pylint-2.12.2-py3-none-any.whl", hash = "sha256:daabda3f7ed9d1c60f52d563b1b854632fd90035bcf01443e234d3dc794e3b74"},
|
||||
|
|
|
@ -25,13 +25,11 @@ requests = "^2.25.1"
|
|||
PyYAML = "^5.3.1"
|
||||
extruct = "^0.13.0"
|
||||
python-multipart = "^0.0.5"
|
||||
fastapi-camelcase = "^1.0.5"
|
||||
bcrypt = "^3.2.0"
|
||||
python-jose = "^3.3.0"
|
||||
passlib = "^1.7.4"
|
||||
lxml = "^4.7.1"
|
||||
Pillow = "^8.2.0"
|
||||
pathvalidate = "^2.4.1"
|
||||
apprise = "^0.9.6"
|
||||
recipe-scrapers = "^13.18.1"
|
||||
psycopg2-binary = {version = "^2.9.1", optional = true}
|
||||
|
@ -41,6 +39,7 @@ python-i18n = "^0.3.9"
|
|||
python-ldap = "^3.3.1"
|
||||
pydantic = "^1.9.0"
|
||||
tzdata = "^2021.5"
|
||||
pyhumps = "^3.5.3"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
pylint = "^2.6.0"
|
||||
|
|
63
tests/unit_tests/schema_tests/test_mealie_model.py
Normal file
63
tests/unit_tests/schema_tests/test_mealie_model.py
Normal file
|
@ -0,0 +1,63 @@
|
|||
from mealie.schema._mealie.mealie_model import MealieModel
|
||||
|
||||
|
||||
class TestModel(MealieModel):
|
||||
long_name: str
|
||||
long_int: int
|
||||
long_float: float
|
||||
|
||||
|
||||
class TestModel2(MealieModel):
|
||||
long_name: str
|
||||
long_int: int
|
||||
long_float: float
|
||||
another_str: str
|
||||
|
||||
|
||||
def test_camelize_variables():
|
||||
model = TestModel(long_name="Hello", long_int=1, long_float=1.1)
|
||||
|
||||
as_dict = model.dict(by_alias=True)
|
||||
|
||||
assert as_dict["longName"] == "Hello"
|
||||
assert as_dict["longInt"] == 1
|
||||
assert as_dict["longFloat"] == 1.1
|
||||
|
||||
|
||||
def test_cast_to():
|
||||
|
||||
model = TestModel(long_name="Hello", long_int=1, long_float=1.1)
|
||||
|
||||
model2 = model.cast(TestModel2, another_str="World")
|
||||
|
||||
assert model2.long_name == "Hello"
|
||||
assert model2.long_int == 1
|
||||
assert model2.long_float == 1.1
|
||||
assert model2.another_str == "World"
|
||||
|
||||
|
||||
def test_map_to():
|
||||
|
||||
model = TestModel(long_name="Model1", long_int=100, long_float=1.5)
|
||||
|
||||
model2 = TestModel2(long_name="Model2", long_int=1, long_float=1.1, another_str="World")
|
||||
|
||||
model.map_to(model2)
|
||||
|
||||
assert model2.long_name == "Model1"
|
||||
assert model2.long_int == 100
|
||||
assert model2.long_float == 1.5
|
||||
assert model2.another_str == "World"
|
||||
|
||||
|
||||
def test_map_from():
|
||||
model = TestModel(long_name="Model1", long_int=50, long_float=1.5)
|
||||
|
||||
model2 = TestModel2(long_name="Hello", long_int=1, long_float=1.1, another_str="World")
|
||||
|
||||
model2.map_from(model)
|
||||
|
||||
assert model2.long_name == "Model1"
|
||||
assert model2.long_int == 50
|
||||
assert model2.long_float == 1.5
|
||||
assert model2.another_str == "World"
|
Loading…
Reference in a new issue