1 """TemplateEntity utility class."""
3 from __future__
import annotations
5 from collections.abc
import Callable, Mapping
9 from typing
import Any, cast
11 from propcache
import under_cached_property
12 import voluptuous
as vol
16 CONF_ENTITY_PICTURE_TEMPLATE,
29 EventStateChangedData,
41 TrackTemplateResultInfo,
42 async_track_template_result,
48 TemplateStateFromEntityId,
52 TEMPLATE_ENTITY_BASE_SCHEMA,
53 make_template_entity_base_schema,
58 CONF_ATTRIBUTE_TEMPLATES,
61 CONF_AVAILABILITY_TEMPLATE,
65 _LOGGER = logging.getLogger(__name__)
67 TEMPLATE_ENTITY_AVAILABILITY_SCHEMA = vol.Schema(
69 vol.Optional(CONF_AVAILABILITY): cv.template,
73 TEMPLATE_ENTITY_ICON_SCHEMA = vol.Schema(
75 vol.Optional(CONF_ICON): cv.template,
79 TEMPLATE_ENTITY_COMMON_SCHEMA = vol.Schema(
81 vol.Optional(CONF_ATTRIBUTES): vol.Schema({cv.string: cv.template}),
82 vol.Optional(CONF_AVAILABILITY): cv.template,
83 vol.Optional(CONF_VARIABLES): cv.SCRIPT_VARIABLES_SCHEMA,
85 ).extend(TEMPLATE_ENTITY_BASE_SCHEMA.schema)
89 """Return a schema with default name."""
92 vol.Optional(CONF_ATTRIBUTES): vol.Schema({cv.string: cv.template}),
93 vol.Optional(CONF_AVAILABILITY): cv.template,
98 TEMPLATE_ENTITY_ATTRIBUTES_SCHEMA_LEGACY = vol.Schema(
100 vol.Optional(CONF_ATTRIBUTE_TEMPLATES, default={}): vol.Schema(
101 {cv.string: cv.template}
106 TEMPLATE_ENTITY_AVAILABILITY_SCHEMA_LEGACY = vol.Schema(
108 vol.Optional(CONF_AVAILABILITY_TEMPLATE): cv.template,
112 TEMPLATE_ENTITY_COMMON_SCHEMA_LEGACY = vol.Schema(
114 vol.Optional(CONF_ENTITY_PICTURE_TEMPLATE): cv.template,
115 vol.Optional(CONF_ICON_TEMPLATE): cv.template,
117 ).extend(TEMPLATE_ENTITY_AVAILABILITY_SCHEMA_LEGACY.schema)
121 CONF_ICON_TEMPLATE: CONF_ICON,
122 CONF_ENTITY_PICTURE_TEMPLATE: CONF_PICTURE,
123 CONF_AVAILABILITY_TEMPLATE: CONF_AVAILABILITY,
124 CONF_ATTRIBUTE_TEMPLATES: CONF_ATTRIBUTES,
125 CONF_FRIENDLY_NAME: CONF_NAME,
131 entity_cfg: dict[str, Any],
132 extra_legacy_fields: dict[str, str] |
None =
None,
134 """Rewrite legacy config."""
135 entity_cfg = {**entity_cfg}
136 if extra_legacy_fields
is None:
137 extra_legacy_fields = {}
139 for from_key, to_key
in itertools.chain(
140 LEGACY_FIELDS.items(), extra_legacy_fields.items()
142 if from_key
not in entity_cfg
or to_key
in entity_cfg:
145 val = entity_cfg.pop(from_key)
146 if isinstance(val, str):
148 entity_cfg[to_key] = val
150 if CONF_NAME
in entity_cfg
and isinstance(entity_cfg[CONF_NAME], str):
151 entity_cfg[CONF_NAME] =
Template(entity_cfg[CONF_NAME], hass)
157 """Attribute value linked to template result."""
164 validator: Callable[[Any], Any] |
None =
None,
165 on_update: Callable[[Any],
None] |
None =
None,
166 none_on_template_error: bool |
None =
False,
168 """Template attribute."""
179 """Config update path for the attribute."""
184 raise AttributeError(f
"Attribute '{self._attribute}' does not exist.")
190 attr_result =
None if isinstance(result, TemplateError)
else result
196 event: Event[EventStateChangedData] |
None,
198 last_result: str | TemplateError |
None,
199 result: str | TemplateError,
201 """Handle a template result event callback."""
202 if isinstance(result, TemplateError):
205 "TemplateError('%s') "
206 "while processing template '%s' "
207 "for attribute '%s' in entity '%s'"
227 validated = self.
validatorvalidator(result)
228 except vol.Invalid
as ex:
231 "Error validating template result '%s' "
232 "from template '%s' "
233 "for attribute '%s' in entity %s "
234 "validation message '%s'"
252 """Entity that uses templates to calculate attributes."""
254 _attr_available =
True
255 _attr_entity_picture =
None
262 availability_template: Template |
None =
None,
263 icon_template: Template |
None =
None,
264 entity_picture_template: Template |
None =
None,
265 attribute_templates: dict[str, Template] |
None =
None,
266 config: ConfigType |
None =
None,
267 fallback_name: str |
None =
None,
268 unique_id: str |
None =
None,
270 """Template Entity."""
271 self._template_attrs: dict[Template, list[_TemplateAttribute]] = {}
280 dict[str, Any] |
None,
281 dict[str, bool | set[str]] |
None,
302 self.
_run_variables_run_variables = config.get(CONF_VARIABLES, {})
305 class DummyState(
State):
306 """None-state for template entities not yet added to the state machine."""
309 """Initialize a new state."""
310 super().
__init__(
"unknown.unknown", STATE_UNKNOWN)
313 @under_cached_property
314 def name(self) -> str:
315 """Name of this state."""
318 variables = {
"this": DummyState()}
323 with contextlib.suppress(TemplateError):
325 variables=variables, parse_result=
False
331 with contextlib.suppress(TemplateError):
333 variables=variables, parse_result=
False
337 with contextlib.suppress(TemplateError):
339 variables=variables, parse_result=
False
356 if isinstance(result, TemplateError):
371 self, attribute_key: str, attribute_template: Template
373 """Create a template tracker for the attribute."""
375 def _update_attribute(result: str | TemplateError) ->
None:
376 attr_result =
None if isinstance(result, TemplateError)
else result
380 attribute_key, attribute_template,
None, _update_attribute
385 """Return referenced blueprint or None."""
388 return cast(str, self.
_blueprint_inputs_blueprint_inputs[CONF_USE_BLUEPRINT][CONF_PATH])
394 validator: Callable[[Any], Any] |
None =
None,
395 on_update: Callable[[Any],
None] |
None =
None,
396 none_on_template_error: bool =
False,
398 """Call in the constructor to add a template linked to a attribute.
403 The name of the attribute to link to. This attribute must exist
404 unless a custom on_update method is supplied.
406 The template to calculate.
408 Validator function to parse the result and ensure it's valid.
410 Called to store the template result rather than storing it
411 the supplied attribute. Passed the result of the validator, or None
412 if the template or validator resulted in an error.
413 none_on_template_error
414 If True, the attribute will be set to None if the template errors.
417 if self.
hasshass
is None:
418 raise ValueError(
"hass cannot be None")
419 if template.hass
is None:
420 raise ValueError(
"template.hass cannot be None")
422 self, attribute, template, validator, on_update, none_on_template_error
424 self._template_attrs.setdefault(template, [])
425 self._template_attrs[template].append(template_attribute)
430 event: Event[EventStateChangedData] |
None,
431 updates: list[TrackTemplateResult],
433 """Call back the results to the attributes."""
437 entity_id = event
and event.data[
"entity_id"]
445 for update
in updates:
448 "Template loop detected while processing event: %s, skipping"
449 " template render for Template[%s]"
452 update.template.template,
456 for update
in updates:
457 for template_attr
in self._template_attrs[update.template]:
458 template_attr.handle_result(
459 event, update.template, update.last_result, update.result
469 except Exception
as err:
474 calculated_state.state,
475 calculated_state.attributes,
483 _hass: HomeAssistant |
None,
484 log_fn: Callable[[int, str],
None] |
None =
None,
486 template_var_tups: list[TrackTemplate] = []
487 has_availability_template =
False
494 for template, attributes
in self._template_attrs.items():
496 is_availability_template =
False
497 for attribute
in attributes:
498 if attribute._attribute ==
"_attr_available":
499 has_availability_template =
True
500 is_availability_template =
True
501 attribute.async_setup()
503 if is_availability_template:
504 template_var_tups.insert(0, template_var_tup)
506 template_var_tups.append(template_var_tup)
513 has_super_template=has_availability_template,
517 result_info.async_refresh()
521 """Set up templates."""
534 "_attr_icon", self.
_icon_template_icon_template, vol.Or(cv.whitespace, cv.icon)
551 preview_callback: Callable[
554 Mapping[str, Any] |
None,
555 dict[str, bool | set[str]] |
None,
561 """Render a preview."""
563 def log_template_error(level: int, msg: str) ->
None:
564 preview_callback(
None,
None,
None, msg)
570 except Exception
as err:
571 preview_callback(
None,
None,
None,
str(err))
575 """Run when entity about to be added to hass."""
581 """Call for forced update."""
589 run_variables: _VarsType |
None =
None,
590 context: Context |
None =
None,
592 """Run an action script."""
593 if run_variables
is None:
595 await script.async_run(
None _update_state(self, str|TemplateError result)
_attr_extra_state_attributes
None _update_available(self, str|TemplateError result)
None async_run_script(self, Script script, *_VarsType|None run_variables=None, Context|None context=None)
str|None referenced_blueprint(self)
None async_added_to_hass(self)
None _async_template_startup(self, HomeAssistant|None _hass, Callable[[int, str], None]|None log_fn=None)
CALLBACK_TYPE async_start_preview(self, Callable[[str|None, Mapping[str, Any]|None, dict[str, bool|set[str]]|None, str|None,], None,] preview_callback)
dict _render_variables(self)
None _async_setup_templates(self)
None add_template_attribute(self, str attribute, Template template, Callable[[Any], Any]|None validator=None, Callable[[Any], None]|None on_update=None, bool none_on_template_error=False)
None _add_attribute_template(self, str attribute_key, Template attribute_template)
None _handle_results(self, Event[EventStateChangedData]|None event, list[TrackTemplateResult] updates)
None __init__(self, HomeAssistant hass, *Template|None availability_template=None, Template|None icon_template=None, Template|None entity_picture_template=None, dict[str, Template]|None attribute_templates=None, ConfigType|None config=None, str|None fallback_name=None, str|None unique_id=None)
None __init__(self, Entity entity, str attribute, Template template, Callable[[Any], Any]|None validator=None, Callable[[Any], None]|None on_update=None, bool|None none_on_template_error=False)
None handle_result(self, Event[EventStateChangedData]|None event, Template template, str|TemplateError|None last_result, str|TemplateError result)
None _default_update(self, str|TemplateError result)
None async_write_ha_state(self)
CalculatedState _async_calculate_state(self)
None _call_on_remove_callbacks(self)
None async_on_remove(self, CALLBACK_TYPE func)
str|UndefinedType|None name(self)
None async_set_context(self, Context context)
dict[str, Any] rewrite_common_legacy_to_modern_conf(HomeAssistant hass, dict[str, Any] entity_cfg, dict[str, str]|None extra_legacy_fields=None)
vol.Schema make_template_entity_common_schema(str default_name)
str validate_state(str state)
TrackTemplateResultInfo async_track_template_result(HomeAssistant hass, Sequence[TrackTemplate] track_templates, TrackTemplateResultListener action, bool strict=False, Callable[[int, str], None]|None log_fn=None, bool has_super_template=False)
CALLBACK_TYPE async_at_start(HomeAssistant hass, Callable[[HomeAssistant], Coroutine[Any, Any, None]|None] at_start_cb)
bool result_as_boolean(Any|None template_result)
vol.Schema make_template_entity_base_schema(str default_name)