mirror of
https://github.com/Electric-Special/ha-core.git
synced 2026-03-21 02:03:27 +01:00
Mealie add get shopping list items action (#163090)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -33,6 +33,9 @@
|
||||
"get_recipes": {
|
||||
"service": "mdi:book-open-page-variant"
|
||||
},
|
||||
"get_shopping_list_items": {
|
||||
"service": "mdi:basket"
|
||||
},
|
||||
"import_recipe": {
|
||||
"service": "mdi:map-search"
|
||||
},
|
||||
|
||||
@@ -12,6 +12,7 @@ from aiomealie import (
|
||||
from awesomeversion import AwesomeVersion
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.todo import DOMAIN as TODO_DOMAIN
|
||||
from homeassistant.const import ATTR_CONFIG_ENTRY_ID, ATTR_DATE
|
||||
from homeassistant.core import (
|
||||
HomeAssistant,
|
||||
@@ -64,6 +65,8 @@ SERVICE_GET_RECIPES_SCHEMA = vol.Schema(
|
||||
}
|
||||
)
|
||||
|
||||
SERVICE_GET_SHOPPING_LIST_ITEMS = "get_shopping_list_items"
|
||||
|
||||
SERVICE_IMPORT_RECIPE = "import_recipe"
|
||||
SERVICE_IMPORT_RECIPE_SCHEMA = vol.Schema(
|
||||
{
|
||||
@@ -321,3 +324,12 @@ def async_setup_services(hass: HomeAssistant) -> None:
|
||||
schema=SERVICE_SET_MEALPLAN_SCHEMA,
|
||||
supports_response=SupportsResponse.OPTIONAL,
|
||||
)
|
||||
service.async_register_platform_entity_service(
|
||||
hass,
|
||||
DOMAIN,
|
||||
SERVICE_GET_SHOPPING_LIST_ITEMS,
|
||||
entity_domain=TODO_DOMAIN,
|
||||
schema=None,
|
||||
func="async_get_shopping_list_items",
|
||||
supports_response=SupportsResponse.ONLY,
|
||||
)
|
||||
|
||||
@@ -45,6 +45,12 @@ get_recipes:
|
||||
mode: box
|
||||
unit_of_measurement: recipes
|
||||
|
||||
get_shopping_list_items:
|
||||
target:
|
||||
entity:
|
||||
integration: mealie
|
||||
domain: todo
|
||||
|
||||
import_recipe:
|
||||
fields:
|
||||
config_entry_id:
|
||||
|
||||
@@ -147,6 +147,9 @@
|
||||
"setup_failed": {
|
||||
"message": "Could not connect to the Mealie instance."
|
||||
},
|
||||
"shopping_list_not_found": {
|
||||
"message": "Shopping list with name or ID `{shopping_list}` not found."
|
||||
},
|
||||
"update_failed_mealplan": {
|
||||
"message": "Could not fetch mealplan data."
|
||||
},
|
||||
@@ -227,6 +230,10 @@
|
||||
},
|
||||
"name": "Get recipes"
|
||||
},
|
||||
"get_shopping_list_items": {
|
||||
"description": "Gets items from a shopping list in Mealie",
|
||||
"name": "Get shopping list items"
|
||||
},
|
||||
"import_recipe": {
|
||||
"description": "Imports a recipe from an URL",
|
||||
"fields": {
|
||||
|
||||
@@ -2,7 +2,15 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from aiomealie import MealieError, MutateShoppingItem, ShoppingItem, ShoppingList
|
||||
from dataclasses import asdict
|
||||
|
||||
from aiomealie import (
|
||||
MealieConnectionError,
|
||||
MealieError,
|
||||
MutateShoppingItem,
|
||||
ShoppingItem,
|
||||
ShoppingList,
|
||||
)
|
||||
|
||||
from homeassistant.components.todo import (
|
||||
DOMAIN as TODO_DOMAIN,
|
||||
@@ -11,7 +19,7 @@ from homeassistant.components.todo import (
|
||||
TodoListEntity,
|
||||
TodoListEntityFeature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.core import HomeAssistant, ServiceResponse
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
@@ -265,3 +273,18 @@ class MealieShoppingListTodoListEntity(MealieEntity, TodoListEntity):
|
||||
def available(self) -> bool:
|
||||
"""Return False if shopping list no longer available."""
|
||||
return super().available and self._shopping_list_id in self.coordinator.data
|
||||
|
||||
async def async_get_shopping_list_items(self) -> ServiceResponse:
|
||||
"""Get structured shopping list items."""
|
||||
client = self.coordinator.client
|
||||
try:
|
||||
shopping_items = await client.get_shopping_items(self._shopping_list_id)
|
||||
except MealieConnectionError as err:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="connection_error",
|
||||
) from err
|
||||
return {
|
||||
"name": self.shopping_list.name,
|
||||
"items": [asdict(item) for item in shopping_items.items],
|
||||
}
|
||||
|
||||
@@ -1637,6 +1637,159 @@
|
||||
}),
|
||||
})
|
||||
# ---
|
||||
# name: test_service_get_shopping_list_items
|
||||
dict({
|
||||
'todo.mealie_supermarket': dict({
|
||||
'items': list([
|
||||
dict({
|
||||
'checked': False,
|
||||
'disable_amount': None,
|
||||
'display': '2 Apples',
|
||||
'food': None,
|
||||
'food_id': None,
|
||||
'is_food': None,
|
||||
'item_id': 'f45430f7-3edf-45a9-a50f-73bb375090be',
|
||||
'label': None,
|
||||
'label_id': None,
|
||||
'list_id': '9ce096fe-ded2-4077-877d-78ba450ab13e',
|
||||
'note': 'Apples',
|
||||
'position': 0,
|
||||
'quantity': 2.0,
|
||||
'unit': None,
|
||||
'unit_id': None,
|
||||
}),
|
||||
dict({
|
||||
'checked': False,
|
||||
'disable_amount': False,
|
||||
'display': '1 can acorn squash',
|
||||
'food': dict({
|
||||
'aliases': list([
|
||||
]),
|
||||
'created_at': datetime.datetime(2024, 5, 14, 14, 45, 4, 454134),
|
||||
'description': '',
|
||||
'extras': dict({
|
||||
}),
|
||||
'food_id': '09322430-d24c-4b1a-abb6-22b6ed3a88f5',
|
||||
'households_with_ingredient_food': None,
|
||||
'label': None,
|
||||
'label_id': None,
|
||||
'name': 'acorn squash',
|
||||
'plural_name': None,
|
||||
'updated_at': datetime.datetime(2024, 5, 14, 14, 45, 4, 454141),
|
||||
}),
|
||||
'food_id': '09322430-d24c-4b1a-abb6-22b6ed3a88f5',
|
||||
'is_food': True,
|
||||
'item_id': '84d8fd74-8eb0-402e-84b6-71f251bfb7cc',
|
||||
'label': None,
|
||||
'label_id': None,
|
||||
'list_id': '9ce096fe-ded2-4077-877d-78ba450ab13e',
|
||||
'note': '',
|
||||
'position': 1,
|
||||
'quantity': 1.0,
|
||||
'unit': dict({
|
||||
'abbreviation': '',
|
||||
'aliases': list([
|
||||
]),
|
||||
'created_at': datetime.datetime(2024, 5, 14, 14, 45, 2, 464122),
|
||||
'description': '',
|
||||
'extras': dict({
|
||||
}),
|
||||
'fraction': True,
|
||||
'name': 'can',
|
||||
'plural_abbreviation': '',
|
||||
'plural_name': None,
|
||||
'unit_id': '7bf539d4-fc78-48bc-b48e-c35ccccec34a',
|
||||
'updated_at': datetime.datetime(2024, 5, 14, 14, 45, 2, 464124),
|
||||
'use_abbreviation': False,
|
||||
}),
|
||||
'unit_id': '7bf539d4-fc78-48bc-b48e-c35ccccec34a',
|
||||
}),
|
||||
dict({
|
||||
'checked': False,
|
||||
'disable_amount': False,
|
||||
'display': 'aubergine',
|
||||
'food': dict({
|
||||
'aliases': list([
|
||||
]),
|
||||
'created_at': datetime.datetime(2024, 5, 14, 14, 45, 3, 868792),
|
||||
'description': '',
|
||||
'extras': dict({
|
||||
}),
|
||||
'food_id': '96801494-4e26-4148-849a-8155deb76327',
|
||||
'households_with_ingredient_food': None,
|
||||
'label': None,
|
||||
'label_id': None,
|
||||
'name': 'aubergine',
|
||||
'plural_name': None,
|
||||
'updated_at': datetime.datetime(2024, 5, 14, 14, 45, 3, 868794),
|
||||
}),
|
||||
'food_id': '96801494-4e26-4148-849a-8155deb76327',
|
||||
'is_food': True,
|
||||
'item_id': '69913b9a-7c75-4935-abec-297cf7483f88',
|
||||
'label': None,
|
||||
'label_id': None,
|
||||
'list_id': '9ce096fe-ded2-4077-877d-78ba450ab13e',
|
||||
'note': '',
|
||||
'position': 2,
|
||||
'quantity': 0.0,
|
||||
'unit': None,
|
||||
'unit_id': None,
|
||||
}),
|
||||
dict({
|
||||
'checked': False,
|
||||
'disable_amount': None,
|
||||
'display': '1 US cup flour',
|
||||
'food': dict({
|
||||
'aliases': list([
|
||||
]),
|
||||
'created_at': datetime.datetime(2024, 8, 25, 13, 29, 29, 40354, tzinfo=datetime.timezone.utc),
|
||||
'description': '',
|
||||
'extras': dict({
|
||||
}),
|
||||
'food_id': '8d2ef4d7-bfc2-4420-9cba-152016c1ee7c',
|
||||
'households_with_ingredient_food': list([
|
||||
]),
|
||||
'label': None,
|
||||
'label_id': None,
|
||||
'name': 'flour',
|
||||
'plural_name': None,
|
||||
'updated_at': datetime.datetime(2024, 8, 25, 13, 29, 29, 40371, tzinfo=datetime.timezone.utc),
|
||||
}),
|
||||
'food_id': '8d2ef4d7-bfc2-4420-9cba-152016c1ee7c',
|
||||
'is_food': None,
|
||||
'item_id': '22b389bb-e079-481c-915d-394e5edb20a5',
|
||||
'label': dict({
|
||||
'label_id': '0e55cae5-6037-4cbb-8d4f-1042cbb83fd0',
|
||||
'name': 'Household',
|
||||
}),
|
||||
'label_id': None,
|
||||
'list_id': 'a33af640-4704-453c-ab03-a95a393bf1c4',
|
||||
'note': '',
|
||||
'position': 0,
|
||||
'quantity': 1.0,
|
||||
'unit': dict({
|
||||
'abbreviation': 'US cup',
|
||||
'aliases': list([
|
||||
]),
|
||||
'created_at': datetime.datetime(2024, 8, 25, 13, 29, 25, 477518, tzinfo=datetime.timezone.utc),
|
||||
'description': '',
|
||||
'extras': dict({
|
||||
}),
|
||||
'fraction': True,
|
||||
'name': 'US cup',
|
||||
'plural_abbreviation': '',
|
||||
'plural_name': None,
|
||||
'unit_id': '89765d44-8412-4ab5-a6de-594aa8eac44c',
|
||||
'updated_at': datetime.datetime(2024, 8, 25, 13, 29, 25, 477535, tzinfo=datetime.timezone.utc),
|
||||
'use_abbreviation': False,
|
||||
}),
|
||||
'unit_id': '89765d44-8412-4ab5-a6de-594aa8eac44c',
|
||||
}),
|
||||
]),
|
||||
'name': 'Supermarket',
|
||||
}),
|
||||
})
|
||||
# ---
|
||||
# name: test_service_import_recipe
|
||||
dict({
|
||||
'recipe': dict({
|
||||
|
||||
@@ -31,6 +31,7 @@ from homeassistant.components.mealie.services import (
|
||||
SERVICE_GET_MEALPLAN,
|
||||
SERVICE_GET_RECIPE,
|
||||
SERVICE_GET_RECIPES,
|
||||
SERVICE_GET_SHOPPING_LIST_ITEMS,
|
||||
SERVICE_IMPORT_RECIPE,
|
||||
SERVICE_SET_MEALPLAN,
|
||||
SERVICE_SET_RANDOM_MEALPLAN,
|
||||
@@ -395,6 +396,47 @@ async def test_service_set_mealplan_invalid_entry_type(
|
||||
mock_mealie_client.set_mealplan.assert_not_called()
|
||||
|
||||
|
||||
async def test_service_get_shopping_list_items(
|
||||
hass: HomeAssistant,
|
||||
mock_mealie_client: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test the get_shopping_list_items service."""
|
||||
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
response = await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_GET_SHOPPING_LIST_ITEMS,
|
||||
target={"entity_id": "todo.mealie_supermarket"},
|
||||
blocking=True,
|
||||
return_response=True,
|
||||
)
|
||||
assert response == snapshot
|
||||
|
||||
|
||||
async def test_service_get_shopping_list_items_connection_error(
|
||||
hass: HomeAssistant,
|
||||
mock_mealie_client: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test the get_shopping_list_items service with connection error."""
|
||||
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
mock_mealie_client.get_shopping_items.side_effect = MealieConnectionError
|
||||
|
||||
with pytest.raises(HomeAssistantError, match="Error connecting to Mealie instance"):
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_GET_SHOPPING_LIST_ITEMS,
|
||||
target={"entity_id": "todo.mealie_supermarket"},
|
||||
blocking=True,
|
||||
return_response=True,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("service", "payload", "function", "exception", "raised_exception", "message"),
|
||||
[
|
||||
|
||||
Reference in New Issue
Block a user