RFC: Deprecate auto target all for services and introduce entity_id: * (#19006)

* Deprecate auto target all

* Match on word 'all'
This commit is contained in:
Paulus Schoutsen 2018-12-13 10:07:59 +01:00 committed by GitHub
parent 56c7e78cf2
commit 8ea0a8d40b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 143 additions and 41 deletions

View file

@ -25,7 +25,7 @@ ATTR_CHANGED_BY = 'changed_by'
ENTITY_ID_FORMAT = DOMAIN + '.{}' ENTITY_ID_FORMAT = DOMAIN + '.{}'
ALARM_SERVICE_SCHEMA = vol.Schema({ 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, vol.Optional(ATTR_CODE): cv.string,
}) })

View file

@ -94,11 +94,11 @@ PLATFORM_SCHEMA = vol.Schema({
}) })
SERVICE_SCHEMA = vol.Schema({ 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({ 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, vol.Optional(ATTR_VARIABLES, default={}): dict,
}) })

View file

@ -61,7 +61,7 @@ FALLBACK_STREAM_INTERVAL = 1 # seconds
MIN_STREAM_INTERVAL = 0.5 # seconds MIN_STREAM_INTERVAL = 0.5 # seconds
CAMERA_SERVICE_SCHEMA = vol.Schema({ 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({ CAMERA_SERVICE_SNAPSHOT = CAMERA_SERVICE_SCHEMA.extend({

View file

@ -92,15 +92,15 @@ CONVERTIBLE_ATTRIBUTE = [
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
ON_OFF_SERVICE_SCHEMA = vol.Schema({ 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({ 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, vol.Required(ATTR_AWAY_MODE): cv.boolean,
}) })
SET_AUX_HEAT_SCHEMA = vol.Schema({ 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, vol.Required(ATTR_AUX_HEAT): cv.boolean,
}) })
SET_TEMPERATURE_SCHEMA = vol.Schema(vol.All( SET_TEMPERATURE_SCHEMA = vol.Schema(vol.All(
@ -110,28 +110,28 @@ SET_TEMPERATURE_SCHEMA = vol.Schema(vol.All(
vol.Exclusive(ATTR_TEMPERATURE, 'temperature'): vol.Coerce(float), vol.Exclusive(ATTR_TEMPERATURE, 'temperature'): vol.Coerce(float),
vol.Inclusive(ATTR_TARGET_TEMP_HIGH, '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.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, vol.Optional(ATTR_OPERATION_MODE): cv.string,
} }
)) ))
SET_FAN_MODE_SCHEMA = vol.Schema({ 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, vol.Required(ATTR_FAN_MODE): cv.string,
}) })
SET_HOLD_MODE_SCHEMA = vol.Schema({ 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, vol.Required(ATTR_HOLD_MODE): cv.string,
}) })
SET_OPERATION_MODE_SCHEMA = vol.Schema({ 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, vol.Required(ATTR_OPERATION_MODE): cv.string,
}) })
SET_HUMIDITY_SCHEMA = vol.Schema({ 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), vol.Required(ATTR_HUMIDITY): vol.Coerce(float),
}) })
SET_SWING_MODE_SCHEMA = vol.Schema({ 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, vol.Required(ATTR_SWING_MODE): cv.string,
}) })

View file

@ -33,7 +33,7 @@ SERVICE_INCREMENT = 'increment'
SERVICE_RESET = 'reset' SERVICE_RESET = 'reset'
SERVICE_SCHEMA = vol.Schema({ 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({ CONFIG_SCHEMA = vol.Schema({

View file

@ -60,7 +60,7 @@ INTENT_OPEN_COVER = 'HassOpenCover'
INTENT_CLOSE_COVER = 'HassCloseCover' INTENT_CLOSE_COVER = 'HassCloseCover'
COVER_SERVICE_SCHEMA = vol.Schema({ 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({ COVER_SET_COVER_POSITION_SCHEMA = COVER_SERVICE_SCHEMA.extend({

View file

@ -61,30 +61,30 @@ PROP_TO_ATTR = {
} # type: dict } # type: dict
FAN_SET_SPEED_SCHEMA = vol.Schema({ 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 vol.Required(ATTR_SPEED): cv.string
}) # type: dict }) # type: dict
FAN_TURN_ON_SCHEMA = vol.Schema({ 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 vol.Optional(ATTR_SPEED): cv.string
}) # type: dict }) # type: dict
FAN_TURN_OFF_SCHEMA = vol.Schema({ 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 }) # type: dict
FAN_OSCILLATE_SCHEMA = vol.Schema({ 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 vol.Required(ATTR_OSCILLATING): cv.boolean
}) # type: dict }) # type: dict
FAN_TOGGLE_SCHEMA = vol.Schema({ 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({ 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 vol.Optional(ATTR_DIRECTION): cv.string
}) # type: dict }) # type: dict

View file

@ -49,7 +49,7 @@ SERVICE_REMOVE = 'remove'
CONTROL_TYPES = vol.In(['hidden', None]) CONTROL_TYPES = vol.In(['hidden', None])
SET_VISIBILITY_SERVICE_SCHEMA = vol.Schema({ 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 vol.Required(ATTR_VISIBLE): cv.boolean
}) })

View file

@ -62,7 +62,7 @@ PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
}) })
SERVICE_SCAN_SCHEMA = vol.Schema({ SERVICE_SCAN_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
}) })

View file

@ -88,7 +88,7 @@ VALID_BRIGHTNESS = vol.All(vol.Coerce(int), vol.Clamp(min=0, max=255))
VALID_BRIGHTNESS_PCT = vol.All(vol.Coerce(float), vol.Range(min=0, max=100)) VALID_BRIGHTNESS_PCT = vol.All(vol.Coerce(float), vol.Range(min=0, max=100))
LIGHT_TURN_ON_SCHEMA = vol.Schema({ 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, vol.Exclusive(ATTR_PROFILE, COLOR_GROUP): cv.string,
ATTR_TRANSITION: VALID_TRANSITION, ATTR_TRANSITION: VALID_TRANSITION,
ATTR_BRIGHTNESS: VALID_BRIGHTNESS, ATTR_BRIGHTNESS: VALID_BRIGHTNESS,
@ -115,13 +115,13 @@ LIGHT_TURN_ON_SCHEMA = vol.Schema({
}) })
LIGHT_TURN_OFF_SCHEMA = vol.Schema({ LIGHT_TURN_OFF_SCHEMA = vol.Schema({
ATTR_ENTITY_ID: cv.entity_ids, ATTR_ENTITY_ID: cv.comp_entity_ids,
ATTR_TRANSITION: VALID_TRANSITION, ATTR_TRANSITION: VALID_TRANSITION,
ATTR_FLASH: vol.In([FLASH_SHORT, FLASH_LONG]), ATTR_FLASH: vol.In([FLASH_SHORT, FLASH_LONG]),
}) })
LIGHT_TOGGLE_SCHEMA = vol.Schema({ LIGHT_TOGGLE_SCHEMA = vol.Schema({
ATTR_ENTITY_ID: cv.entity_ids, ATTR_ENTITY_ID: cv.comp_entity_ids,
ATTR_TRANSITION: VALID_TRANSITION, ATTR_TRANSITION: VALID_TRANSITION,
}) })

View file

@ -34,7 +34,7 @@ GROUP_NAME_ALL_LOCKS = 'all locks'
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
LOCK_SERVICE_SCHEMA = vol.Schema({ 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, vol.Optional(ATTR_CODE): cv.string,
}) })

View file

@ -117,7 +117,7 @@ SUPPORT_SELECT_SOUND_MODE = 65536
# Service call validation schemas # Service call validation schemas
MEDIA_PLAYER_SCHEMA = vol.Schema({ 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({ MEDIA_PLAYER_SET_VOLUME_SCHEMA = MEDIA_PLAYER_SCHEMA.extend({

View file

@ -46,7 +46,7 @@ DEFAULT_NUM_REPEATS = 1
DEFAULT_DELAY_SECS = 0.4 DEFAULT_DELAY_SECS = 0.4
REMOTE_SERVICE_SCHEMA = vol.Schema({ 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({ REMOTE_SERVICE_ACTIVITY_SCHEMA = REMOTE_SERVICE_SCHEMA.extend({

View file

@ -56,7 +56,7 @@ PLATFORM_SCHEMA = vol.Schema(
), extra=vol.ALLOW_EXTRA) ), extra=vol.ALLOW_EXTRA)
SCENE_SERVICE_SCHEMA = vol.Schema({ SCENE_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
}) })

View file

@ -39,7 +39,7 @@ PROP_TO_ATTR = {
} }
SWITCH_SERVICE_SCHEMA = vol.Schema({ 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__) _LOGGER = logging.getLogger(__name__)

View file

@ -49,7 +49,7 @@ SERVICE_PAUSE = 'pause'
SERVICE_STOP = 'stop' SERVICE_STOP = 'stop'
VACUUM_SERVICE_SCHEMA = vol.Schema({ 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({ VACUUM_SET_FAN_SPEED_SERVICE_SCHEMA = VACUUM_SERVICE_SCHEMA.extend({

View file

@ -57,22 +57,22 @@ CONVERTIBLE_ATTRIBUTE = [
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
ON_OFF_SERVICE_SCHEMA = vol.Schema({ 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({ 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, vol.Required(ATTR_AWAY_MODE): cv.boolean,
}) })
SET_TEMPERATURE_SCHEMA = vol.Schema(vol.All( SET_TEMPERATURE_SCHEMA = vol.Schema(vol.All(
{ {
vol.Required(ATTR_TEMPERATURE, 'temperature'): vol.Coerce(float), 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, vol.Optional(ATTR_OPERATION_MODE): cv.string,
} }
)) ))
SET_OPERATION_MODE_SCHEMA = vol.Schema({ 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, vol.Required(ATTR_OPERATION_MODE): cv.string,
}) })

View file

@ -13,6 +13,9 @@ PLATFORM_FORMAT = '{}.{}'
# Can be used to specify a catch all when registering state or event listeners. # Can be used to specify a catch all when registering state or event listeners.
MATCH_ALL = '*' MATCH_ALL = '*'
# Entity target all constant
ENTITY_MATCH_ALL = 'all'
# If no name is specified # If no name is specified
DEVICE_DEFAULT_NAME = 'Unnamed Device' DEVICE_DEFAULT_NAME = 'Unnamed Device'

View file

@ -15,7 +15,8 @@ from homeassistant.const import (
CONF_PLATFORM, CONF_SCAN_INTERVAL, TEMP_CELSIUS, TEMP_FAHRENHEIT, CONF_PLATFORM, CONF_SCAN_INTERVAL, TEMP_CELSIUS, TEMP_FAHRENHEIT,
CONF_ALIAS, CONF_ENTITY_ID, CONF_VALUE_TEMPLATE, WEEKDAYS, CONF_ALIAS, CONF_ENTITY_ID, CONF_VALUE_TEMPLATE, WEEKDAYS,
CONF_CONDITION, CONF_BELOW, CONF_ABOVE, CONF_TIMEOUT, SUN_EVENT_SUNSET, 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,
ENTITY_MATCH_ALL)
from homeassistant.core import valid_entity_id, split_entity_id from homeassistant.core import valid_entity_id, split_entity_id
from homeassistant.exceptions import TemplateError from homeassistant.exceptions import TemplateError
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
@ -161,6 +162,12 @@ def entity_ids(value: Union[str, Sequence]) -> Sequence[str]:
return [entity_id(ent_id) for ent_id in value] return [entity_id(ent_id) for ent_id in value]
comp_entity_ids = vol.Any(
vol.All(vol.Lower, ENTITY_MATCH_ALL),
entity_ids
)
def entity_domain(domain: str): def entity_domain(domain: str):
"""Validate that entity belong to domain.""" """Validate that entity belong to domain."""
def validate(value: Any) -> str: def validate(value: Any) -> str:

View file

@ -7,7 +7,7 @@ import logging
from homeassistant import config as conf_util from homeassistant import config as conf_util
from homeassistant.setup import async_prepare_setup_platform from homeassistant.setup import async_prepare_setup_platform
from homeassistant.const import ( 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.core import callback
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_per_platform, discovery from homeassistant.helpers import config_per_platform, discovery
@ -161,7 +161,15 @@ class EntityComponent:
This method must be run in the event loop. 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] return [entity for entity in self.entities if entity.available]
entity_ids = set(extract_entity_ids(self.hass, service, expand_group)) entity_ids = set(extract_entity_ids(self.hass, service, expand_group))

View file

@ -6,7 +6,7 @@ from os import path
import voluptuous as vol import voluptuous as vol
from homeassistant.auth.permissions.const import POLICY_CONTROL from homeassistant.auth.permissions.const import POLICY_CONTROL
from homeassistant.const import ATTR_ENTITY_ID from homeassistant.const import ATTR_ENTITY_ID, ENTITY_MATCH_ALL
import homeassistant.core as ha import homeassistant.core as ha
from homeassistant.exceptions import TemplateError, Unauthorized, UnknownUser from homeassistant.exceptions import TemplateError, Unauthorized, UnknownUser
from homeassistant.helpers import template from homeassistant.helpers import template
@ -197,7 +197,12 @@ async def entity_service_call(hass, platforms, func, call):
entity_perms = None entity_perms = None
# Are we trying to target all entities # 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] == ENTITY_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: if not target_all_entities:
# A set of entities we're trying to target. # A set of entities we're trying to target.

View file

@ -584,3 +584,16 @@ def test_is_regex():
valid_re = ".*" valid_re = ".*"
schema(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)

View file

@ -452,3 +452,37 @@ async def test_set_service_race(hass):
await hass.async_block_till_done() await hass.async_block_till_done()
assert not exception 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

View file

@ -306,3 +306,35 @@ async def test_call_no_context_target_specific(
assert len(mock_service_platform_call.mock_calls) == 1 assert len(mock_service_platform_call.mock_calls) == 1
entities = mock_service_platform_call.mock_calls[0][1][2] entities = mock_service_platform_call.mock_calls[0][1][2]
assert entities == [mock_entities['light.kitchen']] 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': 'all'
}))
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