Skip to content

Commit

Permalink
Add Household support for Mealie V2 (#236)
Browse files Browse the repository at this point in the history
* Prototype

* Change all known affected

* Add dependency

* Change to checking household support

* Tests

* Fix

* WIP

* Fixes

* Fix quotes

* Fix quotes

* Fix deserialize

* Fix

* Fix

---------

Co-authored-by: Joostlek <joostlek@outlook.com>
  • Loading branch information
andrew-codechimp and joostlek authored Aug 30, 2024
1 parent 1cbf64e commit 7e451d9
Show file tree
Hide file tree
Showing 11 changed files with 329 additions and 71 deletions.
2 changes: 1 addition & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 34 additions & 10 deletions src/aiomealie/mealie.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from __future__ import annotations


import asyncio
import json
from dataclasses import dataclass
Expand Down Expand Up @@ -55,6 +56,7 @@ class MealieClient:
session: ClientSession | None = None
request_timeout: int = 10
_close_session: bool = False
household_support: bool | None = None

async def _request(
self,
Expand Down Expand Up @@ -158,6 +160,23 @@ async def _delete(
"""Handle a DELETE request to Mealie."""
return await self._request(METH_DELETE, uri, data=data, params=params)

async def define_household_support(self) -> bool:
"""Check whether households are supported."""
try:
await self._get("api/households/mealplans/today")
except MealieNotFoundError:
self.household_support = False
else:
self.household_support = True
return self.household_support

def _versioned_path(self, path_end: str) -> str:
"""Return the path with a prefix based on household support detected."""
assert self.household_support
if self.household_support:
return "api/households/" + path_end
return "api/groups/" + path_end

async def get_startup_info(self) -> StartupInfo:
"""Get startup info."""
response = await self._get("api/app/about/startup-info")
Expand Down Expand Up @@ -201,7 +220,7 @@ async def import_recipe(self, url: str, include_tags: bool = False) -> Recipe:

async def get_mealplan_today(self) -> list[Mealplan]:
"""Get mealplan."""
response = await self._get("api/groups/mealplans/today")
response = await self._get(self._versioned_path("mealplans/today"))
return ORJSONDecoder(list[Mealplan]).decode(response)

async def get_mealplans(
Expand All @@ -216,14 +235,14 @@ async def get_mealplans(
if end_date:
params["end_date"] = end_date.isoformat()
params["perPage"] = -1
response = await self._get("api/groups/mealplans", params)
response = await self._get(self._versioned_path("mealplans"), params)
return MealplanResponse.from_json(response)

async def get_shopping_lists(self) -> ShoppingListsResponse:
"""Get shopping lists."""
params: dict[str, Any] = {}
params["perPage"] = -1
response = await self._get("api/groups/shopping/lists", params)
response = await self._get(self._versioned_path("shopping/lists"), params)
return ShoppingListsResponse.from_json(response)

async def get_shopping_items(
Expand All @@ -236,7 +255,7 @@ async def get_shopping_items(
params["orderBy"] = ShoppingItemsOrderBy.POSITION
params["orderDirection"] = OrderDirection.ASCENDING
params["perPage"] = -1
response = await self._get("api/groups/shopping/items", params)
response = await self._get(self._versioned_path("shopping/items"), params)
return ShoppingItemsResponse.from_json(response)

async def add_shopping_item(
Expand All @@ -245,34 +264,39 @@ async def add_shopping_item(
) -> None:
"""Add a shopping item."""

await self._post("api/groups/shopping/items", data=item.to_dict(omit_none=True))
await self._post(
self._versioned_path("shopping/items"), data=item.to_dict(omit_none=True)
)

async def update_shopping_item(
self, item_id: str, item: MutateShoppingItem
) -> None:
"""Update a shopping item."""

await self._put(
f"api/groups/shopping/items/{item_id}", data=item.to_dict(omit_none=True)
f"{self._versioned_path('shopping/items')}/{item_id}",
data=item.to_dict(omit_none=True),
)

async def delete_shopping_item(self, item_id: str) -> None:
"""Delete shopping item."""

await self._delete(f"api/groups/shopping/items/{item_id}")
await self._delete(
f"{self._versioned_path('shopping/items')}/{item_id}",
)

async def get_statistics(self) -> Statistics:
"""Get statistics."""

response = await self._get("api/groups/statistics")
response = await self._get(self._versioned_path("statistics"))
return Statistics.from_json(response)

async def random_mealplan(
self, at: date, entry_type: MealplanEntryType
) -> Mealplan:
"""Set a random mealplan for a specific date."""
response = await self._post(
"api/groups/mealplans/random",
self._versioned_path("mealplans/random"),
{
"date": at.isoformat(),
"entryType": entry_type.value,
Expand Down Expand Up @@ -300,7 +324,7 @@ async def set_mealplan(
data["title"] = note_title
if note_text:
data["text"] = note_text
response = await self._post("api/groups/mealplans", data)
response = await self._post(self._versioned_path("mealplans"), data)
return Mealplan.from_json(response)

async def close(self) -> None:
Expand Down
21 changes: 18 additions & 3 deletions src/aiomealie/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from __future__ import annotations


from dataclasses import dataclass, field
from datetime import date
from enum import StrEnum
Expand All @@ -20,8 +19,10 @@ def serialize(self, value: str | None) -> str | None:
"""Serialize optional string."""
return value

def deserialize(self, value: str) -> str | None:
def deserialize(self, value: str | None) -> str | None:
"""Deserialize optional string."""
if not value:
return None
val = value.strip()
return val if val else None

Expand Down Expand Up @@ -141,9 +142,16 @@ class BaseRecipe(DataClassORJSONMixin):
recipe_yield: str = field(metadata=field_options(alias="recipeYield"))
description: str
original_url: str = field(metadata=field_options(alias="orgURL"))
household_id: str | None = field(
default=None,
metadata=field_options(
alias="householdId",
serialization_strategy=OptionalStringSerializationStrategy(),
),
)


@dataclass
@dataclass(kw_only=True)
class Recipe(BaseRecipe):
"""Recipe model."""

Expand Down Expand Up @@ -193,6 +201,13 @@ class Mealplan(DataClassORJSONMixin):
)
)
recipe: BaseRecipe | None
household_id: str | None = field(
default=None,
metadata=field_options(
alias="householdId",
serialization_strategy=OptionalStringSerializationStrategy(),
),
)


@dataclass
Expand Down
Loading

0 comments on commit 7e451d9

Please sign in to comment.