From 23b2953773152e6563a2ff1e1c8f817a746fed65 Mon Sep 17 00:00:00 2001 From: MHV33 <41445347+MHV33@users.noreply.github.com> Date: Tue, 23 Feb 2021 16:25:50 -0600 Subject: [PATCH] Add more shopping list services (#45591) Co-authored-by: Franck Nijhof --- .../components/shopping_list/__init__.py | 54 ++++++++++++++++++- .../components/shopping_list/services.yaml | 13 +++++ tests/components/shopping_list/test_init.py | 34 ++++++++++++ 3 files changed, 99 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/shopping_list/__init__.py b/homeassistant/components/shopping_list/__init__.py index 1831f894cec..e438bf3b8f4 100644 --- a/homeassistant/components/shopping_list/__init__.py +++ b/homeassistant/components/shopping_list/__init__.py @@ -15,17 +15,21 @@ from homeassistant.util.json import load_json, save_json from .const import DOMAIN ATTR_NAME = "name" +ATTR_COMPLETE = "complete" _LOGGER = logging.getLogger(__name__) CONFIG_SCHEMA = vol.Schema({DOMAIN: {}}, extra=vol.ALLOW_EXTRA) EVENT = "shopping_list_updated" -ITEM_UPDATE_SCHEMA = vol.Schema({"complete": bool, ATTR_NAME: str}) +ITEM_UPDATE_SCHEMA = vol.Schema({ATTR_COMPLETE: bool, ATTR_NAME: str}) PERSISTENCE = ".shopping_list.json" SERVICE_ADD_ITEM = "add_item" SERVICE_COMPLETE_ITEM = "complete_item" - +SERVICE_INCOMPLETE_ITEM = "incomplete_item" +SERVICE_COMPLETE_ALL = "complete_all" +SERVICE_INCOMPLETE_ALL = "incomplete_all" SERVICE_ITEM_SCHEMA = vol.Schema({vol.Required(ATTR_NAME): vol.Any(None, cv.string)}) +SERVICE_LIST_SCHEMA = vol.Schema({}) WS_TYPE_SHOPPING_LIST_ITEMS = "shopping_list/items" WS_TYPE_SHOPPING_LIST_ADD_ITEM = "shopping_list/items/add" @@ -92,6 +96,27 @@ async def async_setup_entry(hass, config_entry): else: await data.async_update(item["id"], {"name": name, "complete": True}) + async def incomplete_item_service(call): + """Mark the item provided via `name` as incomplete.""" + data = hass.data[DOMAIN] + name = call.data.get(ATTR_NAME) + if name is None: + return + try: + item = [item for item in data.items if item["name"] == name][0] + except IndexError: + _LOGGER.error("Restoring of item failed: %s cannot be found", name) + else: + await data.async_update(item["id"], {"name": name, "complete": False}) + + async def complete_all_service(call): + """Mark all items in the list as complete.""" + await data.async_update_list({"complete": True}) + + async def incomplete_all_service(call): + """Mark all items in the list as incomplete.""" + await data.async_update_list({"complete": False}) + data = hass.data[DOMAIN] = ShoppingData(hass) await data.async_load() @@ -101,6 +126,24 @@ async def async_setup_entry(hass, config_entry): hass.services.async_register( DOMAIN, SERVICE_COMPLETE_ITEM, complete_item_service, schema=SERVICE_ITEM_SCHEMA ) + hass.services.async_register( + DOMAIN, + SERVICE_INCOMPLETE_ITEM, + incomplete_item_service, + schema=SERVICE_ITEM_SCHEMA, + ) + hass.services.async_register( + DOMAIN, + SERVICE_COMPLETE_ALL, + complete_all_service, + schema=SERVICE_LIST_SCHEMA, + ) + hass.services.async_register( + DOMAIN, + SERVICE_INCOMPLETE_ALL, + incomplete_all_service, + schema=SERVICE_LIST_SCHEMA, + ) hass.http.register_view(ShoppingListView) hass.http.register_view(CreateShoppingListItemView) @@ -165,6 +208,13 @@ class ShoppingData: self.items = [itm for itm in self.items if not itm["complete"]] await self.hass.async_add_executor_job(self.save) + async def async_update_list(self, info): + """Update all items in the list.""" + for item in self.items: + item.update(info) + await self.hass.async_add_executor_job(self.save) + return self.items + @callback def async_reorder(self, item_ids): """Reorder items.""" diff --git a/homeassistant/components/shopping_list/services.yaml b/homeassistant/components/shopping_list/services.yaml index 2a1e89b9786..73540210232 100644 --- a/homeassistant/components/shopping_list/services.yaml +++ b/homeassistant/components/shopping_list/services.yaml @@ -21,3 +21,16 @@ complete_item: example: Beer selector: text: + +incomplete_item: + description: Marks an item as incomplete in the shopping list. + fields: + name: + description: The name of the item to mark as incomplete. + example: Beer + +complete_all: + description: Marks all items as completed in the shopping list. It does not remove the items. + +incomplete_all: + description: Marks all items as incomplete in the shopping list. diff --git a/tests/components/shopping_list/test_init.py b/tests/components/shopping_list/test_init.py index 0be4c70ef18..48482787f4d 100644 --- a/tests/components/shopping_list/test_init.py +++ b/tests/components/shopping_list/test_init.py @@ -1,5 +1,6 @@ """Test shopping list component.""" +from homeassistant.components.shopping_list.const import DOMAIN from homeassistant.components.websocket_api.const import ( ERR_INVALID_FORMAT, ERR_NOT_FOUND, @@ -19,6 +20,39 @@ async def test_add_item(hass, sl_setup): assert response.speech["plain"]["speech"] == "I've added beer to your shopping list" +async def test_update_list(hass, sl_setup): + """Test updating all list items.""" + await intent.async_handle( + hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}} + ) + + await intent.async_handle( + hass, "test", "HassShoppingListAddItem", {"item": {"value": "cheese"}} + ) + + # Update a single attribute, other attributes shouldn't change + await hass.data[DOMAIN].async_update_list({"complete": True}) + + beer = hass.data[DOMAIN].items[0] + assert beer["name"] == "beer" + assert beer["complete"] is True + + cheese = hass.data[DOMAIN].items[1] + assert cheese["name"] == "cheese" + assert cheese["complete"] is True + + # Update multiple attributes + await hass.data[DOMAIN].async_update_list({"name": "dupe", "complete": False}) + + beer = hass.data[DOMAIN].items[0] + assert beer["name"] == "dupe" + assert beer["complete"] is False + + cheese = hass.data[DOMAIN].items[1] + assert cheese["name"] == "dupe" + assert cheese["complete"] is False + + async def test_recent_items_intent(hass, sl_setup): """Test recent items."""