def test_service_schema(): """Test service_schema validation.""" options = ( {}, None, { "service": "homeassistant.turn_on", "service_template": "homeassistant.turn_on", }, {"data": {"entity_id": "light.kitchen"}}, {"service": "homeassistant.turn_on", "data": None}, { "service": "homeassistant.turn_on", "data_template": {"brightness": "{{ no_end"}, }, ) for value in options: with pytest.raises(vol.MultipleInvalid): cv.SERVICE_SCHEMA(value) options = ( {"service": "homeassistant.turn_on"}, {"service": "homeassistant.turn_on", "entity_id": "light.kitchen"}, {"service": "light.turn_on", "entity_id": "all"}, { "service": "homeassistant.turn_on", "entity_id": ["light.kitchen", "light.ceiling"], }, ) for value in options: cv.SERVICE_SCHEMA(value)
def test_service_schema(): """Test service_schema validation.""" options = ( {}, None, { 'service': 'homeassistant.turn_on', 'service_template': 'homeassistant.turn_on' }, { 'data': { 'entity_id': 'light.kitchen' }, }, { 'service': 'homeassistant.turn_on', 'data': None }, { 'service': 'homeassistant.turn_on', 'data_template': { 'brightness': '{{ no_end' } }, ) for value in options: with pytest.raises(vol.MultipleInvalid): cv.SERVICE_SCHEMA(value) options = ( { 'service': 'homeassistant.turn_on' }, { 'service': 'homeassistant.turn_on', 'entity_id': 'light.kitchen', }, { 'service': 'light.turn_on', 'entity_id': 'all', }, { 'service': 'homeassistant.turn_on', 'entity_id': ['light.kitchen', 'light.ceiling'], }, ) for value in options: cv.SERVICE_SCHEMA(value)
def test_entity_service_schema(): """Test make_entity_service_schema validation.""" schema = cv.make_entity_service_schema( {vol.Required("required"): cv.positive_int, vol.Optional("optional"): cv.string} ) options = ( {}, None, {"entity_id": "light.kitchen"}, {"optional": "value", "entity_id": "light.kitchen"}, {"required": 1}, {"required": 2, "area_id": "kitchen", "foo": "bar"}, {"required": "str", "area_id": "kitchen"}, ) for value in options: with pytest.raises(vol.MultipleInvalid): cv.SERVICE_SCHEMA(value) options = ( {"required": 1, "entity_id": "light.kitchen"}, {"required": 2, "optional": "value", "device_id": "a_device"}, {"required": 3, "area_id": "kitchen"}, ) for value in options: schema(value)
def async_call_from_config(hass, config, blocking=False, variables=None, validate_config=True): """Call a service based on a config hash.""" if validate_config: try: config = cv.SERVICE_SCHEMA(config) except vol.Invalid as ex: _LOGGER.error("Invalid config for calling service: %s", ex) return if CONF_SERVICE in config: domain_service = config[CONF_SERVICE] else: try: config[CONF_SERVICE_TEMPLATE].hass = hass domain_service = config[CONF_SERVICE_TEMPLATE].async_render( variables) domain_service = cv.service(domain_service) except TemplateError as ex: _LOGGER.error('Error rendering service name template: %s', ex) return except vol.Invalid as ex: _LOGGER.error('Template rendered invalid service: %s', domain_service) return domain, service_name = domain_service.split('.', 1) service_data = dict(config.get(CONF_SERVICE_DATA, {})) if CONF_SERVICE_DATA_TEMPLATE in config: def _data_template_creator(value): """Recursive template creator helper function.""" if isinstance(value, list): return [_data_template_creator(item) for item in value] elif isinstance(value, dict): return { key: _data_template_creator(item) for key, item in value.items() } value.hass = hass return value.async_render(variables) service_data.update( _data_template_creator(config[CONF_SERVICE_DATA_TEMPLATE])) if CONF_SERVICE_ENTITY_ID in config: service_data[ATTR_ENTITY_ID] = config[CONF_SERVICE_ENTITY_ID] if CONF_SERVICE_EXCLUDE in config: service_data[ATTR_EXCLUDE] = config[CONF_SERVICE_EXCLUDE] yield from hass.services.async_call(domain, service_name, service_data, blocking)
async def async_call_from_config(hass, config, blocking=False, variables=None, validate_config=True, context=None): """Call a service based on a config hash.""" if validate_config: try: config = cv.SERVICE_SCHEMA(config) except vol.Invalid as ex: _LOGGER.error("Invalid config for calling service: %s", ex) return if CONF_SERVICE in config: domain_service = config[CONF_SERVICE] else: try: config[CONF_SERVICE_TEMPLATE].hass = hass domain_service = config[CONF_SERVICE_TEMPLATE].async_render( variables) domain_service = cv.service(domain_service) except TemplateError as ex: if blocking: raise _LOGGER.error('Error rendering service name template: %s', ex) return except vol.Invalid: if blocking: raise _LOGGER.error('Template rendered invalid service: %s', domain_service) return domain, service_name = domain_service.split('.', 1) service_data = dict(config.get(CONF_SERVICE_DATA, {})) if CONF_SERVICE_DATA_TEMPLATE in config: try: template.attach(hass, config[CONF_SERVICE_DATA_TEMPLATE]) service_data.update( template.render_complex(config[CONF_SERVICE_DATA_TEMPLATE], variables)) except TemplateError as ex: _LOGGER.error('Error rendering data template: %s', ex) return if CONF_SERVICE_ENTITY_ID in config: service_data[ATTR_ENTITY_ID] = config[CONF_SERVICE_ENTITY_ID] await hass.services.async_call(domain, service_name, service_data, blocking=blocking, context=context)
def test_service_schema(): """Test service_schema validation.""" options = ( {}, None, { "service": "homeassistant.turn_on", "service_template": "homeassistant.turn_on", }, {"data": {"entity_id": "light.kitchen"}}, {"service": "homeassistant.turn_on", "data": None}, { "service": "homeassistant.turn_on", "data_template": {"brightness": "{{ no_end"}, }, ) for value in options: with pytest.raises(vol.MultipleInvalid): cv.SERVICE_SCHEMA(value) options = ( {"service": "homeassistant.turn_on"}, {"service": "homeassistant.turn_on", "entity_id": "light.kitchen"}, {"service": "light.turn_on", "entity_id": "all"}, { "service": "homeassistant.turn_on", "entity_id": ["light.kitchen", "light.ceiling"], }, { "service": "light.turn_on", "entity_id": "all", "alias": "turn on kitchen lights", }, {"service": "scene.turn_on", "metadata": {}}, ) for value in options: cv.SERVICE_SCHEMA(value) # Check metadata is removed from the validated output assert cv.SERVICE_SCHEMA({"service": "scene.turn_on", "metadata": {}}) == { "service": "scene.turn_on" }
def async_prepare_call_from_config( hass: HomeAssistantType, config: ConfigType, variables: TemplateVarsType = None, validate_config: bool = False, ) -> Tuple[str, str, Dict[str, Any]]: """Prepare to call a service based on a config hash.""" if validate_config: try: config = cv.SERVICE_SCHEMA(config) except vol.Invalid as ex: raise HomeAssistantError( f"Invalid config for calling service: {ex}" ) from ex if CONF_SERVICE in config: domain_service = config[CONF_SERVICE] else: domain_service = config[CONF_SERVICE_TEMPLATE] if isinstance(domain_service, template.Template): try: domain_service.hass = hass domain_service = domain_service.async_render(variables) domain_service = cv.service(domain_service) except TemplateError as ex: raise HomeAssistantError( f"Error rendering service name template: {ex}" ) from ex except vol.Invalid as ex: raise HomeAssistantError( f"Template rendered invalid service: {domain_service}" ) from ex domain, service = domain_service.split(".", 1) service_data = {} if CONF_TARGET in config: service_data.update(config[CONF_TARGET]) for conf in [CONF_SERVICE_DATA, CONF_SERVICE_DATA_TEMPLATE]: if conf not in config: continue try: template.attach(hass, config[conf]) service_data.update(template.render_complex(config[conf], variables)) except TemplateError as ex: raise HomeAssistantError(f"Error rendering data template: {ex}") from ex if CONF_SERVICE_ENTITY_ID in config: service_data[ATTR_ENTITY_ID] = config[CONF_SERVICE_ENTITY_ID] return domain, service, service_data
def call_from_config(hass, config, blocking=False, variables=None, validate_config=True): """Call a service based on a config hash.""" if validate_config: try: config = cv.SERVICE_SCHEMA(config) except vol.Invalid as ex: _LOGGER.error("Invalid config for calling service: %s", ex) return if CONF_SERVICE in config: domain_service = config[CONF_SERVICE] else: try: domain_service = template.render(hass, config[CONF_SERVICE_TEMPLATE], variables) domain_service = cv.service(domain_service) except TemplateError as ex: _LOGGER.error('Error rendering service name template: %s', ex) return except vol.Invalid as ex: _LOGGER.error('Template rendered invalid service: %s', domain_service) return domain, service_name = domain_service.split('.', 1) service_data = dict(config.get(CONF_SERVICE_DATA, {})) def _data_template_creator(value): """Recursive template creator helper function.""" if isinstance(value, list): for idx, element in enumerate(value): value[idx] = _data_template_creator(element) return value if isinstance(value, dict): for key, element in value.items(): value[key] = _data_template_creator(element) return value return template.render(hass, value, variables) if CONF_SERVICE_DATA_TEMPLATE in config: for key, value in config[CONF_SERVICE_DATA_TEMPLATE].items(): service_data[key] = _data_template_creator(value) if CONF_SERVICE_ENTITY_ID in config: service_data[ATTR_ENTITY_ID] = config[CONF_SERVICE_ENTITY_ID] hass.services.call(domain, service_name, service_data, blocking)
def test_entity_service_schema_with_metadata(): """Test make_entity_service_schema with overridden metadata key.""" schema = cv.make_entity_service_schema({vol.Required("metadata"): cv.positive_int}) options = ({"metadata": {"some": "frontend_stuff"}, "entity_id": "light.kitchen"},) for value in options: with pytest.raises(vol.MultipleInvalid): cv.SERVICE_SCHEMA(value) options = ({"metadata": 1, "entity_id": "light.kitchen"},) for value in options: validated = schema(value) assert "metadata" in validated
def test_not_mutate_input(self): """Test for immutable input.""" config = cv.SERVICE_SCHEMA( { "service": "test_domain.test_service", "entity_id": "hello.world, sensor.beer", "data": {"hello": 1}, "data_template": {"nested": {"value": "{{ 1 + 1 }}"}}, } ) orig = deepcopy(config) # Only change after call is each template getting hass attached template.attach(self.hass, orig) service.call_from_config(self.hass, config, validate_config=False) assert orig == config
def async_prepare_call_from_config(hass, config, variables=None, validate_config=False): """Prepare to call a service based on a config hash.""" if validate_config: try: config = cv.SERVICE_SCHEMA(config) except vol.Invalid as ex: raise HomeAssistantError( f"Invalid config for calling service: {ex}") from ex if CONF_SERVICE in config: domain_service = config[CONF_SERVICE] else: try: config[CONF_SERVICE_TEMPLATE].hass = hass domain_service = config[CONF_SERVICE_TEMPLATE].async_render( variables) domain_service = cv.service(domain_service) except TemplateError as ex: raise HomeAssistantError( f"Error rendering service name template: {ex}") from ex except vol.Invalid as ex: raise HomeAssistantError( f"Template rendered invalid service: {domain_service}") from ex domain, service = domain_service.split(".", 1) service_data = dict(config.get(CONF_SERVICE_DATA, {})) if CONF_SERVICE_DATA_TEMPLATE in config: try: template.attach(hass, config[CONF_SERVICE_DATA_TEMPLATE]) service_data.update( template.render_complex(config[CONF_SERVICE_DATA_TEMPLATE], variables)) except TemplateError as ex: raise HomeAssistantError( f"Error rendering data template: {ex}") from ex if CONF_SERVICE_ENTITY_ID in config: service_data[ATTR_ENTITY_ID] = config[CONF_SERVICE_ENTITY_ID] return domain, service, service_data
def test_not_mutate_input(self): """Test for immutable input.""" config = cv.SERVICE_SCHEMA({ 'service': 'test_domain.test_service', 'entity_id': 'hello.world, sensor.beer', 'data': { 'hello': 1, }, 'data_template': { 'nested': { 'value': '{{ 1 + 1 }}' } } }) orig = deepcopy(config) # Only change after call is each template getting hass attached template.attach(self.hass, orig) service.call_from_config(self.hass, config, validate_config=False) assert orig == config
def call_from_config(hass, config, blocking=False, variables=None, validate_config=True): """Call a service based on a config hash.""" if validate_config: try: config = cv.SERVICE_SCHEMA(config) except vol.Invalid as ex: _LOGGER.error("Invalid config for calling service: %s", ex) return if CONF_SERVICE in config: domain_service = config[CONF_SERVICE] else: try: domain_service = template.render(hass, config[CONF_SERVICE_TEMPLATE], variables) domain_service = cv.service(domain_service) except TemplateError as ex: _LOGGER.error('Error rendering service name template: %s', ex) return except vol.Invalid as ex: _LOGGER.error('Template rendered invalid service: %s', domain_service) return domain, service_name = domain_service.split('.', 1) service_data = dict(config.get(CONF_SERVICE_DATA, {})) if CONF_SERVICE_DATA_TEMPLATE in config: for key, value in config[CONF_SERVICE_DATA_TEMPLATE].items(): service_data[key] = template.render(hass, value, variables) if CONF_SERVICE_ENTITY_ID in config: service_data[ATTR_ENTITY_ID] = config[CONF_SERVICE_ENTITY_ID] hass.services.call(domain, service_name, service_data, blocking)
def async_prepare_call_from_config( hass: HomeAssistantType, config: ConfigType, variables: TemplateVarsType = None, validate_config: bool = False, ) -> ServiceParams: """Prepare to call a service based on a config hash.""" if validate_config: try: config = cv.SERVICE_SCHEMA(config) except vol.Invalid as ex: raise HomeAssistantError( f"Invalid config for calling service: {ex}") from ex if CONF_SERVICE in config: domain_service = config[CONF_SERVICE] else: domain_service = config[CONF_SERVICE_TEMPLATE] if isinstance(domain_service, template.Template): try: domain_service.hass = hass domain_service = domain_service.async_render(variables) domain_service = cv.service(domain_service) except TemplateError as ex: raise HomeAssistantError( f"Error rendering service name template: {ex}") from ex except vol.Invalid as ex: raise HomeAssistantError( f"Template rendered invalid service: {domain_service}") from ex domain, service = domain_service.split(".", 1) target = {} if CONF_TARGET in config: conf = config.get(CONF_TARGET) try: template.attach(hass, conf) target.update(template.render_complex(conf, variables)) if CONF_ENTITY_ID in target: target[CONF_ENTITY_ID] = cv.comp_entity_ids( target[CONF_ENTITY_ID]) except TemplateError as ex: raise HomeAssistantError( f"Error rendering service target template: {ex}") from ex except vol.Invalid as ex: raise HomeAssistantError( f"Template rendered invalid entity IDs: {target[CONF_ENTITY_ID]}" ) from ex service_data = {} for conf in [CONF_SERVICE_DATA, CONF_SERVICE_DATA_TEMPLATE]: if conf not in config: continue try: template.attach(hass, config[conf]) service_data.update( template.render_complex(config[conf], variables)) except TemplateError as ex: raise HomeAssistantError( f"Error rendering data template: {ex}") from ex if CONF_SERVICE_ENTITY_ID in config: if target: target[ATTR_ENTITY_ID] = config[CONF_SERVICE_ENTITY_ID] else: target = {ATTR_ENTITY_ID: config[CONF_SERVICE_ENTITY_ID]} return { "domain": domain, "service": service, "service_data": service_data, "target": target, }