Add Mealie service to set a random mealplan (#122313)

* Add Mealie service to set a random mealplan

* Fix coverage

* Fix coverage
pull/122316/head
Joost Lekkerkerker 2024-07-21 16:43:46 +02:00 committed by GitHub
parent 39068bb786
commit 6f4a8a4a14
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 232 additions and 3 deletions

View File

@ -14,5 +14,6 @@ ATTR_END_DATE = "end_date"
ATTR_RECIPE_ID = "recipe_id"
ATTR_URL = "url"
ATTR_INCLUDE_TAGS = "include_tags"
ATTR_ENTRY_TYPE = "entry_type"
MIN_REQUIRED_MEALIE_VERSION = AwesomeVersion("v1.0.0")

View File

@ -26,6 +26,7 @@
"services": {
"get_mealplan": "mdi:food",
"get_recipe": "mdi:map",
"import_recipe": "mdi:map-search"
"import_recipe": "mdi:map-search",
"set_random_mealplan": "mdi:dice-multiple"
}
}

View File

@ -4,10 +4,16 @@ from dataclasses import asdict
from datetime import date
from typing import cast
from aiomealie import MealieConnectionError, MealieNotFoundError, MealieValidationError
from aiomealie import (
MealieConnectionError,
MealieNotFoundError,
MealieValidationError,
MealplanEntryType,
)
import voluptuous as vol
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import ATTR_DATE
from homeassistant.core import (
HomeAssistant,
ServiceCall,
@ -20,6 +26,7 @@ from homeassistant.helpers import config_validation as cv
from .const import (
ATTR_CONFIG_ENTRY_ID,
ATTR_END_DATE,
ATTR_ENTRY_TYPE,
ATTR_INCLUDE_TAGS,
ATTR_RECIPE_ID,
ATTR_START_DATE,
@ -54,6 +61,15 @@ SERVICE_IMPORT_RECIPE_SCHEMA = vol.Schema(
}
)
SERVICE_SET_RANDOM_MEALPLAN = "set_random_mealplan"
SERVICE_SET_RANDOM_MEALPLAN_SCHEMA = vol.Schema(
{
vol.Required(ATTR_CONFIG_ENTRY_ID): str,
vol.Required(ATTR_DATE): cv.date,
vol.Required(ATTR_ENTRY_TYPE): vol.In([x.lower() for x in MealplanEntryType]),
}
)
def async_get_entry(hass: HomeAssistant, config_entry_id: str) -> MealieConfigEntry:
"""Get the Mealie config entry."""
@ -137,6 +153,23 @@ def setup_services(hass: HomeAssistant) -> None:
return {"recipe": asdict(recipe)}
return None
async def async_set_random_mealplan(call: ServiceCall) -> ServiceResponse:
"""Set a random mealplan."""
entry = async_get_entry(hass, call.data[ATTR_CONFIG_ENTRY_ID])
mealplan_date = call.data[ATTR_DATE]
entry_type = MealplanEntryType(call.data[ATTR_ENTRY_TYPE])
client = entry.runtime_data.client
try:
mealplan = await client.random_mealplan(mealplan_date, entry_type)
except MealieConnectionError as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="connection_error",
) from err
if call.return_response:
return {"mealplan": asdict(mealplan)}
return None
hass.services.async_register(
DOMAIN,
SERVICE_GET_MEALPLAN,
@ -158,3 +191,10 @@ def setup_services(hass: HomeAssistant) -> None:
schema=SERVICE_IMPORT_RECIPE_SCHEMA,
supports_response=SupportsResponse.OPTIONAL,
)
hass.services.async_register(
DOMAIN,
SERVICE_SET_RANDOM_MEALPLAN,
async_set_random_mealplan,
schema=SERVICE_SET_RANDOM_MEALPLAN_SCHEMA,
supports_response=SupportsResponse.OPTIONAL,
)

View File

@ -11,6 +11,7 @@ get_mealplan:
end_date:
selector:
date:
get_recipe:
fields:
config_entry_id:
@ -22,6 +23,7 @@ get_recipe:
required: true
selector:
text:
import_recipe:
fields:
config_entry_id:
@ -36,3 +38,23 @@ import_recipe:
include_tags:
selector:
boolean:
set_random_mealplan:
fields:
config_entry_id:
required: true
selector:
config_entry:
integration: mealie
date:
selector:
date:
entry_type:
selector:
select:
options:
- breakfast
- lunch
- dinner
- side
translation_key: mealplan_entry_type

View File

@ -157,6 +157,34 @@
"description": "Include tags from the website to the recipe."
}
}
},
"set_random_mealplan": {
"name": "Set random mealplan",
"description": "Set a random mealplan for a specific date",
"fields": {
"config_entry_id": {
"name": "[%key:component::mealie::services::get_mealplan::fields::config_entry_id::name%]",
"description": "[%key:component::mealie::services::get_mealplan::fields::config_entry_id::description%]"
},
"date": {
"name": "Date",
"description": "The date to set the mealplan for."
},
"entry_type": {
"name": "Entry type",
"description": "The type of dish to randomize."
}
}
}
},
"selector": {
"mealplan_entry_type": {
"options": {
"breakfast": "[%key:component::mealie::entity::calendar::breakfast::name%]",
"lunch": "[%key:component::mealie::entity::calendar::lunch::name%]",
"dinner": "[%key:component::mealie::entity::calendar::dinner::name%]",
"side": "[%key:component::mealie::entity::calendar::side::name%]"
}
}
}
}

View File

@ -74,6 +74,9 @@ def mock_mealie_client() -> Generator[AsyncMock]:
client.get_statistics.return_value = Statistics.from_json(
load_fixture("statistics.json", DOMAIN)
)
client.random_mealplan.return_value = Mealplan.from_json(
load_fixture("mealplan.json", DOMAIN)
)
yield client

View File

@ -0,0 +1,34 @@
{
"date": "2024-01-22",
"entryType": "dinner",
"title": "",
"text": "",
"recipeId": "c5f00a93-71a2-4e48-900f-d9ad0bb9de93",
"id": 230,
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"recipe": {
"id": "c5f00a93-71a2-4e48-900f-d9ad0bb9de93",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"name": "Zoete aardappel curry traybake",
"slug": "zoete-aardappel-curry-traybake",
"image": "AiIo",
"recipeYield": "2 servings",
"totalTime": "40 Minutes",
"prepTime": null,
"cookTime": null,
"performTime": null,
"description": "Een traybake is eigenlijk altijd een goed idee. Deze zoete aardappel curry traybake dus ook. Waarom? Omdat je alleen maar wat groenten - en in dit geval kip - op een bakplaat (traybake dus) legt, hier wat kruiden aan toevoegt en deze in de oven schuift. Ideaal dus als je geen zin hebt om lang in de keuken te staan. Maar gewoon lekker op de bank wil ploffen om te wachten tot de oven klaar is. Joe! That\\'s what we like. Deze zoete aardappel curry traybake bevat behalve zoete aardappel en curry ook kikkererwten, kippendijfilet en bloemkoolroosjes. Je gebruikt yoghurt en limoen als een soort dressing. En je serveert deze heerlijke traybake met naanbrood. Je kunt natuurljk ook voor deze traybake met chipolataworstjes gaan. Wil je graag meer ovengerechten? Dan moet je eigenlijk even kijken naar onze Ovenbijbel. Onmisbaar in je keuken! We willen je deze zoete aardappelstamppot met prei ook niet onthouden. Megalekker bordje comfortfood als je \\'t ons vraagt.",
"recipeCategory": [],
"tags": [],
"tools": [],
"rating": null,
"orgURL": "https://chickslovefood.com/recept/zoete-aardappel-curry-traybake/",
"dateAdded": "2024-01-22",
"dateUpdated": "2024-01-22T00:27:46.324512",
"createdAt": "2024-01-22T00:27:46.327546",
"updateAt": "2024-01-22T00:27:46.327548",
"lastMade": null
}
}

View File

@ -675,3 +675,27 @@
}),
})
# ---
# name: test_service_set_random_mealplan
dict({
'mealplan': dict({
'description': None,
'entry_type': <MealplanEntryType.DINNER: 'dinner'>,
'group_id': '0bf60b2e-ca89-42a9-94d4-8f67ca72b157',
'mealplan_date': datetime.date(2024, 1, 22),
'mealplan_id': 230,
'recipe': dict({
'description': "Een traybake is eigenlijk altijd een goed idee. Deze zoete aardappel curry traybake dus ook. Waarom? Omdat je alleen maar wat groenten - en in dit geval kip - op een bakplaat (traybake dus) legt, hier wat kruiden aan toevoegt en deze in de oven schuift. Ideaal dus als je geen zin hebt om lang in de keuken te staan. Maar gewoon lekker op de bank wil ploffen om te wachten tot de oven klaar is. Joe! That\\'s what we like. Deze zoete aardappel curry traybake bevat behalve zoete aardappel en curry ook kikkererwten, kippendijfilet en bloemkoolroosjes. Je gebruikt yoghurt en limoen als een soort dressing. En je serveert deze heerlijke traybake met naanbrood. Je kunt natuurljk ook voor deze traybake met chipolataworstjes gaan. Wil je graag meer ovengerechten? Dan moet je eigenlijk even kijken naar onze Ovenbijbel. Onmisbaar in je keuken! We willen je deze zoete aardappelstamppot met prei ook niet onthouden. Megalekker bordje comfortfood als je \\'t ons vraagt.",
'group_id': '0bf60b2e-ca89-42a9-94d4-8f67ca72b157',
'image': 'AiIo',
'name': 'Zoete aardappel curry traybake',
'original_url': 'https://chickslovefood.com/recept/zoete-aardappel-curry-traybake/',
'recipe_id': 'c5f00a93-71a2-4e48-900f-d9ad0bb9de93',
'recipe_yield': '2 servings',
'slug': 'zoete-aardappel-curry-traybake',
'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d',
}),
'title': None,
'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d',
}),
})
# ---

View File

@ -3,7 +3,12 @@
from datetime import date
from unittest.mock import AsyncMock
from aiomealie import MealieConnectionError, MealieNotFoundError, MealieValidationError
from aiomealie import (
MealieConnectionError,
MealieNotFoundError,
MealieValidationError,
MealplanEntryType,
)
from freezegun.api import FrozenDateTimeFactory
import pytest
from syrupy import SnapshotAssertion
@ -11,6 +16,7 @@ from syrupy import SnapshotAssertion
from homeassistant.components.mealie.const import (
ATTR_CONFIG_ENTRY_ID,
ATTR_END_DATE,
ATTR_ENTRY_TYPE,
ATTR_INCLUDE_TAGS,
ATTR_RECIPE_ID,
ATTR_START_DATE,
@ -21,7 +27,9 @@ from homeassistant.components.mealie.services import (
SERVICE_GET_MEALPLAN,
SERVICE_GET_RECIPE,
SERVICE_IMPORT_RECIPE,
SERVICE_SET_RANDOM_MEALPLAN,
)
from homeassistant.const import ATTR_DATE
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
@ -246,6 +254,74 @@ async def test_service_import_recipe_exceptions(
)
async def test_service_set_random_mealplan(
hass: HomeAssistant,
mock_mealie_client: AsyncMock,
mock_config_entry: MockConfigEntry,
snapshot: SnapshotAssertion,
) -> None:
"""Test the set_random_mealplan service."""
await setup_integration(hass, mock_config_entry)
response = await hass.services.async_call(
DOMAIN,
SERVICE_SET_RANDOM_MEALPLAN,
{
ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id,
ATTR_DATE: "2023-10-21",
ATTR_ENTRY_TYPE: "lunch",
},
blocking=True,
return_response=True,
)
assert response == snapshot
mock_mealie_client.random_mealplan.assert_called_with(
date(2023, 10, 21), MealplanEntryType.LUNCH
)
mock_mealie_client.random_mealplan.reset_mock()
await hass.services.async_call(
DOMAIN,
SERVICE_SET_RANDOM_MEALPLAN,
{
ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id,
ATTR_DATE: "2023-10-21",
ATTR_ENTRY_TYPE: "lunch",
},
blocking=True,
return_response=False,
)
mock_mealie_client.random_mealplan.assert_called_with(
date(2023, 10, 21), MealplanEntryType.LUNCH
)
async def test_service_set_random_mealplan_exceptions(
hass: HomeAssistant,
mock_mealie_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the exceptions of the set_random_mealplan service."""
await setup_integration(hass, mock_config_entry)
mock_mealie_client.random_mealplan.side_effect = MealieConnectionError
with pytest.raises(HomeAssistantError, match="Error connecting to Mealie instance"):
await hass.services.async_call(
DOMAIN,
SERVICE_SET_RANDOM_MEALPLAN,
{
ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id,
ATTR_DATE: "2023-10-21",
ATTR_ENTRY_TYPE: "lunch",
},
blocking=True,
return_response=True,
)
async def test_service_mealplan_connection_error(
hass: HomeAssistant,
mock_mealie_client: AsyncMock,