mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-02-05 15:33:10 -05:00
@@ -43,7 +43,7 @@ class BackupContents:
|
||||
|
||||
|
||||
class BackupFile:
|
||||
temp_dir: Path | None
|
||||
temp_dir: Path | None = None
|
||||
|
||||
def __init__(self, file: Path) -> None:
|
||||
self.zip = file
|
||||
|
||||
@@ -24,7 +24,7 @@ class EmailTemplate(BaseModel):
|
||||
def render_html(self, template: Path) -> str:
|
||||
tmpl = Template(template.read_text())
|
||||
|
||||
return tmpl.render(data=self.dict())
|
||||
return tmpl.render(data=self.model_dump())
|
||||
|
||||
|
||||
class EmailService(BaseService):
|
||||
|
||||
@@ -23,8 +23,8 @@ from .publisher import ApprisePublisher, PublisherLike, WebhookPublisher
|
||||
|
||||
|
||||
class EventListenerBase(ABC):
|
||||
_session: Session | None
|
||||
_repos: AllRepositories | None
|
||||
_session: Session | None = None
|
||||
_repos: AllRepositories | None = None
|
||||
|
||||
def __init__(self, group_id: UUID4, publisher: PublisherLike) -> None:
|
||||
self.group_id = group_id
|
||||
|
||||
@@ -38,9 +38,9 @@ class EventSource:
|
||||
|
||||
|
||||
class EventBusService:
|
||||
bg: BackgroundTasks | None
|
||||
session: Session | None
|
||||
group_id: UUID4 | None
|
||||
bg: BackgroundTasks | None = None
|
||||
session: Session | None = None
|
||||
group_id: UUID4 | None = None
|
||||
|
||||
def __init__(
|
||||
self, bg: BackgroundTasks | None = None, session: Session | None = None, group_id: UUID4 | None = None
|
||||
|
||||
@@ -3,7 +3,7 @@ from datetime import date, datetime
|
||||
from enum import Enum, auto
|
||||
from typing import Any
|
||||
|
||||
from pydantic import UUID4
|
||||
from pydantic import UUID4, field_validator
|
||||
|
||||
from ...schema._mealie.mealie_model import MealieModel
|
||||
|
||||
@@ -85,79 +85,79 @@ class EventDocumentDataBase(MealieModel):
|
||||
|
||||
|
||||
class EventMealplanCreatedData(EventDocumentDataBase):
|
||||
document_type = EventDocumentType.mealplan
|
||||
operation = EventOperation.create
|
||||
document_type: EventDocumentType = EventDocumentType.mealplan
|
||||
operation: EventOperation = EventOperation.create
|
||||
mealplan_id: int
|
||||
date: date
|
||||
recipe_id: UUID4 | None
|
||||
recipe_name: str | None
|
||||
recipe_slug: str | None
|
||||
recipe_id: UUID4 | None = None
|
||||
recipe_name: str | None = None
|
||||
recipe_slug: str | None = None
|
||||
|
||||
|
||||
class EventUserSignupData(EventDocumentDataBase):
|
||||
document_type = EventDocumentType.user
|
||||
operation = EventOperation.create
|
||||
document_type: EventDocumentType = EventDocumentType.user
|
||||
operation: EventOperation = EventOperation.create
|
||||
username: str
|
||||
email: str
|
||||
|
||||
|
||||
class EventCategoryData(EventDocumentDataBase):
|
||||
document_type = EventDocumentType.category
|
||||
document_type: EventDocumentType = EventDocumentType.category
|
||||
category_id: UUID4
|
||||
|
||||
|
||||
class EventCookbookData(EventDocumentDataBase):
|
||||
document_type = EventDocumentType.cookbook
|
||||
document_type: EventDocumentType = EventDocumentType.cookbook
|
||||
cookbook_id: UUID4
|
||||
|
||||
|
||||
class EventCookbookBulkData(EventDocumentDataBase):
|
||||
document_type = EventDocumentType.cookbook
|
||||
document_type: EventDocumentType = EventDocumentType.cookbook
|
||||
cookbook_ids: list[UUID4]
|
||||
|
||||
|
||||
class EventShoppingListData(EventDocumentDataBase):
|
||||
document_type = EventDocumentType.shopping_list
|
||||
document_type: EventDocumentType = EventDocumentType.shopping_list
|
||||
shopping_list_id: UUID4
|
||||
|
||||
|
||||
class EventShoppingListItemData(EventDocumentDataBase):
|
||||
document_type = EventDocumentType.shopping_list_item
|
||||
document_type: EventDocumentType = EventDocumentType.shopping_list_item
|
||||
shopping_list_id: UUID4
|
||||
shopping_list_item_id: UUID4
|
||||
|
||||
|
||||
class EventShoppingListItemBulkData(EventDocumentDataBase):
|
||||
document_type = EventDocumentType.shopping_list_item
|
||||
document_type: EventDocumentType = EventDocumentType.shopping_list_item
|
||||
shopping_list_id: UUID4
|
||||
shopping_list_item_ids: list[UUID4]
|
||||
|
||||
|
||||
class EventRecipeData(EventDocumentDataBase):
|
||||
document_type = EventDocumentType.recipe
|
||||
document_type: EventDocumentType = EventDocumentType.recipe
|
||||
recipe_slug: str
|
||||
|
||||
|
||||
class EventRecipeBulkReportData(EventDocumentDataBase):
|
||||
document_type = EventDocumentType.recipe_bulk_report
|
||||
document_type: EventDocumentType = EventDocumentType.recipe_bulk_report
|
||||
report_id: UUID4
|
||||
|
||||
|
||||
class EventRecipeTimelineEventData(EventDocumentDataBase):
|
||||
document_type = EventDocumentType.recipe_timeline_event
|
||||
document_type: EventDocumentType = EventDocumentType.recipe_timeline_event
|
||||
recipe_slug: str
|
||||
recipe_timeline_event_id: UUID4
|
||||
|
||||
|
||||
class EventTagData(EventDocumentDataBase):
|
||||
document_type = EventDocumentType.tag
|
||||
document_type: EventDocumentType = EventDocumentType.tag
|
||||
tag_id: UUID4
|
||||
|
||||
|
||||
class EventWebhookData(EventDocumentDataBase):
|
||||
webhook_start_dt: datetime
|
||||
webhook_end_dt: datetime
|
||||
webhook_body: Any
|
||||
webhook_body: Any = None
|
||||
|
||||
|
||||
class EventBusMessage(MealieModel):
|
||||
@@ -169,6 +169,11 @@ class EventBusMessage(MealieModel):
|
||||
title = event_type.name.replace("_", " ").title()
|
||||
return cls(title=title, body=body)
|
||||
|
||||
@field_validator("body")
|
||||
def populate_body(v):
|
||||
# if the body is empty, apprise won't send the notification
|
||||
return v or "generic"
|
||||
|
||||
|
||||
class Event(MealieModel):
|
||||
message: EventBusMessage
|
||||
@@ -177,8 +182,8 @@ class Event(MealieModel):
|
||||
document_data: EventDocumentDataBase
|
||||
|
||||
# set at instantiation
|
||||
event_id: UUID4 | None
|
||||
timestamp: datetime | None
|
||||
event_id: UUID4 | None = None
|
||||
timestamp: datetime | None = None
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@@ -27,7 +27,7 @@ class ExportedItem:
|
||||
|
||||
|
||||
class ABCExporter(BaseService):
|
||||
write_dir_to_zip: Callable[[Path, str, set[str] | None], None] | None
|
||||
write_dir_to_zip: Callable[[Path, str, set[str] | None], None] | None = None
|
||||
|
||||
def __init__(self, db: AllRepositories, group_id: UUID) -> None:
|
||||
self.logger = get_logger()
|
||||
@@ -63,7 +63,7 @@ class ABCExporter(BaseService):
|
||||
self.logger.error("Failed to export item. no item found")
|
||||
continue
|
||||
|
||||
zip.writestr(f"{self.destination_dir}/{item.name}/{item.name}.json", item.model.json())
|
||||
zip.writestr(f"{self.destination_dir}/{item.name}/{item.name}.json", item.model.model_dump_json())
|
||||
|
||||
self._post_export_hook(item.model)
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ from .utils.migration_helpers import MigrationReaders, glob_walker, import_image
|
||||
class NextcloudDir:
|
||||
name: str
|
||||
recipe: dict
|
||||
image: Path | None
|
||||
image: Path | None = None
|
||||
|
||||
@property
|
||||
def slug(self):
|
||||
|
||||
@@ -45,7 +45,7 @@ class DatabaseMigrationHelpers:
|
||||
)
|
||||
)
|
||||
|
||||
items_out.append(item_model.dict())
|
||||
items_out.append(item_model.model_dump())
|
||||
return items_out
|
||||
|
||||
def get_or_set_category(self, categories: Iterable[str]) -> list[RecipeCategory]:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import string
|
||||
import unicodedata
|
||||
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
from .._helpers import check_char, move_parens_to_end
|
||||
|
||||
@@ -11,9 +11,7 @@ class BruteParsedIngredient(BaseModel):
|
||||
note: str = ""
|
||||
amount: float = 1.0
|
||||
unit: str = ""
|
||||
|
||||
class Config:
|
||||
anystr_strip_whitespace = True
|
||||
model_config = ConfigDict(str_strip_whitespace=True)
|
||||
|
||||
|
||||
def parse_fraction(x):
|
||||
|
||||
@@ -2,8 +2,10 @@ import subprocess
|
||||
import tempfile
|
||||
from fractions import Fraction
|
||||
from pathlib import Path
|
||||
from typing import Annotated
|
||||
|
||||
from pydantic import BaseModel, validator
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
from pydantic_core.core_schema import ValidationInfo
|
||||
|
||||
from mealie.schema._mealie.types import NoneFloat
|
||||
|
||||
@@ -19,7 +21,7 @@ class CRFConfidence(BaseModel):
|
||||
comment: NoneFloat = None
|
||||
name: NoneFloat = None
|
||||
unit: NoneFloat = None
|
||||
qty: NoneFloat = None
|
||||
qty: Annotated[NoneFloat, Field(validate_default=True)] = None
|
||||
|
||||
|
||||
class CRFIngredient(BaseModel):
|
||||
@@ -31,13 +33,13 @@ class CRFIngredient(BaseModel):
|
||||
unit: str = ""
|
||||
confidence: CRFConfidence
|
||||
|
||||
@validator("qty", always=True, pre=True)
|
||||
def validate_qty(qty, values): # sourcery skip: merge-nested-ifs
|
||||
@field_validator("qty", mode="before")
|
||||
def validate_qty(qty, info: ValidationInfo): # sourcery skip: merge-nested-ifs
|
||||
if qty is None or qty == "":
|
||||
# Check if other contains a fraction
|
||||
try:
|
||||
if values["other"] is not None and values["other"].find("/") != -1:
|
||||
return round(float(Fraction(values["other"])), 3)
|
||||
if info.data["other"] is not None and info.data["other"].find("/") != -1:
|
||||
return round(float(Fraction(info.data["other"])), 3)
|
||||
else:
|
||||
return 1
|
||||
except Exception:
|
||||
|
||||
@@ -228,7 +228,7 @@ class NLPParser(ABCIngredientParser):
|
||||
confidence=IngredientConfidence(
|
||||
quantity=crf_model.confidence.qty,
|
||||
food=crf_model.confidence.name,
|
||||
**crf_model.confidence.dict(),
|
||||
**crf_model.confidence.model_dump(),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ class RecipeService(BaseService):
|
||||
data: Recipe = self._recipe_creation_factory(
|
||||
self.user,
|
||||
name=create_data.name,
|
||||
additional_attrs=create_data.dict(),
|
||||
additional_attrs=create_data.model_dump(),
|
||||
)
|
||||
|
||||
if isinstance(create_data, CreateRecipe) or create_data.settings is None:
|
||||
@@ -175,11 +175,11 @@ class RecipeService(BaseService):
|
||||
# if the item exists, return the actual data
|
||||
query = repo.get_one(slug, "slug")
|
||||
if query:
|
||||
return query.dict()
|
||||
return query.model_dump()
|
||||
|
||||
# otherwise, create the item
|
||||
new_item = repo.create(data)
|
||||
return new_item.dict()
|
||||
return new_item.model_dump()
|
||||
|
||||
def _process_recipe_data(self, key: str, data: list | dict | Any):
|
||||
if isinstance(data, list):
|
||||
@@ -250,20 +250,21 @@ class RecipeService(BaseService):
|
||||
"""Duplicates a recipe and returns the new recipe."""
|
||||
|
||||
old_recipe = self._get_recipe(old_slug)
|
||||
new_recipe = old_recipe.copy(exclude={"id", "name", "slug", "image", "comments"})
|
||||
new_recipe_data = old_recipe.model_dump(exclude={"id", "name", "slug", "image", "comments"}, round_trip=True)
|
||||
new_recipe = Recipe.model_validate(new_recipe_data)
|
||||
|
||||
# Asset images in steps directly link to the original recipe, so we
|
||||
# need to update them to references to the assets we copy below
|
||||
def replace_recipe_step(step: RecipeStep) -> RecipeStep:
|
||||
new_step = step.copy(exclude={"id", "text"})
|
||||
new_step.id = uuid4()
|
||||
new_step.text = step.text.replace(str(old_recipe.id), str(new_recipe.id))
|
||||
new_id = uuid4()
|
||||
new_text = step.text.replace(str(old_recipe.id), str(new_recipe.id))
|
||||
new_step = step.model_copy(update={"id": new_id, "text": new_text})
|
||||
return new_step
|
||||
|
||||
# Copy ingredients to make them independent of the original
|
||||
def copy_recipe_ingredient(ingredient: RecipeIngredient):
|
||||
new_ingredient = ingredient.copy(exclude={"reference_id"})
|
||||
new_ingredient.reference_id = uuid4()
|
||||
new_reference_id = uuid4()
|
||||
new_ingredient = ingredient.model_copy(update={"reference_id": new_reference_id})
|
||||
return new_ingredient
|
||||
|
||||
new_name = dup_data.name if dup_data.name else old_recipe.name or ""
|
||||
@@ -284,7 +285,7 @@ class RecipeService(BaseService):
|
||||
new_recipe = self._recipe_creation_factory(
|
||||
self.user,
|
||||
new_name,
|
||||
additional_attrs=new_recipe.dict(),
|
||||
additional_attrs=new_recipe.model_dump(),
|
||||
)
|
||||
|
||||
new_recipe = self.repos.recipes.create(new_recipe)
|
||||
@@ -350,7 +351,9 @@ class RecipeService(BaseService):
|
||||
if recipe is None:
|
||||
raise exceptions.NoEntryFound("Recipe not found.")
|
||||
|
||||
new_data = self.repos.recipes.by_group(self.group.id).patch(recipe.slug, patch_data.dict(exclude_unset=True))
|
||||
new_data = self.repos.recipes.by_group(self.group.id).patch(
|
||||
recipe.slug, patch_data.model_dump(exclude_unset=True)
|
||||
)
|
||||
|
||||
self.check_assets(new_data, recipe.slug)
|
||||
return new_data
|
||||
|
||||
@@ -92,7 +92,7 @@ class TemplateService(BaseService):
|
||||
|
||||
save_path = self.temp.joinpath(f"{recipe.slug}.json")
|
||||
with open(save_path, "w") as f:
|
||||
f.write(recipe.json(indent=4, by_alias=True))
|
||||
f.write(recipe.model_dump_json(indent=4, by_alias=True))
|
||||
|
||||
return save_path
|
||||
|
||||
@@ -115,7 +115,7 @@ class TemplateService(BaseService):
|
||||
template_text = f.read()
|
||||
|
||||
template = Template(template_text)
|
||||
rendered_text = template.render(recipe=recipe.dict(by_alias=True))
|
||||
rendered_text = template.render(recipe=recipe.model_dump(by_alias=True))
|
||||
|
||||
save_name = f"{recipe.slug}{j2_path.suffix}"
|
||||
|
||||
@@ -140,7 +140,7 @@ class TemplateService(BaseService):
|
||||
zip_temp = self.temp.joinpath(f"{recipe.slug}.zip")
|
||||
|
||||
with ZipFile(zip_temp, "w") as myzip:
|
||||
myzip.writestr(f"{recipe.slug}.json", recipe.json())
|
||||
myzip.writestr(f"{recipe.slug}.json", recipe.model_dump_json())
|
||||
|
||||
if image_asset.is_file():
|
||||
myzip.write(image_asset, arcname=image_asset.name)
|
||||
|
||||
@@ -29,7 +29,7 @@ class RegistrationService:
|
||||
password=hash_password(self.registration.password),
|
||||
full_name=self.registration.username,
|
||||
advanced=self.registration.advanced,
|
||||
group=group.name,
|
||||
group=group,
|
||||
can_invite=new_group,
|
||||
can_manage=new_group,
|
||||
can_organize=new_group,
|
||||
|
||||
Reference in New Issue
Block a user