From 454cc684e43447153f5374e5c31cd97ca3f38bc3 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Mon, 9 Dec 2019 15:15:32 -0500 Subject: [PATCH] Add input_select reload service. (#29647) * Add input_select reload service. * Add test. --- .../components/input_select/__init__.py | 46 +++++++--- .../components/input_select/services.yaml | 2 + tests/components/input_select/test_init.py | 89 ++++++++++++++++++- 3 files changed, 125 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/input_select/__init__.py b/homeassistant/components/input_select/__init__.py index ae609e09271..b2b4b2083e8 100644 --- a/homeassistant/components/input_select/__init__.py +++ b/homeassistant/components/input_select/__init__.py @@ -3,10 +3,11 @@ import logging import voluptuous as vol -from homeassistant.const import CONF_ICON, CONF_NAME +from homeassistant.const import CONF_ICON, CONF_NAME, SERVICE_RELOAD import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.restore_state import RestoreEntity +import homeassistant.helpers.service _LOGGER = logging.getLogger(__name__) @@ -61,23 +62,31 @@ CONFIG_SCHEMA = vol.Schema( required=True, extra=vol.ALLOW_EXTRA, ) +RELOAD_SERVICE_SCHEMA = vol.Schema({}) async def async_setup(hass, config): """Set up an input select.""" component = EntityComponent(_LOGGER, DOMAIN, hass) - entities = [] + entities = await _async_process_config(config) - for object_id, cfg in config[DOMAIN].items(): - name = cfg.get(CONF_NAME) - options = cfg.get(CONF_OPTIONS) - initial = cfg.get(CONF_INITIAL) - icon = cfg.get(CONF_ICON) - entities.append(InputSelect(object_id, name, initial, options, icon)) + async def reload_service_handler(service_call): + """Remove all entities and load new ones from config.""" + conf = await component.async_prepare_reload() + if conf is None: + return + new_entities = await _async_process_config(conf) + if new_entities: + await component.async_add_entities(new_entities) - if not entities: - return False + homeassistant.helpers.service.async_register_admin_service( + hass, + DOMAIN, + SERVICE_RELOAD, + reload_service_handler, + schema=RELOAD_SERVICE_SCHEMA, + ) component.async_register_entity_service( SERVICE_SELECT_OPTION, @@ -103,10 +112,25 @@ async def async_setup(hass, config): "async_set_options", ) - await component.async_add_entities(entities) + if entities: + await component.async_add_entities(entities) return True +async def _async_process_config(config): + """Process config and create list of entities.""" + entities = [] + + for object_id, cfg in config[DOMAIN].items(): + name = cfg.get(CONF_NAME) + options = cfg.get(CONF_OPTIONS) + initial = cfg.get(CONF_INITIAL) + icon = cfg.get(CONF_ICON) + entities.append(InputSelect(object_id, name, initial, options, icon)) + + return entities + + class InputSelect(RestoreEntity): """Representation of a select input.""" diff --git a/homeassistant/components/input_select/services.yaml b/homeassistant/components/input_select/services.yaml index 8084e56b731..2cce496d0b6 100644 --- a/homeassistant/components/input_select/services.yaml +++ b/homeassistant/components/input_select/services.yaml @@ -20,3 +20,5 @@ set_options: for., example: input_select.my_select} options: {description: Options for the input select entity., example: '["Item A", "Item B", "Item C"]'} +reload: + description: Reload the input_select configuration. diff --git a/tests/components/input_select/test_init.py b/tests/components/input_select/test_init.py index 6c5d8501239..51f0b24bc8a 100644 --- a/tests/components/input_select/test_init.py +++ b/tests/components/input_select/test_init.py @@ -1,6 +1,9 @@ """The tests for the Input select component.""" # pylint: disable=protected-access import asyncio +from unittest.mock import patch + +import pytest from homeassistant.components.input_select import ( ATTR_OPTION, @@ -11,8 +14,14 @@ from homeassistant.components.input_select import ( SERVICE_SELECT_PREVIOUS, SERVICE_SET_OPTIONS, ) -from homeassistant.const import ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, ATTR_ICON +from homeassistant.const import ( + ATTR_ENTITY_ID, + ATTR_FRIENDLY_NAME, + ATTR_ICON, + SERVICE_RELOAD, +) from homeassistant.core import Context, State +from homeassistant.exceptions import Unauthorized from homeassistant.loader import bind_hass from homeassistant.setup import async_setup_component @@ -322,3 +331,81 @@ async def test_input_select_context(hass, hass_admin_user): assert state2 is not None assert state.state != state2.state assert state2.context.user_id == hass_admin_user.id + + +async def test_reload(hass, hass_admin_user, hass_read_only_user): + """Test reload service.""" + count_start = len(hass.states.async_entity_ids()) + + assert await async_setup_component( + hass, + DOMAIN, + { + DOMAIN: { + "test_1": { + "options": ["first option", "middle option", "last option"], + "initial": "middle option", + }, + "test_2": { + "options": ["an option", "not an option"], + "initial": "an option", + }, + } + }, + ) + + assert count_start + 2 == len(hass.states.async_entity_ids()) + + state_1 = hass.states.get("input_select.test_1") + state_2 = hass.states.get("input_select.test_2") + state_3 = hass.states.get("input_select.test_3") + + assert state_1 is not None + assert state_2 is not None + assert state_3 is None + assert "middle option" == state_1.state + assert "an option" == state_2.state + + with patch( + "homeassistant.config.load_yaml_config_file", + autospec=True, + return_value={ + DOMAIN: { + "test_2": { + "options": ["an option", "reloaded option"], + "initial": "reloaded option", + }, + "test_3": { + "options": ["new option", "newer option"], + "initial": "newer option", + }, + } + }, + ): + with patch("homeassistant.config.find_config_file", return_value=""): + with pytest.raises(Unauthorized): + await hass.services.async_call( + DOMAIN, + SERVICE_RELOAD, + blocking=True, + context=Context(user_id=hass_read_only_user.id), + ) + await hass.services.async_call( + DOMAIN, + SERVICE_RELOAD, + blocking=True, + context=Context(user_id=hass_admin_user.id), + ) + await hass.async_block_till_done() + + assert count_start + 2 == len(hass.states.async_entity_ids()) + + state_1 = hass.states.get("input_select.test_1") + state_2 = hass.states.get("input_select.test_2") + state_3 = hass.states.get("input_select.test_3") + + assert state_1 is None + assert state_2 is not None + assert state_3 is not None + assert "reloaded option" == state_2.state + assert "newer option" == state_3.state