Compare commits
4 commits
dev
...
synesthesi
Author | SHA1 | Date | |
---|---|---|---|
|
a1c23b0235 | ||
|
4ba9358d5c | ||
|
800f816e9f | ||
|
9b604b4a1c |
6 changed files with 149 additions and 25 deletions
|
@ -1,38 +1,20 @@
|
||||||
"""Intents for the Shopping List integration."""
|
"""Intents for the Shopping List integration."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import intent
|
from homeassistant.helpers import intent
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
from . import DOMAIN, EVENT_SHOPPING_LIST_UPDATED
|
from . import DOMAIN
|
||||||
|
|
||||||
INTENT_ADD_ITEM = "HassShoppingListAddItem"
|
|
||||||
INTENT_LAST_ITEMS = "HassShoppingListLastItems"
|
INTENT_LAST_ITEMS = "HassShoppingListLastItems"
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_intents(hass):
|
async def async_setup_intents(hass: HomeAssistant) -> None:
|
||||||
"""Set up the Shopping List intents."""
|
"""Set up the Shopping List intents."""
|
||||||
intent.async_register(hass, AddItemIntent())
|
|
||||||
intent.async_register(hass, ListTopItemsIntent())
|
intent.async_register(hass, ListTopItemsIntent())
|
||||||
|
|
||||||
|
|
||||||
class AddItemIntent(intent.IntentHandler):
|
|
||||||
"""Handle AddItem intents."""
|
|
||||||
|
|
||||||
intent_type = INTENT_ADD_ITEM
|
|
||||||
slot_schema = {"item": cv.string}
|
|
||||||
|
|
||||||
async def async_handle(self, intent_obj: intent.Intent):
|
|
||||||
"""Handle the intent."""
|
|
||||||
slots = self.async_validate_slots(intent_obj.slots)
|
|
||||||
item = slots["item"]["value"]
|
|
||||||
await intent_obj.hass.data[DOMAIN].async_add(item)
|
|
||||||
|
|
||||||
response = intent_obj.create_response()
|
|
||||||
intent_obj.hass.bus.async_fire(EVENT_SHOPPING_LIST_UPDATED)
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
class ListTopItemsIntent(intent.IntentHandler):
|
class ListTopItemsIntent(intent.IntentHandler):
|
||||||
"""Handle AddItem intents."""
|
"""Handle AddItem intents."""
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"name": "Shopping List",
|
"name": "Shopping List",
|
||||||
"codeowners": [],
|
"codeowners": [],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"dependencies": ["http"],
|
"dependencies": ["http", "intent", "todo"],
|
||||||
"documentation": "https://www.home-assistant.io/integrations/shopping_list",
|
"documentation": "https://www.home-assistant.io/integrations/shopping_list",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"quality_scale": "internal"
|
"quality_scale": "internal"
|
||||||
|
|
73
homeassistant/components/todo/intent.py
Normal file
73
homeassistant/components/todo/intent.py
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
"""Intents for the todo integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.components.shopping_list import EVENT_SHOPPING_LIST_UPDATED
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import intent
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
from homeassistant.helpers.entity_component import EntityComponent
|
||||||
|
|
||||||
|
from . import DOMAIN, TodoItem, TodoListEntity
|
||||||
|
|
||||||
|
INTENT_ADD_ITEM = "HassShoppingListAddItem"
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_intents(hass: HomeAssistant) -> None:
|
||||||
|
"""Set up the todo intents."""
|
||||||
|
intent.async_register(hass, AddItemIntent())
|
||||||
|
|
||||||
|
|
||||||
|
class AddItemIntent(intent.IntentHandler):
|
||||||
|
"""Handle AddItem intents."""
|
||||||
|
|
||||||
|
intent_type = INTENT_ADD_ITEM
|
||||||
|
slot_schema = {"item": cv.string, vol.Optional("list"): cv.string}
|
||||||
|
|
||||||
|
async def async_handle(self, intent_obj: intent.Intent):
|
||||||
|
"""Handle the intent."""
|
||||||
|
hass = intent_obj.hass
|
||||||
|
|
||||||
|
slots = self.async_validate_slots(intent_obj.slots)
|
||||||
|
item = slots["item"]["value"]
|
||||||
|
|
||||||
|
component: EntityComponent[TodoListEntity] = hass.data[DOMAIN]
|
||||||
|
list_entities: list[TodoListEntity] = list(component.entities)
|
||||||
|
if not list_entities:
|
||||||
|
raise intent.IntentHandleError("No list entities")
|
||||||
|
|
||||||
|
target_list: TodoListEntity | None = None
|
||||||
|
|
||||||
|
if "list" in slots:
|
||||||
|
# Add to a list by name
|
||||||
|
list_name = slots["list"]["value"]
|
||||||
|
|
||||||
|
# Find matching list
|
||||||
|
matching_states = intent.async_match_states(
|
||||||
|
hass, name=list_name, domains=[DOMAIN]
|
||||||
|
)
|
||||||
|
for list_state in matching_states:
|
||||||
|
for maybe_list in list_entities:
|
||||||
|
if maybe_list.entity_id == list_state.entity_id:
|
||||||
|
target_list = maybe_list
|
||||||
|
break
|
||||||
|
|
||||||
|
if target_list is not None:
|
||||||
|
break
|
||||||
|
|
||||||
|
if target_list is None:
|
||||||
|
raise intent.IntentHandleError(f"No list named {list_name}")
|
||||||
|
else:
|
||||||
|
# Add to the first list
|
||||||
|
target_list = list_entities[0]
|
||||||
|
|
||||||
|
assert target_list is not None
|
||||||
|
|
||||||
|
# Add to list
|
||||||
|
await target_list.async_create_todo_item(TodoItem(item))
|
||||||
|
|
||||||
|
response = intent_obj.create_response()
|
||||||
|
response.response_type = intent.IntentResponseType.ACTION_DONE
|
||||||
|
intent_obj.hass.bus.async_fire(EVENT_SHOPPING_LIST_UPDATED)
|
||||||
|
return response
|
|
@ -21,13 +21,12 @@ def mock_shopping_list_io():
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_config_entry() -> MockConfigEntry:
|
def mock_config_entry() -> MockConfigEntry:
|
||||||
"""Config Entry fixture."""
|
"""Config Entry fixture."""
|
||||||
return MockConfigEntry(domain="shopping_list")
|
return MockConfigEntry(domain="shopping_list", entry_id="1234")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
async def sl_setup(hass: HomeAssistant, mock_config_entry: MockConfigEntry):
|
async def sl_setup(hass: HomeAssistant, mock_config_entry: MockConfigEntry):
|
||||||
"""Set up the shopping list."""
|
"""Set up the shopping list."""
|
||||||
|
|
||||||
mock_config_entry.add_to_hass(hass)
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
|
|
|
@ -11,7 +11,8 @@ from homeassistant.exceptions import HomeAssistantError
|
||||||
|
|
||||||
from tests.typing import WebSocketGenerator
|
from tests.typing import WebSocketGenerator
|
||||||
|
|
||||||
TEST_ENTITY = "todo.shopping_list"
|
# NOTE: This depends on the config entry_id in sl_setup
|
||||||
|
TEST_ENTITY = "todo.shopping_list_1234"
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|
|
@ -13,11 +13,13 @@ from homeassistant.components.todo import (
|
||||||
TodoItemStatus,
|
TodoItemStatus,
|
||||||
TodoListEntity,
|
TodoListEntity,
|
||||||
TodoListEntityFeature,
|
TodoListEntityFeature,
|
||||||
|
intent as todo_intent,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry, ConfigEntryState, ConfigFlow
|
from homeassistant.config_entries import ConfigEntry, ConfigEntryState, ConfigFlow
|
||||||
from homeassistant.const import Platform
|
from homeassistant.const import Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
from homeassistant.helpers import intent
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from tests.common import (
|
from tests.common import (
|
||||||
|
@ -37,6 +39,18 @@ class MockFlow(ConfigFlow):
|
||||||
"""Test flow."""
|
"""Test flow."""
|
||||||
|
|
||||||
|
|
||||||
|
class MockTodoListEntity(TodoListEntity):
|
||||||
|
"""Test todo list entity."""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
"""Initialize entity."""
|
||||||
|
self.items: list[TodoItem] = []
|
||||||
|
|
||||||
|
async def async_create_todo_item(self, item: TodoItem) -> None:
|
||||||
|
"""Add an item to the To-do list."""
|
||||||
|
self.items.append(item)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def config_flow_fixture(hass: HomeAssistant) -> Generator[None, None, None]:
|
def config_flow_fixture(hass: HomeAssistant) -> Generator[None, None, None]:
|
||||||
"""Mock config flow."""
|
"""Mock config flow."""
|
||||||
|
@ -728,3 +742,58 @@ async def test_move_item_unsupported(
|
||||||
resp = await client.receive_json()
|
resp = await client.receive_json()
|
||||||
assert resp.get("id") == 1
|
assert resp.get("id") == 1
|
||||||
assert resp.get("error", {}).get("code") == "not_supported"
|
assert resp.get("error", {}).get("code") == "not_supported"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_add_item_intent(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
hass_ws_client: WebSocketGenerator,
|
||||||
|
) -> None:
|
||||||
|
"""Test adding items to lists using an intent."""
|
||||||
|
await todo_intent.async_setup_intents(hass)
|
||||||
|
|
||||||
|
entity1 = MockTodoListEntity()
|
||||||
|
entity1._attr_name = "List 1"
|
||||||
|
entity1.entity_id = "todo.list_1"
|
||||||
|
|
||||||
|
entity2 = MockTodoListEntity()
|
||||||
|
entity2._attr_name = "List 2"
|
||||||
|
entity2.entity_id = "todo.list_2"
|
||||||
|
|
||||||
|
await create_mock_platform(hass, [entity1, entity2])
|
||||||
|
|
||||||
|
# Add to first list
|
||||||
|
response = await intent.async_handle(
|
||||||
|
hass, "test", todo_intent.INTENT_ADD_ITEM, {"item": {"value": "beer"}}
|
||||||
|
)
|
||||||
|
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||||
|
|
||||||
|
assert len(entity1.items) == 1
|
||||||
|
assert len(entity2.items) == 0
|
||||||
|
assert entity1.items[0].summary == "beer"
|
||||||
|
entity1.items.clear()
|
||||||
|
|
||||||
|
# Add to list by name
|
||||||
|
response = await intent.async_handle(
|
||||||
|
hass,
|
||||||
|
"test",
|
||||||
|
todo_intent.INTENT_ADD_ITEM,
|
||||||
|
{"item": {"value": "cheese"}, "list": {"value": "List 2"}},
|
||||||
|
)
|
||||||
|
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||||
|
|
||||||
|
assert len(entity1.items) == 0
|
||||||
|
assert len(entity2.items) == 1
|
||||||
|
assert entity2.items[0].summary == "cheese"
|
||||||
|
|
||||||
|
# List name is case insensitive
|
||||||
|
response = await intent.async_handle(
|
||||||
|
hass,
|
||||||
|
"test",
|
||||||
|
todo_intent.INTENT_ADD_ITEM,
|
||||||
|
{"item": {"value": "wine"}, "list": {"value": "lIST 2"}},
|
||||||
|
)
|
||||||
|
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||||
|
|
||||||
|
assert len(entity1.items) == 0
|
||||||
|
assert len(entity2.items) == 2
|
||||||
|
assert entity2.items[1].summary == "wine"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue