def _process_data(self) -> None: """Process new data.""" try: rendered = dict(self._static_rendered) for key in self._to_render_simple: rendered[key] = self._config[key].async_render( self.coordinator.data["run_variables"], parse_result=key in self._parse_result, ) for key in self._to_render_complex: rendered[key] = template.render_complex( self._config[key], self.coordinator.data["run_variables"], ) if CONF_ATTRIBUTES in self._config: rendered[CONF_ATTRIBUTES] = template.render_complex( self._config[CONF_ATTRIBUTES], self.coordinator.data["run_variables"], ) self._rendered = rendered except TemplateError as err: logging.getLogger( f"{__package__}.{self.entity_id.split('.')[0]}").error( "Error rendering %s template for %s: %s", key, self.entity_id, err) self._rendered = self._static_rendered self.async_set_context(self.coordinator.data["context"])
async def async_update(self, log_errors=True): """Get the latest data from REST service with provided method.""" if not self._async_client: self._async_client = httpx.AsyncClient(verify=self._verify_ssl, proxies=self._proxies) rendered_headers = template.render_complex(self._headers, parse_result=False) rendered_params = template.render_complex(self._params) _LOGGER.debug("Updating from %s", self._resource) try: response = await self._async_client.request( self._method, self._resource, headers=rendered_headers, params=rendered_params, auth=self._auth, data=self._request_data, timeout=self._timeout, follow_redirects=True, ) self.data = response.text self.headers = response.headers except httpx.RequestError as ex: if log_errors: _LOGGER.warning("Error fetching data: %s failed with %s", self._resource, ex) self.last_exception = ex self.data = None self.headers = None
async def upload_file(self, timeout=None): """Upload file to Jabber server and return new URL. upload a file with Jabber XEP_0363 from a remote URL or a local file path and return a URL of that file. """ if data.get(ATTR_URL_TEMPLATE): _LOGGER.debug("Got url template: %s", data[ATTR_URL_TEMPLATE]) templ = template_helper.Template(data[ATTR_URL_TEMPLATE], hass) get_url = template_helper.render_complex(templ, None) url = await self.upload_file_from_url(get_url, timeout=timeout) elif data.get(ATTR_URL): url = await self.upload_file_from_url(data[ATTR_URL], timeout=timeout) elif data.get(ATTR_PATH_TEMPLATE): _LOGGER.debug("Got path template: %s", data[ATTR_PATH_TEMPLATE]) templ = template_helper.Template(data[ATTR_PATH_TEMPLATE], hass) get_path = template_helper.render_complex(templ, None) url = await self.upload_file_from_path(get_path, timeout=timeout) elif data.get(ATTR_PATH): url = await self.upload_file_from_path(data[ATTR_PATH], timeout=timeout) else: url = None if url is None: _LOGGER.error("No path or URL found for file") raise FileUploadError("Could not upload file") return url
async def get_device_state(self, hass): """Get the latest data from REST API and update the state.""" websession = async_get_clientsession(hass, self._verify_ssl) state_resource = self._resource if self._resource_template is not None: state_resource = self._resource_template.async_render(parse_result=False) if self._state_resource is not None: state_resource = self._state_resource if self._state_resource_template is not None: state_resource = self._state_resource_template.async_render(parse_result=False) rendered_headers = template.render_complex(self._headers, parse_result=False) rendered_params = template.render_complex(self._params) async with async_timeout.timeout(self._timeout): req = await websession.get( state_resource, auth=self._auth, headers=rendered_headers, params=rendered_params, ) text = await req.text() _LOGGER.debug("[%s] Raw response is (%s): %s", self._name, req.status, text) if self._is_on_template is not None: text = self._is_on_template.async_render_with_possible_json_value( text, "None" ) _LOGGER.debug("[%s] Value after template rendering: %s", self._name, text) text = text.lower() if text == "true": self._state = True elif text == "false": self._state = False else: self._state = None else: if text == self._body_on.template: self._state = True elif text == self._body_off.template: self._state = False else: self._state = None return req
def test_passing_vars_as_dict_element(self): """Test passing variables as list.""" self.assertEqual( 'bar', template.render_complex(template.Template('{{ hello.foo }}', self.hass), {'hello': {'foo': 'bar'}}))
def test_passing_vars_as_list_element(self): """Test passing variables as list.""" self.assertEqual( 'bar', template.render_complex( template.Template('{{ hello[1] }}', self.hass), {'hello': ['foo', 'bar']}))
def _handle_coordinator_update(self) -> None: """Handle updated data from the coordinator.""" try: rendered = dict(self._static_rendered) for key in self._to_render: rendered[key] = self._config[key].async_render( self.coordinator.data["run_variables"], parse_result=False ) if CONF_ATTRIBUTES in self._config: rendered[CONF_ATTRIBUTES] = template.render_complex( self._config[CONF_ATTRIBUTES], self.coordinator.data["run_variables"], ) self._rendered = rendered except template.TemplateError as err: logging.getLogger(f"{__package__}.{self.entity_id.split('.')[0]}").error( "Error rendering %s template for %s: %s", key, self.entity_id, err ) self._rendered = self._static_rendered self.async_set_context(self.coordinator.data["context"]) self.async_write_ha_state()
async def _async_delay(self, action, variables, context): """Handle delay.""" # Call ourselves in the future to continue work unsub = None @callback def async_script_delay(now): """Handle delay.""" with suppress(ValueError): self._async_listener.remove(unsub) self.hass.async_create_task(self.async_run(variables, context)) delay = action[CONF_DELAY] try: if isinstance(delay, template.Template): delay = vol.All(cv.time_period, cv.positive_timedelta)( delay.async_render(variables)) elif isinstance(delay, dict): delay_data = {} delay_data.update(template.render_complex(delay, variables)) delay = cv.time_period(delay_data) except (exceptions.TemplateError, vol.Invalid) as ex: _LOGGER.error("Error rendering '%s' delay template: %s", self.name, ex) raise _StopScript self.last_action = action.get(CONF_ALIAS, "delay {}".format(delay)) self._log("Executing step %s" % self.last_action) unsub = async_track_point_in_utc_time(self.hass, async_script_delay, date_util.utcnow() + delay) self._async_listener.append(unsub) raise _SuspendScript
async def _async_delay_step(self): """Handle delay.""" try: delay = vol.All(cv.time_period, cv.positive_timedelta)(template.render_complex( self._action[CONF_DELAY], self._variables)) except (exceptions.TemplateError, vol.Invalid) as ex: self._log( "Error rendering %s delay template: %s", self._script.name, ex, level=logging.ERROR, ) raise _StopScript self._script.last_action = self._action.get(CONF_ALIAS, f"delay {delay}") self._log("Executing step %s", self._script.last_action) delay = delay.total_seconds() self._changed() try: async with timeout(delay): await self._stop.wait() except asyncio.TimeoutError: pass
def async_on_service_call(service: HomeassistantServiceCall) -> None: """Call service when user automation in ESPHome config is triggered.""" domain, service_name = service.service.split(".", 1) service_data = service.data if service.data_template: try: data_template = { key: Template(value) for key, value in service.data_template.items() } template.attach(hass, data_template) service_data.update( template.render_complex(data_template, service.variables)) except TemplateError as ex: _LOGGER.error("Error rendering data template for %s: %s", host, ex) return if service.is_event: # ESPHome uses servicecall packet for both events and service calls # Ensure the user can only send events of form 'esphome.xyz' if domain != "esphome": _LOGGER.error( "Can only generate events under esphome domain! (%s)", host) return hass.bus.async_fire(service.service, service_data) else: hass.async_create_task( hass.services.async_call(domain, service_name, service_data, blocking=True))
async def set_device_state(self, body): """Send a state update to the device.""" websession = async_get_clientsession(self.hass, self._verify_ssl) rendered_headers = template.render_complex(self._headers, parse_result=False) rendered_params = template.render_complex(self._params) async with async_timeout.timeout(self._timeout): req = await getattr(websession, self._method)( self._resource, auth=self._auth, data=bytes(body, "utf-8"), headers=rendered_headers, params=rendered_params, ) return req
def test_passing_vars_as_list_element(hass): """Test passing variables as list.""" assert ( template.render_complex( template.Template("{{ hello[1] }}", hass), {"hello": ["foo", "bar"]} ) == "bar" )
def test_passing_vars_as_dict(hass): """Test passing variables as list.""" assert ( template.render_complex( template.Template("{{ hello }}", hass), {"hello": {"foo": "bar"}} ) == "{'foo': 'bar'}" )
def state_automation_listener(entity, from_s, to_s): """Listen for state changes and calls action.""" @callback def call_action(): """Call action with right context.""" hass.async_run_job(action({ 'trigger': { 'platform': 'state', 'entity_id': entity, 'from_state': from_s, 'to_state': to_s, 'for': time_delta if not time_delta else period[entity] } }, context=to_s.context)) # Ignore changes to state attributes if from/to is in use if (not match_all and from_s is not None and to_s is not None and from_s.state == to_s.state): return if not time_delta: call_action() return variables = { 'trigger': { 'platform': 'state', 'entity_id': entity, 'from_state': from_s, 'to_state': to_s, } } try: if isinstance(time_delta, template.Template): period[entity] = vol.All( cv.time_period, cv.positive_timedelta)( time_delta.async_render(variables)) elif isinstance(time_delta, dict): time_delta_data = {} time_delta_data.update( template.render_complex(time_delta, variables)) period[entity] = vol.All( cv.time_period, cv.positive_timedelta)( time_delta_data) else: period[entity] = time_delta except (exceptions.TemplateError, vol.Invalid) as ex: _LOGGER.error("Error rendering '%s' for template: %s", automation_info['name'], ex) return unsub_track_same[entity] = async_track_same_state( hass, period[entity], call_action, lambda _, _2, to_state: to_state.state == to_s.state, entity_ids=entity)
def template_listener(entity_id, from_s, to_s): """Listen for state changes and calls action.""" nonlocal unsub_track_same @callback def call_action(): """Call action with right context.""" hass.async_run_job( action( { "trigger": { "platform": "template", "entity_id": entity_id, "from_state": from_s, "to_state": to_s, "for": time_delta if not time_delta else period, } }, context=(to_s.context if to_s else None), )) if not time_delta: call_action() return variables = { "trigger": { "platform": platform_type, "entity_id": entity_id, "from_state": from_s, "to_state": to_s, } } try: if isinstance(time_delta, template.Template): period = vol.All(cv.time_period, cv.positive_timedelta)( time_delta.async_render(variables)) elif isinstance(time_delta, dict): time_delta_data = {} time_delta_data.update( template.render_complex(time_delta, variables)) period = vol.All(cv.time_period, cv.positive_timedelta)(time_delta_data) else: period = time_delta except (exceptions.TemplateError, vol.Invalid) as ex: _LOGGER.error("Error rendering '%s' for template: %s", automation_info["name"], ex) return unsub_track_same = async_track_same_state( hass, period, call_action, lambda _, _2, _3: condition.async_template(hass, value_template), value_template.extract_entities(), )
def send_message(self, message="", **kwargs): """Send a notification to the device.""" data = {**self.data, **kwargs.get(ATTR_DATA, {})} if data.get(ATTR_VALUE) is not None: templ = template_helper.Template(self.data[ATTR_VALUE], self.hass) data[ATTR_VALUE] = template_helper.render_complex(templ, None) self.hass.services.call(DOMAIN, SERVICE_SET_DEVICE_VALUE, data)
def test_passing_vars_as_dict(self): """Test passing variables as list.""" self.assertEqual( "{'foo': 'bar'}", template.render_complex( template.Template('{{ hello }}', self.hass), {'hello': { 'foo': 'bar' }}))
def state_automation_listener(event): """Listen for state changes and calls action.""" entity_id = event.data.get("entity_id") from_s = event.data.get("old_state") to_s = event.data.get("new_state") @callback def call_action(): """Call action with right context.""" hass.async_run_hass_job( job, { "trigger": { "platform": platform_type, "entity_id": entity_id, "below": below, "above": above, "from_state": from_s, "to_state": to_s, "for": time_delta if not time_delta else period[entity_id], "description": f"numeric state of {entity_id}", } }, to_s.context, ) matching = check_numeric_state(entity_id, from_s, to_s) if not matching: entities_triggered.discard(entity_id) elif entity_id not in entities_triggered: entities_triggered.add(entity_id) if time_delta: try: period[entity_id] = cv.positive_time_period( template.render_complex(time_delta, variables(entity_id))) except (exceptions.TemplateError, vol.Invalid) as ex: _LOGGER.error( "Error rendering '%s' for template: %s", automation_info["name"], ex, ) entities_triggered.discard(entity_id) return unsub_track_same[entity_id] = async_track_same_state( hass, period[entity_id], call_action, entity_ids=entity_id, async_check_same_func=check_numeric_state, ) else: call_action()
def template_listener(event, updates): """Listen for state changes and calls action.""" nonlocal delay_cancel result = updates.pop().result if delay_cancel: # pylint: disable=not-callable delay_cancel() delay_cancel = None if not result_as_boolean(result): return entity_id = event.data.get("entity_id") from_s = event.data.get("old_state") to_s = event.data.get("new_state") @callback def call_action(*_): """Call action with right context.""" hass.async_run_hass_job( job, { "trigger": { "platform": "template", "entity_id": entity_id, "from_state": from_s, "to_state": to_s, "for": time_delta if not time_delta else period, "description": f"{entity_id} via template", } }, (to_s.context if to_s else None), ) if not time_delta: call_action() return variables = { "trigger": { "platform": platform_type, "entity_id": entity_id, "from_state": from_s, "to_state": to_s, } } try: period = cv.positive_time_period( template.render_complex(time_delta, variables)) except (exceptions.TemplateError, vol.Invalid) as ex: _LOGGER.error("Error rendering '%s' for template: %s", automation_info["name"], ex) return delay_cancel = async_call_later(hass, period.seconds, call_action)
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 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 _get_pos_time_period_template(self, key): try: return cv.positive_time_period( template.render_complex(self._action[key], self._variables)) except (exceptions.TemplateError, vol.Invalid) as ex: self._log( "Error rendering %s %s template: %s", self._script.name, key, ex, level=logging.ERROR, ) raise _StopScript from ex
def async_on_service_call(service: HomeassistantServiceCall) -> None: """Call service when user automation in ESPHome config is triggered.""" domain, service_name = service.service.split(".", 1) service_data = service.data if service.data_template: try: data_template = { key: Template(value) # type: ignore[no-untyped-call] for key, value in service.data_template.items() } template.attach(hass, data_template) service_data.update( template.render_complex(data_template, service.variables)) except TemplateError as ex: _LOGGER.error("Error rendering data template for %s: %s", host, ex) return if service.is_event: # ESPHome uses servicecall packet for both events and service calls # Ensure the user can only send events of form 'esphome.xyz' if domain != "esphome": _LOGGER.error( "Can only generate events under esphome domain! (%s)", host) return # Call native tag scan if service_name == "tag_scanned" and device_id is not None: # Importing tag via hass.components in case it is overridden # in a custom_components (custom_components.tag) tag = hass.components.tag tag_id = service_data["tag_id"] hass.async_create_task(tag.async_scan_tag(tag_id, device_id)) return hass.bus.async_fire( service.service, { ATTR_DEVICE_ID: device_id, **service_data, }, ) else: hass.async_create_task( hass.services.async_call(domain, service_name, service_data, blocking=True))
def _async_fire_event(self, action, variables): """Fire an event.""" self.last_action = action.get(CONF_ALIAS, action[CONF_EVENT]) self._log("Executing step %s" % self.last_action) event_data = dict(action.get(CONF_EVENT_DATA, {})) if CONF_EVENT_DATA_TEMPLATE in action: try: event_data.update(template.render_complex( action[CONF_EVENT_DATA_TEMPLATE], variables)) except TemplateError as ex: _LOGGER.error('Error rendering event data template: %s', ex) self.hass.bus.async_fire(action[CONF_EVENT], event_data)
async def async_send_message(self, message: str, **kwargs: Any) -> None: """Send a message to Slack.""" data = kwargs.get(ATTR_DATA) or {} try: DATA_SCHEMA(data) except vol.Invalid as err: _LOGGER.error("Invalid message data: %s", err) data = {} title = kwargs.get(ATTR_TITLE) targets = _async_sanitize_channel_names( kwargs.get(ATTR_TARGET, [self._config[CONF_DEFAULT_CHANNEL]])) # Message Type 1: A text-only message if ATTR_FILE not in data: if ATTR_BLOCKS_TEMPLATE in data: value = cv.template_complex(data[ATTR_BLOCKS_TEMPLATE]) template.attach(self._hass, value) blocks = template.render_complex(value) elif ATTR_BLOCKS in data: blocks = data[ATTR_BLOCKS] else: blocks = None return await self._async_send_text_only_message( targets, message, title, username=data.get(ATTR_USERNAME, self._config.get(ATTR_USERNAME)), icon=data.get(ATTR_ICON, self._config.get(ATTR_ICON)), blocks=blocks, ) # Message Type 2: A message that uploads a remote file if ATTR_URL in data[ATTR_FILE]: return await self._async_send_remote_file_message( data[ATTR_FILE][ATTR_URL], targets, message, title, username=data[ATTR_FILE].get(ATTR_USERNAME), password=data[ATTR_FILE].get(ATTR_PASSWORD), ) # Message Type 3: A message that uploads a local file return await self._async_send_local_file_message( data[ATTR_FILE][ATTR_PATH], targets, message, title)
async def upload_file(self, timeout=None): """Upload file to Jabber server and return new URL. upload a file with Jabber XEP_0363 from a remote URL or a local file path and return a URL of that file. """ if data.get(ATTR_URL_TEMPLATE): _LOGGER.debug( "Got url template: %s", data[ATTR_URL_TEMPLATE]) templ = template_helper.Template( data[ATTR_URL_TEMPLATE], hass) get_url = template_helper.render_complex(templ, None) url = await self.upload_file_from_url( get_url, timeout=timeout) elif data.get(ATTR_URL): url = await self.upload_file_from_url( data[ATTR_URL], timeout=timeout) elif data.get(ATTR_PATH_TEMPLATE): _LOGGER.debug( "Got path template: %s", data[ATTR_PATH_TEMPLATE]) templ = template_helper.Template( data[ATTR_PATH_TEMPLATE], hass) get_path = template_helper.render_complex(templ, None) url = await self.upload_file_from_path( get_path, timeout=timeout) elif data.get(ATTR_PATH): url = await self.upload_file_from_path( data[ATTR_PATH], timeout=timeout) else: url = None if url is None: _LOGGER.error("No path or URL found for file") raise FileUploadError("Could not upload file") return url
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 async_on_service_call(service: 'ServiceCall') -> None: """Call service when user automation in ESPHome config is triggered.""" domain, service_name = service.service.split('.', 1) service_data = service.data if service.data_template: try: data_template = {key: Template(value) for key, value in service.data_template.items()} template.attach(hass, data_template) service_data.update(template.render_complex( data_template, service.variables)) except TemplateError as ex: _LOGGER.error('Error rendering data template: %s', ex) return hass.async_create_task(hass.services.async_call( domain, service_name, service_data, blocking=True))
def _prep_delay_step(self): try: delay = vol.All(cv.time_period, cv.positive_timedelta)(template.render_complex( self._action[CONF_DELAY], self._variables)) except (exceptions.TemplateError, vol.Invalid) as ex: self._raise( "Error rendering %s delay template: %s", self._script.name, ex, exception=_StopScript, ) self._script.last_action = self._action.get(CONF_ALIAS, f"delay {delay}") self._log("Executing step %s", self._script.last_action) return delay
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
async def _async_event_step(self): """Fire an event.""" self._step_log(self._action.get(CONF_ALIAS, self._action[CONF_EVENT])) event_data = {} for conf in [CONF_EVENT_DATA, CONF_EVENT_DATA_TEMPLATE]: if conf not in self._action: continue try: event_data.update( template.render_complex(self._action[conf], self._variables)) except exceptions.TemplateError as ex: self._log("Error rendering event data template: %s", ex, level=logging.ERROR) self._hass.bus.async_fire(self._action[CONF_EVENT], event_data, context=self._context)
async def _async_event_step(self): """Fire an event.""" self._script.last_action = self._action.get(CONF_ALIAS, self._action[CONF_EVENT]) self._log("Executing step %s", self._script.last_action) event_data = dict(self._action.get(CONF_EVENT_DATA, {})) if CONF_EVENT_DATA_TEMPLATE in self._action: try: event_data.update( template.render_complex( self._action[CONF_EVENT_DATA_TEMPLATE], self._variables)) except exceptions.TemplateError as ex: self._log("Error rendering event data template: %s", ex, level=logging.ERROR) self._hass.bus.async_fire(self._action[CONF_EVENT], event_data, context=self._context)
async def async_call_action_from_config(hass: HomeAssistant, config: dict, variables: dict, context: Optional[Context]) -> None: """Execute a device action.""" webhook_id = webhook_id_from_device_id(hass, config[CONF_DEVICE_ID]) if webhook_id is None: raise InvalidDeviceAutomationConfig( "Unable to resolve webhook ID from the device ID") service_name = get_notify_service(hass, webhook_id) if service_name is None: raise InvalidDeviceAutomationConfig( "Unable to find notify service for webhook ID") service_data = {notify.ATTR_TARGET: webhook_id} # Render it here because we have access to variables here. for key in (notify.ATTR_MESSAGE, notify.ATTR_TITLE, notify.ATTR_DATA): if key not in config: continue value_template = config[key] template.attach(hass, value_template) try: service_data[key] = template.render_complex( value_template, variables) except template.TemplateError as err: raise InvalidDeviceAutomationConfig( f"Error rendering {key}: {err}") from err await hass.services.async_call(notify.DOMAIN, service_name, service_data, blocking=True, context=context)
async def async_run(self, variables: Optional[Sequence] = None, context: Optional[Context] = None) -> None: """Run script. This method is a coroutine. """ self.last_triggered = date_util.utcnow() if self._cur == -1: self._log('Running script') self._cur = 0 # Unregister callback if we were in a delay or wait but turn on is # called again. In that case we just continue execution. self._async_remove_listener() for cur, action in islice(enumerate(self.sequence), self._cur, None): if CONF_DELAY in action: # Call ourselves in the future to continue work unsub = None @callback def async_script_delay(now): """Handle delay.""" # pylint: disable=cell-var-from-loop with suppress(ValueError): self._async_listener.remove(unsub) self.hass.async_create_task( self.async_run(variables, context)) delay = action[CONF_DELAY] try: if isinstance(delay, template.Template): delay = vol.All( cv.time_period, cv.positive_timedelta)( delay.async_render(variables)) elif isinstance(delay, dict): delay_data = {} delay_data.update( template.render_complex(delay, variables)) delay = cv.time_period(delay_data) except (TemplateError, vol.Invalid) as ex: _LOGGER.error("Error rendering '%s' delay template: %s", self.name, ex) break unsub = async_track_point_in_utc_time( self.hass, async_script_delay, date_util.utcnow() + delay ) self._async_listener.append(unsub) self._cur = cur + 1 if self._change_listener: self.hass.async_add_job(self._change_listener) return if CONF_WAIT_TEMPLATE in action: # Call ourselves in the future to continue work wait_template = action[CONF_WAIT_TEMPLATE] wait_template.hass = self.hass # check if condition already okay if condition.async_template( self.hass, wait_template, variables): continue @callback def async_script_wait(entity_id, from_s, to_s): """Handle script after template condition is true.""" self._async_remove_listener() self.hass.async_create_task( self.async_run(variables, context)) self._async_listener.append(async_track_template( self.hass, wait_template, async_script_wait, variables)) self._cur = cur + 1 if self._change_listener: self.hass.async_add_job(self._change_listener) if CONF_TIMEOUT in action: self._async_set_timeout( action, variables, context, action.get(CONF_CONTINUE, True)) return if CONF_CONDITION in action: if not self._async_check_condition(action, variables): break elif CONF_EVENT in action: self._async_fire_event(action, variables, context) else: await self._async_call_service(action, variables, context) self._cur = -1 self.last_action = None if self._change_listener: self.hass.async_add_job(self._change_listener)
def test_passing_vars_as_list_element(self): """Test passing variables as list.""" assert 'bar' == \ template.render_complex(template.Template('{{ hello[1] }}', self.hass), {'hello': ['foo', 'bar']})
def test_passing_vars_as_dict(self): """Test passing variables as list.""" assert "{'foo': 'bar'}" == \ template.render_complex(template.Template('{{ hello }}', self.hass), {'hello': {'foo': 'bar'}})
def test_passing_vars_as_list(self): """Test passing variables as list.""" self.assertEqual( "['foo', 'bar']", template.render_complex(template.Template('{{ hello }}', self.hass), {'hello': ['foo', 'bar']}))