From 4a627eaae4dececcfd92e8a2d2d3a643a3275004 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 4 Dec 2018 13:17:28 +0100 Subject: [PATCH 1/2] Deprecate auto target all --- .../alarm_control_panel/__init__.py | 2 +- .../components/automation/__init__.py | 4 +-- homeassistant/components/camera/__init__.py | 2 +- homeassistant/components/climate/__init__.py | 18 +++++----- homeassistant/components/counter/__init__.py | 2 +- homeassistant/components/cover/__init__.py | 2 +- homeassistant/components/fan/__init__.py | 12 +++---- homeassistant/components/group/__init__.py | 2 +- .../components/image_processing/__init__.py | 2 +- homeassistant/components/light/__init__.py | 6 ++-- homeassistant/components/lock/__init__.py | 2 +- .../components/media_player/__init__.py | 2 +- homeassistant/components/remote/__init__.py | 2 +- homeassistant/components/scene/__init__.py | 2 +- homeassistant/components/switch/__init__.py | 2 +- homeassistant/components/vacuum/__init__.py | 2 +- .../components/water_heater/__init__.py | 8 ++--- homeassistant/helpers/config_validation.py | 6 +++- homeassistant/helpers/entity_component.py | 12 +++++-- homeassistant/helpers/service.py | 9 +++-- tests/helpers/test_entity_component.py | 34 +++++++++++++++++++ tests/helpers/test_service.py | 32 +++++++++++++++++ 22 files changed, 124 insertions(+), 41 deletions(-) diff --git a/homeassistant/components/alarm_control_panel/__init__.py b/homeassistant/components/alarm_control_panel/__init__.py index a42e6e880b514c..ad8520118b45b9 100644 --- a/homeassistant/components/alarm_control_panel/__init__.py +++ b/homeassistant/components/alarm_control_panel/__init__.py @@ -25,7 +25,7 @@ ENTITY_ID_FORMAT = DOMAIN + '.{}' ALARM_SERVICE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids, vol.Optional(ATTR_CODE): cv.string, }) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 4a2df399e0a209..d062376f2a8c37 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -94,11 +94,11 @@ def _platform_validator(config): }) SERVICE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids, }) TRIGGER_SERVICE_SCHEMA = vol.Schema({ - vol.Required(ATTR_ENTITY_ID): cv.entity_ids, + vol.Required(ATTR_ENTITY_ID): cv.comp_entity_ids, vol.Optional(ATTR_VARIABLES, default={}): dict, }) diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 0463b172d7a3db..653d0315ad4e34 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -61,7 +61,7 @@ MIN_STREAM_INTERVAL = 0.5 # seconds CAMERA_SERVICE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids, }) CAMERA_SERVICE_SNAPSHOT = CAMERA_SERVICE_SCHEMA.extend({ diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index 4b73e24fb417ba..d116a885319067 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -92,15 +92,15 @@ _LOGGER = logging.getLogger(__name__) ON_OFF_SERVICE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids, }) SET_AWAY_MODE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids, vol.Required(ATTR_AWAY_MODE): cv.boolean, }) SET_AUX_HEAT_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids, vol.Required(ATTR_AUX_HEAT): cv.boolean, }) SET_TEMPERATURE_SCHEMA = vol.Schema(vol.All( @@ -110,28 +110,28 @@ vol.Exclusive(ATTR_TEMPERATURE, 'temperature'): vol.Coerce(float), vol.Inclusive(ATTR_TARGET_TEMP_HIGH, 'temperature'): vol.Coerce(float), vol.Inclusive(ATTR_TARGET_TEMP_LOW, 'temperature'): vol.Coerce(float), - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids, vol.Optional(ATTR_OPERATION_MODE): cv.string, } )) SET_FAN_MODE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids, vol.Required(ATTR_FAN_MODE): cv.string, }) SET_HOLD_MODE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids, vol.Required(ATTR_HOLD_MODE): cv.string, }) SET_OPERATION_MODE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids, vol.Required(ATTR_OPERATION_MODE): cv.string, }) SET_HUMIDITY_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids, vol.Required(ATTR_HUMIDITY): vol.Coerce(float), }) SET_SWING_MODE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids, vol.Required(ATTR_SWING_MODE): cv.string, }) diff --git a/homeassistant/components/counter/__init__.py b/homeassistant/components/counter/__init__.py index 228870489a2d68..cd3a29df2b642f 100644 --- a/homeassistant/components/counter/__init__.py +++ b/homeassistant/components/counter/__init__.py @@ -33,7 +33,7 @@ SERVICE_RESET = 'reset' SERVICE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids, }) CONFIG_SCHEMA = vol.Schema({ diff --git a/homeassistant/components/cover/__init__.py b/homeassistant/components/cover/__init__.py index ec11b139f6b081..ef8fcc42302da1 100644 --- a/homeassistant/components/cover/__init__.py +++ b/homeassistant/components/cover/__init__.py @@ -60,7 +60,7 @@ INTENT_CLOSE_COVER = 'HassCloseCover' COVER_SERVICE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids, }) COVER_SET_COVER_POSITION_SCHEMA = COVER_SERVICE_SCHEMA.extend({ diff --git a/homeassistant/components/fan/__init__.py b/homeassistant/components/fan/__init__.py index 36b075747e07ae..a54d52f4b12de3 100644 --- a/homeassistant/components/fan/__init__.py +++ b/homeassistant/components/fan/__init__.py @@ -61,30 +61,30 @@ } # type: dict FAN_SET_SPEED_SCHEMA = vol.Schema({ - vol.Required(ATTR_ENTITY_ID): cv.entity_ids, + vol.Required(ATTR_ENTITY_ID): cv.comp_entity_ids, vol.Required(ATTR_SPEED): cv.string }) # type: dict FAN_TURN_ON_SCHEMA = vol.Schema({ - vol.Required(ATTR_ENTITY_ID): cv.entity_ids, + vol.Required(ATTR_ENTITY_ID): cv.comp_entity_ids, vol.Optional(ATTR_SPEED): cv.string }) # type: dict FAN_TURN_OFF_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids + vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids }) # type: dict FAN_OSCILLATE_SCHEMA = vol.Schema({ - vol.Required(ATTR_ENTITY_ID): cv.entity_ids, + vol.Required(ATTR_ENTITY_ID): cv.comp_entity_ids, vol.Required(ATTR_OSCILLATING): cv.boolean }) # type: dict FAN_TOGGLE_SCHEMA = vol.Schema({ - vol.Required(ATTR_ENTITY_ID): cv.entity_ids + vol.Required(ATTR_ENTITY_ID): cv.comp_entity_ids }) FAN_SET_DIRECTION_SCHEMA = vol.Schema({ - vol.Required(ATTR_ENTITY_ID): cv.entity_ids, + vol.Required(ATTR_ENTITY_ID): cv.comp_entity_ids, vol.Optional(ATTR_DIRECTION): cv.string }) # type: dict diff --git a/homeassistant/components/group/__init__.py b/homeassistant/components/group/__init__.py index 15a3816c559e83..b6dcd65fc2c89a 100644 --- a/homeassistant/components/group/__init__.py +++ b/homeassistant/components/group/__init__.py @@ -49,7 +49,7 @@ CONTROL_TYPES = vol.In(['hidden', None]) SET_VISIBILITY_SERVICE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids, vol.Required(ATTR_VISIBLE): cv.boolean }) diff --git a/homeassistant/components/image_processing/__init__.py b/homeassistant/components/image_processing/__init__.py index 72a4a8155e2911..b2cbb2b2391afc 100644 --- a/homeassistant/components/image_processing/__init__.py +++ b/homeassistant/components/image_processing/__init__.py @@ -62,7 +62,7 @@ }) SERVICE_SCAN_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids, }) diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 41dbbcd6d0c95c..aa12e56251585b 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -88,7 +88,7 @@ VALID_BRIGHTNESS_PCT = vol.All(vol.Coerce(float), vol.Range(min=0, max=100)) LIGHT_TURN_ON_SCHEMA = vol.Schema({ - ATTR_ENTITY_ID: cv.entity_ids, + ATTR_ENTITY_ID: cv.comp_entity_ids, vol.Exclusive(ATTR_PROFILE, COLOR_GROUP): cv.string, ATTR_TRANSITION: VALID_TRANSITION, ATTR_BRIGHTNESS: VALID_BRIGHTNESS, @@ -115,13 +115,13 @@ }) LIGHT_TURN_OFF_SCHEMA = vol.Schema({ - ATTR_ENTITY_ID: cv.entity_ids, + ATTR_ENTITY_ID: cv.comp_entity_ids, ATTR_TRANSITION: VALID_TRANSITION, ATTR_FLASH: vol.In([FLASH_SHORT, FLASH_LONG]), }) LIGHT_TOGGLE_SCHEMA = vol.Schema({ - ATTR_ENTITY_ID: cv.entity_ids, + ATTR_ENTITY_ID: cv.comp_entity_ids, ATTR_TRANSITION: VALID_TRANSITION, }) diff --git a/homeassistant/components/lock/__init__.py b/homeassistant/components/lock/__init__.py index 22923602dc2957..72e87f763d203b 100644 --- a/homeassistant/components/lock/__init__.py +++ b/homeassistant/components/lock/__init__.py @@ -34,7 +34,7 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) LOCK_SERVICE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids, vol.Optional(ATTR_CODE): cv.string, }) diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index 8530a01d3e643d..cd109cce7d348d 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -117,7 +117,7 @@ # Service call validation schemas MEDIA_PLAYER_SCHEMA = vol.Schema({ - ATTR_ENTITY_ID: cv.entity_ids, + ATTR_ENTITY_ID: cv.comp_entity_ids, }) MEDIA_PLAYER_SET_VOLUME_SCHEMA = MEDIA_PLAYER_SCHEMA.extend({ diff --git a/homeassistant/components/remote/__init__.py b/homeassistant/components/remote/__init__.py index 4fc491e57e83f0..162cb41d92e17b 100644 --- a/homeassistant/components/remote/__init__.py +++ b/homeassistant/components/remote/__init__.py @@ -46,7 +46,7 @@ DEFAULT_DELAY_SECS = 0.4 REMOTE_SERVICE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids, }) REMOTE_SERVICE_ACTIVITY_SCHEMA = REMOTE_SERVICE_SCHEMA.extend({ diff --git a/homeassistant/components/scene/__init__.py b/homeassistant/components/scene/__init__.py index 2bcb1c8e16d7a3..b3ab522887557f 100644 --- a/homeassistant/components/scene/__init__.py +++ b/homeassistant/components/scene/__init__.py @@ -56,7 +56,7 @@ def _platform_validator(config): ), extra=vol.ALLOW_EXTRA) SCENE_SERVICE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Required(ATTR_ENTITY_ID): cv.entity_ids, }) diff --git a/homeassistant/components/switch/__init__.py b/homeassistant/components/switch/__init__.py index 1adabe4b57e4af..513ebbcb5ea2e7 100644 --- a/homeassistant/components/switch/__init__.py +++ b/homeassistant/components/switch/__init__.py @@ -39,7 +39,7 @@ } SWITCH_SERVICE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids, }) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/vacuum/__init__.py b/homeassistant/components/vacuum/__init__.py index 212e6bd648f5f6..4799e945be0cb2 100644 --- a/homeassistant/components/vacuum/__init__.py +++ b/homeassistant/components/vacuum/__init__.py @@ -49,7 +49,7 @@ SERVICE_STOP = 'stop' VACUUM_SERVICE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids, }) VACUUM_SET_FAN_SPEED_SERVICE_SCHEMA = VACUUM_SERVICE_SCHEMA.extend({ diff --git a/homeassistant/components/water_heater/__init__.py b/homeassistant/components/water_heater/__init__.py index 92dbebc4421825..fee2846e8d56bc 100644 --- a/homeassistant/components/water_heater/__init__.py +++ b/homeassistant/components/water_heater/__init__.py @@ -57,22 +57,22 @@ _LOGGER = logging.getLogger(__name__) ON_OFF_SERVICE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids, }) SET_AWAY_MODE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids, vol.Required(ATTR_AWAY_MODE): cv.boolean, }) SET_TEMPERATURE_SCHEMA = vol.Schema(vol.All( { vol.Required(ATTR_TEMPERATURE, 'temperature'): vol.Coerce(float), - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids, vol.Optional(ATTR_OPERATION_MODE): cv.string, } )) SET_OPERATION_MODE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids, vol.Required(ATTR_OPERATION_MODE): cv.string, }) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 5c49a1b50e1dfd..0ab834da5d58b5 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -15,7 +15,8 @@ CONF_PLATFORM, CONF_SCAN_INTERVAL, TEMP_CELSIUS, TEMP_FAHRENHEIT, CONF_ALIAS, CONF_ENTITY_ID, CONF_VALUE_TEMPLATE, WEEKDAYS, CONF_CONDITION, CONF_BELOW, CONF_ABOVE, CONF_TIMEOUT, SUN_EVENT_SUNSET, - SUN_EVENT_SUNRISE, CONF_UNIT_SYSTEM_IMPERIAL, CONF_UNIT_SYSTEM_METRIC) + SUN_EVENT_SUNRISE, CONF_UNIT_SYSTEM_IMPERIAL, CONF_UNIT_SYSTEM_METRIC, + MATCH_ALL) from homeassistant.core import valid_entity_id, split_entity_id from homeassistant.exceptions import TemplateError import homeassistant.util.dt as dt_util @@ -161,6 +162,9 @@ def entity_ids(value: Union[str, Sequence]) -> Sequence[str]: return [entity_id(ent_id) for ent_id in value] +comp_entity_ids = vol.Any(MATCH_ALL, entity_ids) + + def entity_domain(domain: str): """Validate that entity belong to domain.""" def validate(value: Any) -> str: diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py index 982c92510a9101..ce876991097f4d 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -7,7 +7,7 @@ from homeassistant import config as conf_util from homeassistant.setup import async_prepare_setup_platform from homeassistant.const import ( - ATTR_ENTITY_ID, CONF_SCAN_INTERVAL, CONF_ENTITY_NAMESPACE) + ATTR_ENTITY_ID, CONF_SCAN_INTERVAL, CONF_ENTITY_NAMESPACE, MATCH_ALL) from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_per_platform, discovery @@ -161,7 +161,15 @@ def async_extract_from_service(self, service, expand_group=True): This method must be run in the event loop. """ - if ATTR_ENTITY_ID not in service.data: + data_ent_id = service.data.get(ATTR_ENTITY_ID) + + if data_ent_id in (None, MATCH_ALL): + if data_ent_id is None: + self.logger.warning( + 'Not passing an entity ID to a service to target all ' + 'entities is deprecated. Update your call to %s.%s to be ' + 'instead: entity_id: "*"', service.domain, service.service) + return [entity for entity in self.entities if entity.available] entity_ids = set(extract_entity_ids(self.hass, service, expand_group)) diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index e8068f5728649f..581aba23c31216 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -6,7 +6,7 @@ import voluptuous as vol from homeassistant.auth.permissions.const import POLICY_CONTROL -from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.const import ATTR_ENTITY_ID, MATCH_ALL import homeassistant.core as ha from homeassistant.exceptions import TemplateError, Unauthorized, UnknownUser from homeassistant.helpers import template @@ -197,7 +197,12 @@ async def entity_service_call(hass, platforms, func, call): entity_perms = None # Are we trying to target all entities - target_all_entities = ATTR_ENTITY_ID not in call.data + if ATTR_ENTITY_ID in call.data: + target_all_entities = call.data[ATTR_ENTITY_ID] == MATCH_ALL + else: + _LOGGER.warning('Not passing an entity ID to a service to target all ' + 'entities is deprecated. Use instead: entity_id: "*"') + target_all_entities = True if not target_all_entities: # A set of entities we're trying to target. diff --git a/tests/helpers/test_entity_component.py b/tests/helpers/test_entity_component.py index 7562a38d268e7a..8f54f0ee5bce70 100644 --- a/tests/helpers/test_entity_component.py +++ b/tests/helpers/test_entity_component.py @@ -452,3 +452,37 @@ def async_loop_exception_handler(_, _2) -> None: await hass.async_block_till_done() assert not exception + + +async def test_extract_all_omit_entity_id(hass, caplog): + """Test extract all with None and *.""" + component = EntityComponent(_LOGGER, DOMAIN, hass) + await component.async_add_entities([ + MockEntity(name='test_1'), + MockEntity(name='test_2'), + ]) + + call = ha.ServiceCall('test', 'service') + + assert ['test_domain.test_1', 'test_domain.test_2'] == \ + sorted(ent.entity_id for ent in + component.async_extract_from_service(call)) + assert ('Not passing an entity ID to a service to target all entities is ' + 'deprecated') in caplog.text + + +async def test_extract_all_use_match_all(hass, caplog): + """Test extract all with None and *.""" + component = EntityComponent(_LOGGER, DOMAIN, hass) + await component.async_add_entities([ + MockEntity(name='test_1'), + MockEntity(name='test_2'), + ]) + + call = ha.ServiceCall('test', 'service', {'entity_id': '*'}) + + assert ['test_domain.test_1', 'test_domain.test_2'] == \ + sorted(ent.entity_id for ent in + component.async_extract_from_service(call)) + assert ('Not passing an entity ID to a service to target all entities is ' + 'deprecated') not in caplog.text diff --git a/tests/helpers/test_service.py b/tests/helpers/test_service.py index 8fca7df69c1346..5996fca78cc4aa 100644 --- a/tests/helpers/test_service.py +++ b/tests/helpers/test_service.py @@ -306,3 +306,35 @@ async def test_call_no_context_target_specific( assert len(mock_service_platform_call.mock_calls) == 1 entities = mock_service_platform_call.mock_calls[0][1][2] assert entities == [mock_entities['light.kitchen']] + + +async def test_call_with_match_all(hass, mock_service_platform_call, + mock_entities, caplog): + """Check we only target allowed entities if targetting all.""" + await service.entity_service_call(hass, [ + Mock(entities=mock_entities) + ], Mock(), ha.ServiceCall('test_domain', 'test_service', { + 'entity_id': '*' + })) + + assert len(mock_service_platform_call.mock_calls) == 1 + entities = mock_service_platform_call.mock_calls[0][1][2] + assert entities == [ + mock_entities['light.kitchen'], mock_entities['light.living_room']] + assert ('Not passing an entity ID to a service to target ' + 'all entities is deprecated') not in caplog.text + + +async def test_call_with_omit_entity_id(hass, mock_service_platform_call, + mock_entities, caplog): + """Check we only target allowed entities if targetting all.""" + await service.entity_service_call(hass, [ + Mock(entities=mock_entities) + ], Mock(), ha.ServiceCall('test_domain', 'test_service')) + + assert len(mock_service_platform_call.mock_calls) == 1 + entities = mock_service_platform_call.mock_calls[0][1][2] + assert entities == [ + mock_entities['light.kitchen'], mock_entities['light.living_room']] + assert ('Not passing an entity ID to a service to target ' + 'all entities is deprecated') in caplog.text From 6bfe294213c6a1aaa7c8d18334b0fd2c27255e8d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 6 Dec 2018 10:01:25 +0100 Subject: [PATCH 2/2] Match on word 'all' --- homeassistant/const.py | 3 +++ homeassistant/helpers/config_validation.py | 7 +++++-- homeassistant/helpers/service.py | 4 ++-- tests/helpers/test_config_validation.py | 13 +++++++++++++ tests/helpers/test_service.py | 2 +- 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 3f24da30a0a416..a03b5fe52bcdec 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -13,6 +13,9 @@ # Can be used to specify a catch all when registering state or event listeners. MATCH_ALL = '*' +# Entity target all constant +ENTITY_MATCH_ALL = 'all' + # If no name is specified DEVICE_DEFAULT_NAME = 'Unnamed Device' diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 0ab834da5d58b5..c14f4e4fadb725 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -16,7 +16,7 @@ CONF_ALIAS, CONF_ENTITY_ID, CONF_VALUE_TEMPLATE, WEEKDAYS, CONF_CONDITION, CONF_BELOW, CONF_ABOVE, CONF_TIMEOUT, SUN_EVENT_SUNSET, SUN_EVENT_SUNRISE, CONF_UNIT_SYSTEM_IMPERIAL, CONF_UNIT_SYSTEM_METRIC, - MATCH_ALL) + ENTITY_MATCH_ALL) from homeassistant.core import valid_entity_id, split_entity_id from homeassistant.exceptions import TemplateError import homeassistant.util.dt as dt_util @@ -162,7 +162,10 @@ def entity_ids(value: Union[str, Sequence]) -> Sequence[str]: return [entity_id(ent_id) for ent_id in value] -comp_entity_ids = vol.Any(MATCH_ALL, entity_ids) +comp_entity_ids = vol.Any( + vol.All(vol.Lower, ENTITY_MATCH_ALL), + entity_ids +) def entity_domain(domain: str): diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index 581aba23c31216..f51d0f8b2485df 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -6,7 +6,7 @@ import voluptuous as vol from homeassistant.auth.permissions.const import POLICY_CONTROL -from homeassistant.const import ATTR_ENTITY_ID, MATCH_ALL +from homeassistant.const import ATTR_ENTITY_ID, ENTITY_MATCH_ALL import homeassistant.core as ha from homeassistant.exceptions import TemplateError, Unauthorized, UnknownUser from homeassistant.helpers import template @@ -198,7 +198,7 @@ async def entity_service_call(hass, platforms, func, call): # Are we trying to target all entities if ATTR_ENTITY_ID in call.data: - target_all_entities = call.data[ATTR_ENTITY_ID] == MATCH_ALL + target_all_entities = call.data[ATTR_ENTITY_ID] == ENTITY_MATCH_ALL else: _LOGGER.warning('Not passing an entity ID to a service to target all ' 'entities is deprecated. Use instead: entity_id: "*"') diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index cfd84dbc3b3b0a..412882f0a01ed8 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -584,3 +584,16 @@ def test_is_regex(): valid_re = ".*" schema(valid_re) + + +def test_comp_entity_ids(): + """Test config validation for component entity IDs.""" + schema = vol.Schema(cv.comp_entity_ids) + + for valid in ('ALL', 'all', 'AlL', 'light.kitchen', ['light.kitchen'], + ['light.kitchen', 'light.ceiling'], []): + schema(valid) + + for invalid in (['light.kitchen', 'not-entity-id'], '*', ''): + with pytest.raises(vol.Invalid): + schema(invalid) diff --git a/tests/helpers/test_service.py b/tests/helpers/test_service.py index 5996fca78cc4aa..35e89fc52187de 100644 --- a/tests/helpers/test_service.py +++ b/tests/helpers/test_service.py @@ -314,7 +314,7 @@ async def test_call_with_match_all(hass, mock_service_platform_call, await service.entity_service_call(hass, [ Mock(entities=mock_entities) ], Mock(), ha.ServiceCall('test_domain', 'test_service', { - 'entity_id': '*' + 'entity_id': 'all' })) assert len(mock_service_platform_call.mock_calls) == 1