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:
Hayden 2022-03-25 10:56:49 -08:00 committed by GitHub
parent bcd98cba2f
commit 11b4d2389a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
50 changed files with 253 additions and 623 deletions

View File

@ -96,7 +96,7 @@ def generate_typescript_types() -> None:
try: try:
path_as_module = path_to_module(module) 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: except Exception as e:
failed_modules.append(module) failed_modules.append(module)
print("\nModule Errors:", module, "-----------------") # noqa print("\nModule Errors:", module, "-----------------") # noqa

View File

@ -3,11 +3,12 @@ import pathlib
import _static import _static
import dotenv import dotenv
import requests import requests
from fastapi_camelcase import CamelModel
from jinja2 import Template from jinja2 import Template
from requests import Response from requests import Response
from rich import print from rich import print
from mealie.schema._mealie import MealieModel
BASE = pathlib.Path(__file__).parent.parent.parent BASE = pathlib.Path(__file__).parent.parent.parent
API_KEY = dotenv.get_key(BASE / ".env", "CROWDIN_API_KEY") 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 id: str
name: str name: str
locale: str locale: str

View File

@ -1,22 +1,22 @@
from fastapi import APIRouter from fastapi import APIRouter
from fastapi_camelcase import CamelModel
from mealie.routes._base import BaseAdminController, controller from mealie.routes._base import BaseAdminController, controller
from mealie.schema._mealie import MealieModel
from mealie.services.email import EmailService from mealie.services.email import EmailService
router = APIRouter(prefix="/email") router = APIRouter(prefix="/email")
class EmailReady(CamelModel): class EmailReady(MealieModel):
ready: bool ready: bool
class EmailSuccess(CamelModel): class EmailSuccess(MealieModel):
success: bool success: bool
error: str = None error: str = None
class EmailTest(CamelModel): class EmailTest(MealieModel):
email: str email: str

View File

@ -0,0 +1,2 @@
from .mealie_model import *
from .types import *

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

View File

@ -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_recipes: int
total_users: int total_users: int
total_groups: int total_groups: int
@ -9,7 +9,7 @@ class AppStatistics(CamelModel):
untagged_recipes: int untagged_recipes: int
class AppInfo(CamelModel): class AppInfo(MealieModel):
production: bool production: bool
version: str version: str
demo_status: bool demo_status: bool
@ -26,7 +26,7 @@ class AdminAboutInfo(AppInfo):
build_id: str build_id: str
class CheckAppConfig(CamelModel): class CheckAppConfig(MealieModel):
email_ready: bool = False email_ready: bool = False
ldap_ready: bool = False ldap_ready: bool = False
base_url_set: bool = False base_url_set: bool = False

View File

@ -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 data_dir_size: str
log_file_size: str log_file_size: str
cleanable_images: int cleanable_images: int

View File

@ -1,13 +1,14 @@
from typing import Optional from typing import Optional
from fastapi_camelcase import CamelModel
from pydantic import validator from pydantic import validator
from slugify import slugify from slugify import slugify
from mealie.schema._mealie import MealieModel
from ..recipe.recipe_category import RecipeCategoryResponse from ..recipe.recipe_category import RecipeCategoryResponse
class CustomPageBase(CamelModel): class CustomPageBase(MealieModel):
name: str name: str
slug: Optional[str] slug: Optional[str]
position: int position: int

View File

@ -1,11 +1,12 @@
from fastapi_camelcase import CamelModel
from pydantic import UUID4, validator from pydantic import UUID4, validator
from slugify import slugify from slugify import slugify
from mealie.schema._mealie import MealieModel
from ..recipe.recipe_category import CategoryBase, RecipeCategoryResponse from ..recipe.recipe_category import CategoryBase, RecipeCategoryResponse
class CreateCookBook(CamelModel): class CreateCookBook(MealieModel):
name: str name: str
description: str = "" description: str = ""
slug: str = None slug: str = None

View File

@ -1,10 +1,11 @@
from fastapi_camelcase import CamelModel
from pydantic import UUID4 from pydantic import UUID4
from mealie.schema._mealie import MealieModel
from .group_preferences import UpdateGroupPreferences from .group_preferences import UpdateGroupPreferences
class GroupAdminUpdate(CamelModel): class GroupAdminUpdate(MealieModel):
id: UUID4 id: UUID4
name: str name: str
preferences: UpdateGroupPreferences preferences: UpdateGroupPreferences

View File

@ -1,11 +1,12 @@
from fastapi_camelcase import CamelModel
from pydantic import UUID4, NoneStr from pydantic import UUID4, NoneStr
from mealie.schema._mealie import MealieModel
# ============================================================================= # =============================================================================
# Group Events Notifier Options # Group Events Notifier Options
class GroupEventNotifierOptions(CamelModel): class GroupEventNotifierOptions(MealieModel):
""" """
These events are in-sync with the EventTypes found in the EventBusService. These events are in-sync with the EventTypes found in the EventBusService.
If you modify this, make sure to update the EventBusService as well. If you modify this, make sure to update the EventBusService as well.
@ -55,7 +56,7 @@ class GroupEventNotifierOptionsOut(GroupEventNotifierOptions):
# Notifiers # Notifiers
class GroupEventNotifierCreate(CamelModel): class GroupEventNotifierCreate(MealieModel):
name: str name: str
apprise_url: str apprise_url: str
@ -71,7 +72,7 @@ class GroupEventNotifierUpdate(GroupEventNotifierSave):
apprise_url: NoneStr = None apprise_url: NoneStr = None
class GroupEventNotifierOut(CamelModel): class GroupEventNotifierOut(MealieModel):
id: UUID4 id: UUID4
name: str name: str
enabled: bool enabled: bool

View File

@ -1,10 +1,11 @@
from datetime import datetime from datetime import datetime
from fastapi_camelcase import CamelModel
from pydantic import UUID4 from pydantic import UUID4
from mealie.schema._mealie import MealieModel
class GroupDataExport(CamelModel):
class GroupDataExport(MealieModel):
id: UUID4 id: UUID4
group_id: UUID4 group_id: UUID4
name: str name: str

View File

@ -1,6 +1,6 @@
import enum import enum
from fastapi_camelcase import CamelModel from mealie.schema._mealie import MealieModel
class SupportedMigrations(str, enum.Enum): class SupportedMigrations(str, enum.Enum):
@ -10,5 +10,5 @@ class SupportedMigrations(str, enum.Enum):
mealie_alpha = "mealie_alpha" mealie_alpha = "mealie_alpha"
class DataMigrationCreate(CamelModel): class DataMigrationCreate(MealieModel):
source_type: SupportedMigrations source_type: SupportedMigrations

View File

@ -1,8 +1,9 @@
from fastapi_camelcase import CamelModel
from pydantic import UUID4 from pydantic import UUID4
from mealie.schema._mealie import MealieModel
class SetPermissions(CamelModel):
class SetPermissions(MealieModel):
user_id: UUID4 user_id: UUID4
can_manage: bool = False can_manage: bool = False
can_invite: bool = False can_invite: bool = False

View File

@ -1,10 +1,11 @@
from uuid import UUID from uuid import UUID
from fastapi_camelcase import CamelModel
from pydantic import UUID4 from pydantic import UUID4
from mealie.schema._mealie import MealieModel
class UpdateGroupPreferences(CamelModel):
class UpdateGroupPreferences(MealieModel):
private_group: bool = False private_group: bool = False
first_day_of_week: int = 0 first_day_of_week: int = 0

View File

@ -2,13 +2,13 @@ from __future__ import annotations
from typing import Optional from typing import Optional
from fastapi_camelcase import CamelModel
from pydantic import UUID4 from pydantic import UUID4
from mealie.schema._mealie import MealieModel
from mealie.schema.recipe.recipe_ingredient import IngredientFood, IngredientUnit from mealie.schema.recipe.recipe_ingredient import IngredientFood, IngredientUnit
class ShoppingListItemRecipeRef(CamelModel): class ShoppingListItemRecipeRef(MealieModel):
recipe_id: UUID4 recipe_id: UUID4
recipe_quantity: float recipe_quantity: float
@ -21,7 +21,7 @@ class ShoppingListItemRecipeRefOut(ShoppingListItemRecipeRef):
orm_mode = True orm_mode = True
class ShoppingListItemCreate(CamelModel): class ShoppingListItemCreate(MealieModel):
shopping_list_id: UUID4 shopping_list_id: UUID4
checked: bool = False checked: bool = False
position: int = 0 position: int = 0
@ -51,11 +51,11 @@ class ShoppingListItemOut(ShoppingListItemUpdate):
orm_mode = True orm_mode = True
class ShoppingListCreate(CamelModel): class ShoppingListCreate(MealieModel):
name: str = None name: str = None
class ShoppingListRecipeRefOut(CamelModel): class ShoppingListRecipeRefOut(MealieModel):
id: UUID4 id: UUID4
shopping_list_id: UUID4 shopping_list_id: UUID4
recipe_id: UUID4 recipe_id: UUID4

View File

@ -1,20 +1,21 @@
from uuid import UUID from uuid import UUID
from fastapi_camelcase import CamelModel
from pydantic import NoneStr from pydantic import NoneStr
from mealie.schema._mealie import MealieModel
class CreateInviteToken(CamelModel):
class CreateInviteToken(MealieModel):
uses: int uses: int
class SaveInviteToken(CamelModel): class SaveInviteToken(MealieModel):
uses_left: int uses_left: int
group_id: UUID group_id: UUID
token: str token: str
class ReadInviteToken(CamelModel): class ReadInviteToken(MealieModel):
token: str token: str
uses_left: int uses_left: int
group_id: UUID group_id: UUID
@ -23,11 +24,11 @@ class ReadInviteToken(CamelModel):
orm_mode = True orm_mode = True
class EmailInvitation(CamelModel): class EmailInvitation(MealieModel):
email: str email: str
token: str token: str
class EmailInitationResponse(CamelModel): class EmailInitationResponse(MealieModel):
success: bool success: bool
error: NoneStr = None error: NoneStr = None

View File

@ -1,10 +1,11 @@
from uuid import UUID from uuid import UUID
from fastapi_camelcase import CamelModel
from pydantic import UUID4 from pydantic import UUID4
from mealie.schema._mealie import MealieModel
class CreateWebhook(CamelModel):
class CreateWebhook(MealieModel):
enabled: bool = True enabled: bool = True
name: str = "" name: str = ""
url: str = "" url: str = ""

View File

@ -1,10 +1,11 @@
from __future__ import annotations from __future__ import annotations
from fastapi_camelcase import CamelModel
from pydantic import UUID4 from pydantic import UUID4
from mealie.schema._mealie import MealieModel
class MultiPurposeLabelCreate(CamelModel):
class MultiPurposeLabelCreate(MealieModel):
name: str name: str
color: str = "#E0E0E0" color: str = "#E0E0E0"

View File

@ -1,11 +1,12 @@
from datetime import date from datetime import date
from typing import Optional from typing import Optional
from fastapi_camelcase import CamelModel
from pydantic import validator from pydantic import validator
from mealie.schema._mealie import MealieModel
class MealIn(CamelModel):
class MealIn(MealieModel):
slug: Optional[str] slug: Optional[str]
name: Optional[str] name: Optional[str]
description: Optional[str] description: Optional[str]
@ -14,7 +15,7 @@ class MealIn(CamelModel):
orm_mode = True orm_mode = True
class MealDayIn(CamelModel): class MealDayIn(MealieModel):
date: Optional[date] date: Optional[date]
meals: list[MealIn] meals: list[MealIn]
@ -29,7 +30,7 @@ class MealDayOut(MealDayIn):
orm_mode = True orm_mode = True
class MealPlanIn(CamelModel): class MealPlanIn(MealieModel):
group: str group: str
start_date: date start_date: date
end_date: date end_date: date

View File

@ -3,9 +3,9 @@ from enum import Enum
from typing import Optional from typing import Optional
from uuid import UUID from uuid import UUID
from fastapi_camelcase import CamelModel
from pydantic import validator from pydantic import validator
from mealie.schema._mealie import MealieModel
from mealie.schema.recipe.recipe import RecipeSummary from mealie.schema.recipe.recipe import RecipeSummary
@ -16,12 +16,12 @@ class PlanEntryType(str, Enum):
side = "side" side = "side"
class CreatRandomEntry(CamelModel): class CreatRandomEntry(MealieModel):
date: date date: date
entry_type: PlanEntryType = PlanEntryType.dinner entry_type: PlanEntryType = PlanEntryType.dinner
class CreatePlanEntry(CamelModel): class CreatePlanEntry(MealieModel):
date: date date: date
entry_type: PlanEntryType = PlanEntryType.breakfast entry_type: PlanEntryType = PlanEntryType.breakfast
title: str = "" title: str = ""

View File

@ -1,11 +1,12 @@
import datetime import datetime
from enum import Enum from enum import Enum
from fastapi_camelcase import CamelModel
from pydantic import UUID4 from pydantic import UUID4
from mealie.schema._mealie import MealieModel
class Category(CamelModel):
class Category(MealieModel):
id: UUID4 id: UUID4
name: str name: str
slug: str slug: str
@ -46,7 +47,7 @@ class PlanRulesType(str, Enum):
unset = "unset" unset = "unset"
class PlanRulesCreate(CamelModel): class PlanRulesCreate(MealieModel):
day: PlanRulesDay = PlanRulesDay.unset day: PlanRulesDay = PlanRulesDay.unset
entry_type: PlanRulesType = PlanRulesType.unset entry_type: PlanRulesType = PlanRulesType.unset
categories: list[Category] = [] categories: list[Category] = []

View File

@ -1,12 +1,12 @@
from typing import Optional from typing import Optional
from fastapi_camelcase import CamelModel
from pydantic.utils import GetterDict from pydantic.utils import GetterDict
from mealie.db.models.group.shopping_list import ShoppingList from mealie.db.models.group.shopping_list import ShoppingList
from mealie.schema._mealie import MealieModel
class ListItem(CamelModel): class ListItem(MealieModel):
title: Optional[str] title: Optional[str]
text: str = "" text: str = ""
quantity: int = 1 quantity: int = 1
@ -16,7 +16,7 @@ class ListItem(CamelModel):
orm_mode = True orm_mode = True
class ShoppingListIn(CamelModel): class ShoppingListIn(MealieModel):
name: str name: str
group: Optional[str] group: Optional[str]
items: list[ListItem] items: list[ListItem]

View File

@ -1,6 +1,6 @@
from fastapi_camelcase import CamelModel from mealie.schema._mealie import MealieModel
class GetAll(CamelModel): class GetAll(MealieModel):
start: int = 0 start: int = 0
limit: int = 999 limit: int = 999

View File

@ -5,13 +5,13 @@ from pathlib import Path
from typing import Any, Optional from typing import Any, Optional
from uuid import uuid4 from uuid import uuid4
from fastapi_camelcase import CamelModel
from pydantic import UUID4, BaseModel, Field, validator from pydantic import UUID4, BaseModel, Field, validator
from pydantic.utils import GetterDict from pydantic.utils import GetterDict
from slugify import slugify from slugify import slugify
from mealie.core.config import get_app_dirs from mealie.core.config import get_app_dirs
from mealie.db.models.recipe.recipe import RecipeModel from mealie.db.models.recipe.recipe import RecipeModel
from mealie.schema._mealie import MealieModel
from .recipe_asset import RecipeAsset from .recipe_asset import RecipeAsset
from .recipe_comments import RecipeCommentOut from .recipe_comments import RecipeCommentOut
@ -23,7 +23,7 @@ from .recipe_step import RecipeStep
app_dirs = get_app_dirs() app_dirs = get_app_dirs()
class RecipeTag(CamelModel): class RecipeTag(MealieModel):
id: UUID4 = None id: UUID4 = None
name: str name: str
slug: str slug: str
@ -58,11 +58,11 @@ class CreateRecipeByUrlBulk(BaseModel):
imports: list[CreateRecipeBulk] imports: list[CreateRecipeBulk]
class CreateRecipe(CamelModel): class CreateRecipe(MealieModel):
name: str name: str
class RecipeSummary(CamelModel): class RecipeSummary(MealieModel):
id: Optional[UUID4] id: Optional[UUID4]
user_id: UUID4 = Field(default_factory=uuid4) user_id: UUID4 = Field(default_factory=uuid4)

View File

@ -1,9 +1,9 @@
from typing import Optional from typing import Optional
from fastapi_camelcase import CamelModel from mealie.schema._mealie import MealieModel
class RecipeAsset(CamelModel): class RecipeAsset(MealieModel):
name: str name: str
icon: str icon: str
file_name: Optional[str] file_name: Optional[str]

View File

@ -1,7 +1,6 @@
import enum import enum
from fastapi_camelcase import CamelModel from mealie.schema._mealie import MealieModel
from mealie.schema.recipe.recipe_category import CategoryBase, TagBase from mealie.schema.recipe.recipe_category import CategoryBase, TagBase
@ -9,7 +8,7 @@ class ExportTypes(str, enum.Enum):
JSON = "json" JSON = "json"
class ExportBase(CamelModel): class ExportBase(MealieModel):
recipes: list[str] recipes: list[str]
@ -29,12 +28,12 @@ class DeleteRecipes(ExportBase):
pass pass
class BulkActionError(CamelModel): class BulkActionError(MealieModel):
recipe: str recipe: str
error: str error: str
class BulkActionsResponse(CamelModel): class BulkActionsResponse(MealieModel):
success: bool success: bool
message: str message: str
errors: list[BulkActionError] = [] errors: list[BulkActionError] = []

View File

@ -1,9 +1,10 @@
from fastapi_camelcase import CamelModel
from pydantic import UUID4 from pydantic import UUID4
from pydantic.utils import GetterDict from pydantic.utils import GetterDict
from mealie.schema._mealie import MealieModel
class CategoryIn(CamelModel):
class CategoryIn(MealieModel):
name: str name: str

View File

@ -2,11 +2,12 @@ from datetime import datetime
from typing import Optional from typing import Optional
from uuid import UUID from uuid import UUID
from fastapi_camelcase import CamelModel
from pydantic import UUID4 from pydantic import UUID4
from mealie.schema._mealie import MealieModel
class UserBase(CamelModel):
class UserBase(MealieModel):
id: int id: int
username: Optional[str] username: Optional[str]
admin: bool admin: bool
@ -15,7 +16,7 @@ class UserBase(CamelModel):
orm_mode = True orm_mode = True
class RecipeCommentCreate(CamelModel): class RecipeCommentCreate(MealieModel):
recipe_id: UUID4 recipe_id: UUID4
text: str text: str
@ -24,7 +25,7 @@ class RecipeCommentSave(RecipeCommentCreate):
user_id: UUID4 user_id: UUID4
class RecipeCommentUpdate(CamelModel): class RecipeCommentUpdate(MealieModel):
id: UUID id: UUID
text: str text: str

View File

@ -4,13 +4,13 @@ import enum
from typing import Optional, Union from typing import Optional, Union
from uuid import UUID, uuid4 from uuid import UUID, uuid4
from fastapi_camelcase import CamelModel
from pydantic import UUID4, Field from pydantic import UUID4, Field
from mealie.schema._mealie import MealieModel
from mealie.schema._mealie.types import NoneFloat from mealie.schema._mealie.types import NoneFloat
class UnitFoodBase(CamelModel): class UnitFoodBase(MealieModel):
name: str name: str
description: str = "" description: str = ""
@ -47,7 +47,7 @@ class IngredientUnit(CreateIngredientUnit):
orm_mode = True orm_mode = True
class RecipeIngredient(CamelModel): class RecipeIngredient(MealieModel):
title: Optional[str] title: Optional[str]
note: Optional[str] note: Optional[str]
unit: Optional[Union[IngredientUnit, CreateIngredientUnit]] unit: Optional[Union[IngredientUnit, CreateIngredientUnit]]
@ -64,7 +64,7 @@ class RecipeIngredient(CamelModel):
orm_mode = True orm_mode = True
class IngredientConfidence(CamelModel): class IngredientConfidence(MealieModel):
average: NoneFloat = None average: NoneFloat = None
comment: NoneFloat = None comment: NoneFloat = None
name: NoneFloat = None name: NoneFloat = None
@ -73,7 +73,7 @@ class IngredientConfidence(CamelModel):
food: NoneFloat = None food: NoneFloat = None
class ParsedIngredient(CamelModel): class ParsedIngredient(MealieModel):
input: Optional[str] input: Optional[str]
confidence: IngredientConfidence = IngredientConfidence() confidence: IngredientConfidence = IngredientConfidence()
ingredient: RecipeIngredient ingredient: RecipeIngredient
@ -84,12 +84,12 @@ class RegisteredParser(str, enum.Enum):
brute = "brute" brute = "brute"
class IngredientsRequest(CamelModel): class IngredientsRequest(MealieModel):
parser: RegisteredParser = RegisteredParser.nlp parser: RegisteredParser = RegisteredParser.nlp
ingredients: list[str] ingredients: list[str]
class IngredientRequest(CamelModel): class IngredientRequest(MealieModel):
parser: RegisteredParser = RegisteredParser.nlp parser: RegisteredParser = RegisteredParser.nlp
ingredient: str ingredient: str

View File

@ -1,9 +1,9 @@
from typing import Optional from typing import Optional
from fastapi_camelcase import CamelModel from mealie.schema._mealie import MealieModel
class Nutrition(CamelModel): class Nutrition(MealieModel):
calories: Optional[str] calories: Optional[str]
fat_content: Optional[str] fat_content: Optional[str]
protein_content: Optional[str] protein_content: Optional[str]

View File

@ -1,7 +1,7 @@
from fastapi_camelcase import CamelModel from mealie.schema._mealie import MealieModel
class RecipeSettings(CamelModel): class RecipeSettings(MealieModel):
public: bool = False public: bool = False
show_nutrition: bool = False show_nutrition: bool = False
show_assets: bool = False show_assets: bool = False

View File

@ -1,8 +1,9 @@
from datetime import datetime, timedelta from datetime import datetime, timedelta
from fastapi_camelcase import CamelModel
from pydantic import UUID4, Field from pydantic import UUID4, Field
from mealie.schema._mealie import MealieModel
from .recipe import Recipe from .recipe import Recipe
@ -10,7 +11,7 @@ def defaut_expires_at_time() -> datetime:
return datetime.utcnow() + timedelta(days=30) return datetime.utcnow() + timedelta(days=30)
class RecipeShareTokenCreate(CamelModel): class RecipeShareTokenCreate(MealieModel):
recipe_id: UUID4 recipe_id: UUID4
expires_at: datetime = Field(default_factory=defaut_expires_at_time) expires_at: datetime = Field(default_factory=defaut_expires_at_time)

View File

@ -1,11 +1,12 @@
from typing import Optional from typing import Optional
from uuid import UUID, uuid4 from uuid import UUID, uuid4
from fastapi_camelcase import CamelModel
from pydantic import UUID4, Field from pydantic import UUID4, Field
from mealie.schema._mealie import MealieModel
class IngredientReferences(CamelModel):
class IngredientReferences(MealieModel):
""" """
A list of ingredient references. A list of ingredient references.
""" """
@ -16,7 +17,7 @@ class IngredientReferences(CamelModel):
orm_mode = True orm_mode = True
class RecipeStep(CamelModel): class RecipeStep(MealieModel):
id: Optional[UUID] = Field(default_factory=uuid4) id: Optional[UUID] = Field(default_factory=uuid4)
title: Optional[str] = "" title: Optional[str] = ""
text: str text: str

View File

@ -1,10 +1,11 @@
import typing import typing
from fastapi_camelcase import CamelModel
from pydantic import UUID4 from pydantic import UUID4
from mealie.schema._mealie import MealieModel
class RecipeToolCreate(CamelModel):
class RecipeToolCreate(MealieModel):
name: str name: str
on_hand: bool = False on_hand: bool = False

View File

@ -1,10 +1,11 @@
from fastapi_camelcase import CamelModel
from pydantic import BaseModel from pydantic import BaseModel
from mealie.schema._mealie import MealieModel
# TODO: Should these exist?!?!?!?!? # TODO: Should these exist?!?!?!?!?
class RecipeSlug(CamelModel): class RecipeSlug(MealieModel):
slug: str slug: str

View File

@ -1,10 +1,11 @@
import datetime import datetime
import enum import enum
from fastapi_camelcase import CamelModel
from pydantic import Field from pydantic import Field
from pydantic.types import UUID4 from pydantic.types import UUID4
from mealie.schema._mealie import MealieModel
class ReportCategory(str, enum.Enum): class ReportCategory(str, enum.Enum):
backup = "backup" backup = "backup"
@ -19,7 +20,7 @@ class ReportSummaryStatus(str, enum.Enum):
partial = "partial" partial = "partial"
class ReportEntryCreate(CamelModel): class ReportEntryCreate(MealieModel):
report_id: UUID4 report_id: UUID4
timestamp: datetime.datetime = Field(default_factory=datetime.datetime.utcnow) timestamp: datetime.datetime = Field(default_factory=datetime.datetime.utcnow)
success: bool = True success: bool = True
@ -34,7 +35,7 @@ class ReportEntryOut(ReportEntryCreate):
orm_mode = True orm_mode = True
class ReportCreate(CamelModel): class ReportCreate(MealieModel):
timestamp: datetime.datetime = Field(default_factory=datetime.datetime.utcnow) timestamp: datetime.datetime = Field(default_factory=datetime.datetime.utcnow)
category: ReportCategory category: ReportCategory
group_id: UUID4 group_id: UUID4

View File

@ -1,8 +1,9 @@
from typing import Optional from typing import Optional
from fastapi_camelcase import CamelModel
from pydantic import BaseModel from pydantic import BaseModel
from mealie.schema._mealie import MealieModel
class ErrorResponse(BaseModel): class ErrorResponse(BaseModel):
message: str message: str
@ -31,7 +32,7 @@ class SuccessResponse(BaseModel):
return cls(message=message).dict() return cls(message=message).dict()
class FileTokenResponse(CamelModel): class FileTokenResponse(MealieModel):
file_token: str file_token: str
@classmethod @classmethod

View File

@ -2,9 +2,10 @@ import datetime
import enum import enum
from uuid import UUID from uuid import UUID
from fastapi_camelcase import CamelModel
from pydantic import Field from pydantic import Field
from mealie.schema._mealie import MealieModel
class ServerTaskNames(str, enum.Enum): class ServerTaskNames(str, enum.Enum):
default = "Background Task" default = "Background Task"
@ -18,7 +19,7 @@ class ServerTaskStatus(str, enum.Enum):
failed = "failed" failed = "failed"
class ServerTaskCreate(CamelModel): class ServerTaskCreate(MealieModel):
group_id: UUID group_id: UUID
name: ServerTaskNames = ServerTaskNames.default name: ServerTaskNames = ServerTaskNames.default
created_at: datetime.datetime = Field(default_factory=datetime.datetime.now) created_at: datetime.datetime = Field(default_factory=datetime.datetime.now)

View File

@ -1,9 +1,10 @@
from fastapi_camelcase import CamelModel
from pydantic import validator from pydantic import validator
from pydantic.types import NoneStr, constr from pydantic.types import NoneStr, constr
from mealie.schema._mealie import MealieModel
class CreateUserRegistration(CamelModel):
class CreateUserRegistration(MealieModel):
group: NoneStr = None group: NoneStr = None
group_token: NoneStr = None group_token: NoneStr = None
email: constr(to_lower=True, strip_whitespace=True) # type: ignore email: constr(to_lower=True, strip_whitespace=True) # type: ignore

View File

@ -3,13 +3,13 @@ from pathlib import Path
from typing import Any, Optional from typing import Any, Optional
from uuid import UUID from uuid import UUID
from fastapi_camelcase import CamelModel
from pydantic import UUID4 from pydantic import UUID4
from pydantic.types import constr from pydantic.types import constr
from pydantic.utils import GetterDict from pydantic.utils import GetterDict
from mealie.core.config import get_app_dirs, get_app_settings from mealie.core.config import get_app_dirs, get_app_settings
from mealie.db.models.users import User from mealie.db.models.users import User
from mealie.schema._mealie import MealieModel
from mealie.schema.group.group_preferences import ReadGroupPreferences from mealie.schema.group.group_preferences import ReadGroupPreferences
from mealie.schema.recipe import RecipeSummary from mealie.schema.recipe import RecipeSummary
@ -18,7 +18,7 @@ from ..recipe import CategoryBase
settings = get_app_settings() settings = get_app_settings()
class LoingLiveTokenIn(CamelModel): class LoingLiveTokenIn(MealieModel):
name: str name: str
@ -38,19 +38,19 @@ class CreateToken(LoingLiveTokenIn):
orm_mode = True orm_mode = True
class ChangePassword(CamelModel): class ChangePassword(MealieModel):
current_password: str current_password: str
new_password: str new_password: str
class GroupBase(CamelModel): class GroupBase(MealieModel):
name: str name: str
class Config: class Config:
orm_mode = True orm_mode = True
class UserBase(CamelModel): class UserBase(MealieModel):
username: Optional[str] username: Optional[str]
full_name: Optional[str] = None full_name: Optional[str] = None
email: constr(to_lower=True, strip_whitespace=True) # type: ignore email: constr(to_lower=True, strip_whitespace=True) # type: ignore

View File

@ -1,14 +1,15 @@
from fastapi_camelcase import CamelModel
from pydantic import UUID4 from pydantic import UUID4
from mealie.schema._mealie import MealieModel
from .user import PrivateUser from .user import PrivateUser
class ForgotPassword(CamelModel): class ForgotPassword(MealieModel):
email: str email: str
class ValidateResetToken(CamelModel): class ValidateResetToken(MealieModel):
token: str token: str
@ -18,7 +19,7 @@ class ResetPassword(ValidateResetToken):
passwordConfirm: str passwordConfirm: str
class SavePasswordResetToken(CamelModel): class SavePasswordResetToken(MealieModel):
user_id: UUID4 user_id: UUID4
token: str token: str

View File

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

View File

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

View File

@ -1,4 +1,3 @@
from .auto_backup import *
from .purge_group_exports import * from .purge_group_exports import *
from .purge_password_reset import * from .purge_password_reset import *
from .purge_registration import * from .purge_registration import *

View File

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

@ -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)"] 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)"] 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]] [[package]]
name = "filelock" name = "filelock"
version = "3.6.0" version = "3.6.0"
@ -829,17 +817,6 @@ category = "dev"
optional = false optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 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]] [[package]]
name = "pillow" name = "pillow"
version = "8.4.0" version = "8.4.0"
@ -1003,7 +980,7 @@ python-versions = ">=3.5"
[[package]] [[package]]
name = "pyhumps" 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" description = "🐫 Convert strings (and dictionary keys) between snake case, camel case and pascal case in Python. Inspired by Humps for Node"
category = "main" category = "main"
optional = false optional = false
@ -1622,7 +1599,7 @@ pgsql = ["psycopg2-binary"]
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = "^3.10" python-versions = "^3.10"
content-hash = "4fba071019a62f5d75e7c9a297a7815b2fed6486bb3616b5029a6fb08001761f" content-hash = "84c1d9352c058da5cc0f50ca195cbe0897ce64abfbe01d08b9da317b6dd70a70"
[metadata.files] [metadata.files]
aiofiles = [ aiofiles = [
@ -1852,9 +1829,6 @@ fastapi = [
{file = "fastapi-0.74.1-py3-none-any.whl", hash = "sha256:b8ec8400623ef0b2ff558ebe06753b349f8e3a5dd38afea650800f2644ddba34"}, {file = "fastapi-0.74.1-py3-none-any.whl", hash = "sha256:b8ec8400623ef0b2ff558ebe06753b349f8e3a5dd38afea650800f2644ddba34"},
{file = "fastapi-0.74.1.tar.gz", hash = "sha256:b58a2c46df14f62ebe6f24a9439927539ba1959b9be55ba0e2f516a683e5b9d4"}, {file = "fastapi-0.74.1.tar.gz", hash = "sha256:b58a2c46df14f62ebe6f24a9439927539ba1959b9be55ba0e2f516a683e5b9d4"},
] ]
fastapi-camelcase = [
{file = "fastapi_camelcase-1.0.5.tar.gz", hash = "sha256:2cee005fb1b75649491b9f7cfccc640b12f028eb88084565f7d8cf415192026a"},
]
filelock = [ filelock = [
{file = "filelock-3.6.0-py3-none-any.whl", hash = "sha256:f8314284bfffbdcfa0ff3d7992b023d4c628ced6feb957351d4c48d059f56bc0"}, {file = "filelock-3.6.0-py3-none-any.whl", hash = "sha256:f8314284bfffbdcfa0ff3d7992b023d4c628ced6feb957351d4c48d059f56bc0"},
{file = "filelock-3.6.0.tar.gz", hash = "sha256:9cd540a9352e432c7246a48fe4e8712b10acb1df2ad1f30e8c070b82ae1fed85"}, {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-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
{file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, {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 = [ pillow = [
{file = "Pillow-8.4.0-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:81f8d5c81e483a9442d72d182e1fb6dcb9723f289a57e8030811bac9ea3fef8d"}, {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"}, {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"}, {file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"},
] ]
pyhumps = [ pyhumps = [
{file = "pyhumps-3.5.0-py3-none-any.whl", hash = "sha256:2433eef13d1c258227a0bd5de9660ba17dd6a307e1255d2d20ec9287f8626d96"}, {file = "pyhumps-3.5.3-py3-none-any.whl", hash = "sha256:8d7e9865d6ddb6e64a2e97d951b78b5cc827d3d66cda1297310fc83b2ddf51dc"},
{file = "pyhumps-3.5.0.tar.gz", hash = "sha256:55e37f16846eaab26057200924cbdadd2152bf0a5d49175a42358464fa881c73"}, {file = "pyhumps-3.5.3.tar.gz", hash = "sha256:0ecf7fee84503b45afdd3841ec769b529d32dfaed855e07046ff8babcc0ab831"},
] ]
pylint = [ pylint = [
{file = "pylint-2.12.2-py3-none-any.whl", hash = "sha256:daabda3f7ed9d1c60f52d563b1b854632fd90035bcf01443e234d3dc794e3b74"}, {file = "pylint-2.12.2-py3-none-any.whl", hash = "sha256:daabda3f7ed9d1c60f52d563b1b854632fd90035bcf01443e234d3dc794e3b74"},

View File

@ -25,13 +25,11 @@ requests = "^2.25.1"
PyYAML = "^5.3.1" PyYAML = "^5.3.1"
extruct = "^0.13.0" extruct = "^0.13.0"
python-multipart = "^0.0.5" python-multipart = "^0.0.5"
fastapi-camelcase = "^1.0.5"
bcrypt = "^3.2.0" bcrypt = "^3.2.0"
python-jose = "^3.3.0" python-jose = "^3.3.0"
passlib = "^1.7.4" passlib = "^1.7.4"
lxml = "^4.7.1" lxml = "^4.7.1"
Pillow = "^8.2.0" Pillow = "^8.2.0"
pathvalidate = "^2.4.1"
apprise = "^0.9.6" apprise = "^0.9.6"
recipe-scrapers = "^13.18.1" recipe-scrapers = "^13.18.1"
psycopg2-binary = {version = "^2.9.1", optional = true} psycopg2-binary = {version = "^2.9.1", optional = true}
@ -41,6 +39,7 @@ python-i18n = "^0.3.9"
python-ldap = "^3.3.1" python-ldap = "^3.3.1"
pydantic = "^1.9.0" pydantic = "^1.9.0"
tzdata = "^2021.5" tzdata = "^2021.5"
pyhumps = "^3.5.3"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
pylint = "^2.6.0" pylint = "^2.6.0"

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