Teach TemplateEntity entity name (#62175)

* Teach TemplateEntity entity name

* Remove default name from number and select config schemas

* Re-add default name to number and select config schemas

* Set name to None if name template fails to render

* Update template button
This commit is contained in:
Erik Montnemery 2022-01-24 15:44:05 +01:00 committed by GitHub
parent 7b93226c6c
commit 321f54494e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 77 additions and 173 deletions

View File

@ -41,7 +41,7 @@ from homeassistant.helpers.script import Script
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from .const import DOMAIN
from .template_entity import TemplateEntity
from .template_entity import TemplateEntity, rewrite_common_legacy_to_modern_conf
_LOGGER = logging.getLogger(__name__)
_VALID_STATES = [
@ -102,6 +102,7 @@ async def _async_create_entities(hass, config):
alarm_control_panels = []
for object_id, entity_config in config[CONF_ALARM_CONTROL_PANELS].items():
entity_config = rewrite_common_legacy_to_modern_conf(entity_config)
unique_id = entity_config.get(CONF_UNIQUE_ID)
alarm_control_panels.append(
@ -137,11 +138,11 @@ class AlarmControlPanelTemplate(TemplateEntity, AlarmControlPanelEntity):
unique_id,
):
"""Initialize the panel."""
super().__init__(config=config)
super().__init__(hass, config=config, fallback_name=object_id)
self.entity_id = async_generate_entity_id(
ENTITY_ID_FORMAT, object_id, hass=hass
)
self._name = name = config.get(CONF_NAME, object_id)
name = self._attr_name
self._template = config.get(CONF_VALUE_TEMPLATE)
self._disarm_script = None
self._code_arm_required = config[CONF_CODE_ARM_REQUIRED]
@ -161,11 +162,6 @@ class AlarmControlPanelTemplate(TemplateEntity, AlarmControlPanelEntity):
self._state = None
self._unique_id = unique_id
@property
def name(self):
"""Return the display name of this alarm control panel."""
return self._name
@property
def unique_id(self):
"""Return the unique id of this alarm control panel."""

View File

@ -196,25 +196,12 @@ class BinarySensorTemplate(TemplateEntity, BinarySensorEntity):
unique_id: str | None,
) -> None:
"""Initialize the Template binary sensor."""
super().__init__(config=config)
super().__init__(hass, config=config)
if (object_id := config.get(CONF_OBJECT_ID)) is not None:
self.entity_id = async_generate_entity_id(
ENTITY_ID_FORMAT, object_id, hass=hass
)
self._name: str | None = None
self._friendly_name_template = config.get(CONF_NAME)
# Try to render the name as it can influence the entity ID
if self._friendly_name_template:
self._friendly_name_template.hass = hass
try:
self._name = self._friendly_name_template.async_render(
parse_result=False
)
except template.TemplateError:
pass
self._device_class = config.get(CONF_DEVICE_CLASS)
self._template = config[CONF_STATE]
self._state = None
@ -228,11 +215,6 @@ class BinarySensorTemplate(TemplateEntity, BinarySensorEntity):
async def async_added_to_hass(self):
"""Register callbacks."""
self.add_template_attribute("_state", self._template, None, self._update_state)
if (
self._friendly_name_template is not None
and not self._friendly_name_template.is_static
):
self.add_template_attribute("_name", self._friendly_name_template)
if self._delay_on_raw is not None:
try:
@ -288,11 +270,6 @@ class BinarySensorTemplate(TemplateEntity, BinarySensorEntity):
# state with delay. Cancelled if template result changes.
self._delay_cancel = async_call_later(self.hass, delay, _set_state)
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def unique_id(self):
"""Return the unique id of this binary sensor."""

View File

@ -1,27 +1,25 @@
"""Support for buttons which integrates with other components."""
from __future__ import annotations
import contextlib
import logging
from typing import Any
import voluptuous as vol
from homeassistant.components.button import (
DEVICE_CLASSES_SCHEMA,
ButtonDeviceClass,
ButtonEntity,
)
from homeassistant.const import CONF_DEVICE_CLASS, CONF_ICON, CONF_NAME, CONF_UNIQUE_ID
from homeassistant.components.button import DEVICE_CLASSES_SCHEMA, ButtonEntity
from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME, CONF_UNIQUE_ID
from homeassistant.core import Config, HomeAssistant
from homeassistant.exceptions import PlatformNotReady
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.script import Script
from homeassistant.helpers.template import Template, TemplateError
from .const import CONF_AVAILABILITY, DOMAIN
from .template_entity import TemplateEntity
from .const import DOMAIN
from .template_entity import (
TEMPLATE_ENTITY_AVAILABILITY_SCHEMA,
TEMPLATE_ENTITY_ICON_SCHEMA,
TemplateEntity,
)
_LOGGER = logging.getLogger(__name__)
@ -30,15 +28,17 @@ CONF_PRESS = "press"
DEFAULT_NAME = "Template Button"
DEFAULT_OPTIMISTIC = False
BUTTON_SCHEMA = vol.Schema(
{
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.template,
vol.Required(CONF_PRESS): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_AVAILABILITY): cv.template,
vol.Optional(CONF_UNIQUE_ID): cv.string,
vol.Optional(CONF_ICON): cv.template,
}
BUTTON_SCHEMA = (
vol.Schema(
{
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.template,
vol.Required(CONF_PRESS): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_UNIQUE_ID): cv.string,
}
)
.extend(TEMPLATE_ENTITY_AVAILABILITY_SCHEMA.schema)
.extend(TEMPLATE_ENTITY_ICON_SCHEMA.schema)
)
@ -51,17 +51,7 @@ async def _async_create_entities(
unique_id = definition.get(CONF_UNIQUE_ID)
if unique_id and unique_id_prefix:
unique_id = f"{unique_id_prefix}-{unique_id}"
entities.append(
TemplateButtonEntity(
hass,
definition[CONF_NAME],
definition.get(CONF_AVAILABILITY),
definition[CONF_PRESS],
definition.get(CONF_DEVICE_CLASS),
unique_id,
definition.get(CONF_ICON),
)
)
entities.append(TemplateButtonEntity(hass, definition, unique_id))
return entities
@ -90,33 +80,16 @@ class TemplateButtonEntity(TemplateEntity, ButtonEntity):
def __init__(
self,
hass: HomeAssistant,
name_template: Template,
availability_template: Template | None,
command_press: dict[str, Any],
device_class: ButtonDeviceClass | None,
config,
unique_id: str | None,
icon_template: Template | None,
) -> None:
"""Initialize the button."""
super().__init__(
availability_template=availability_template, icon_template=icon_template
)
self._attr_name = DEFAULT_NAME
self._name_template = name_template
name_template.hass = hass
with contextlib.suppress(TemplateError):
self._attr_name = name_template.async_render(parse_result=False)
self._command_press = Script(hass, command_press, self._attr_name, DOMAIN)
self._attr_device_class = device_class
super().__init__(hass, config=config)
self._command_press = Script(hass, config[CONF_PRESS], self._attr_name, DOMAIN)
self._attr_device_class = config.get(CONF_DEVICE_CLASS)
self._attr_unique_id = unique_id
self._attr_state = None
async def async_added_to_hass(self) -> None:
"""Register callbacks."""
if self._name_template and not self._name_template.is_static:
self.add_template_attribute("_attr_name", self._name_template, cv.string)
await super().async_added_to_hass()
async def async_press(self) -> None:
"""Press the button."""
await self._command_press.async_run(context=self._context)

View File

@ -148,11 +148,11 @@ class CoverTemplate(TemplateEntity, CoverEntity):
unique_id,
):
"""Initialize the Template cover."""
super().__init__(config=config)
super().__init__(hass, config=config, fallback_name=object_id)
self.entity_id = async_generate_entity_id(
ENTITY_ID_FORMAT, object_id, hass=hass
)
self._name = friendly_name = config.get(CONF_FRIENDLY_NAME, object_id)
friendly_name = self._attr_name
self._template = config.get(CONF_VALUE_TEMPLATE)
self._position_template = config.get(CONF_POSITION_TEMPLATE)
self._tilt_template = config.get(CONF_TILT_TEMPLATE)
@ -271,11 +271,6 @@ class CoverTemplate(TemplateEntity, CoverEntity):
else:
self._tilt_value = state
@property
def name(self):
"""Return the name of the cover."""
return self._name
@property
def unique_id(self):
"""Return the unique id of this cover."""

View File

@ -138,12 +138,12 @@ class TemplateFan(TemplateEntity, FanEntity):
unique_id,
):
"""Initialize the fan."""
super().__init__(config=config)
super().__init__(hass, config=config, fallback_name=object_id)
self.hass = hass
self.entity_id = async_generate_entity_id(
ENTITY_ID_FORMAT, object_id, hass=hass
)
self._name = friendly_name = config.get(CONF_FRIENDLY_NAME, object_id)
friendly_name = self._attr_name
self._template = config[CONF_VALUE_TEMPLATE]
self._percentage_template = config.get(CONF_PERCENTAGE_TEMPLATE)
@ -208,11 +208,6 @@ class TemplateFan(TemplateEntity, FanEntity):
self._unique_id = unique_id
@property
def name(self):
"""Return the display name of this fan."""
return self._name
@property
def unique_id(self):
"""Return the unique id of this fan."""

View File

@ -140,11 +140,11 @@ class LightTemplate(TemplateEntity, LightEntity):
unique_id,
):
"""Initialize the light."""
super().__init__(config=config)
super().__init__(hass, config=config, fallback_name=object_id)
self.entity_id = async_generate_entity_id(
ENTITY_ID_FORMAT, object_id, hass=hass
)
self._name = friendly_name = config.get(CONF_FRIENDLY_NAME, object_id)
friendly_name = self._attr_name
self._template = config.get(CONF_VALUE_TEMPLATE)
self._on_script = Script(hass, config[CONF_ON_ACTION], friendly_name, DOMAIN)
self._off_script = Script(hass, config[CONF_OFF_ACTION], friendly_name, DOMAIN)
@ -235,11 +235,6 @@ class LightTemplate(TemplateEntity, LightEntity):
"""Return the effect list."""
return self._effect_list
@property
def name(self):
"""Return the display name of this light."""
return self._name
@property
def unique_id(self):
"""Return the unique id of this light."""

View File

@ -41,7 +41,7 @@ DEFAULT_OPTIMISTIC = False
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_NAME): cv.string,
vol.Required(CONF_LOCK): cv.SCRIPT_SCHEMA,
vol.Required(CONF_UNLOCK): cv.SCRIPT_SCHEMA,
vol.Required(CONF_VALUE_TEMPLATE): cv.template,
@ -77,9 +77,9 @@ class TemplateLock(TemplateEntity, LockEntity):
unique_id,
):
"""Initialize the lock."""
super().__init__(config=config)
super().__init__(hass, config=config, fallback_name=DEFAULT_NAME)
self._state = None
self._name = name = config.get(CONF_NAME)
name = self._attr_name
self._state_template = config.get(CONF_VALUE_TEMPLATE)
self._command_lock = Script(hass, config[CONF_LOCK], name, DOMAIN)
self._command_unlock = Script(hass, config[CONF_UNLOCK], name, DOMAIN)
@ -91,11 +91,6 @@ class TemplateLock(TemplateEntity, LockEntity):
"""Return true if we do optimistic updates."""
return self._optimistic
@property
def name(self):
"""Return the name of the lock."""
return self._name
@property
def unique_id(self):
"""Return the unique id of this lock."""

View File

@ -1,7 +1,6 @@
"""Support for numbers which integrates with other components."""
from __future__ import annotations
import contextlib
import logging
from typing import Any
@ -22,7 +21,6 @@ from homeassistant.core import Config, HomeAssistant
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.script import Script
from homeassistant.helpers.template import TemplateError
from . import TriggerUpdateCoordinator
from .const import DOMAIN
@ -108,12 +106,7 @@ class TemplateNumber(TemplateEntity, NumberEntity):
unique_id: str | None,
) -> None:
"""Initialize the number."""
super().__init__(config=config)
self._attr_name = DEFAULT_NAME
self._name_template = name_template = config[CONF_NAME]
name_template.hass = hass
with contextlib.suppress(TemplateError):
self._attr_name = name_template.async_render(parse_result=False)
super().__init__(hass, config=config)
self._value_template = config[CONF_STATE]
self._command_set_value = Script(
hass, config[CONF_SET_VALUE], self._attr_name, DOMAIN
@ -130,8 +123,6 @@ class TemplateNumber(TemplateEntity, NumberEntity):
async def async_added_to_hass(self) -> None:
"""Register callbacks."""
if self._name_template and not self._name_template.is_static:
self.add_template_attribute("_attr_name", self._name_template, cv.string)
self.add_template_attribute(
"_attr_value",
self._value_template,

View File

@ -1,7 +1,6 @@
"""Support for selects which integrates with other components."""
from __future__ import annotations
import contextlib
import logging
from typing import Any
@ -18,7 +17,6 @@ from homeassistant.core import Config, HomeAssistant
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.script import Script
from homeassistant.helpers.template import TemplateError
from . import TriggerUpdateCoordinator
from .const import DOMAIN
@ -102,13 +100,7 @@ class TemplateSelect(TemplateEntity, SelectEntity):
unique_id: str | None,
) -> None:
"""Initialize the select."""
super().__init__(config=config)
self._attr_name = DEFAULT_NAME
name_template = config[CONF_NAME]
name_template.hass = hass
with contextlib.suppress(TemplateError):
self._attr_name = name_template.async_render(parse_result=False)
self._name_template = name_template
super().__init__(hass, config=config)
self._value_template = config[CONF_STATE]
self._command_select_option = Script(
hass, config[CONF_SELECT_OPTION], self._attr_name, DOMAIN
@ -133,8 +125,6 @@ class TemplateSelect(TemplateEntity, SelectEntity):
validator=vol.All(cv.ensure_list, [cv.string]),
none_on_template_error=True,
)
if self._name_template and not self._name_template.is_static:
self.add_template_attribute("_attr_name", self._name_template, cv.string)
await super().async_added_to_hass()
async def async_select_option(self, option: str) -> None:

View File

@ -199,25 +199,12 @@ class SensorTemplate(TemplateEntity, SensorEntity):
unique_id: str | None,
) -> None:
"""Initialize the sensor."""
super().__init__(config=config)
super().__init__(hass, config=config)
if (object_id := config.get(CONF_OBJECT_ID)) is not None:
self.entity_id = async_generate_entity_id(
ENTITY_ID_FORMAT, object_id, hass=hass
)
self._friendly_name_template = config.get(CONF_NAME)
self._attr_name = None
# Try to render the name as it can influence the entity ID
if self._friendly_name_template:
self._friendly_name_template.hass = hass
try:
self._attr_name = self._friendly_name_template.async_render(
parse_result=False
)
except template.TemplateError:
pass
self._attr_native_unit_of_measurement = config.get(CONF_UNIT_OF_MEASUREMENT)
self._template = config.get(CONF_STATE)
self._attr_device_class = config.get(CONF_DEVICE_CLASS)
@ -229,8 +216,6 @@ class SensorTemplate(TemplateEntity, SensorEntity):
self.add_template_attribute(
"_attr_native_value", self._template, None, self._update_state
)
if self._friendly_name_template and not self._friendly_name_template.is_static:
self.add_template_attribute("_attr_name", self._friendly_name_template)
await super().async_added_to_hass()

View File

@ -98,11 +98,11 @@ class SwitchTemplate(TemplateEntity, SwitchEntity, RestoreEntity):
unique_id,
):
"""Initialize the Template switch."""
super().__init__(config=config)
super().__init__(hass, config=config, fallback_name=object_id)
self.entity_id = async_generate_entity_id(
ENTITY_ID_FORMAT, object_id, hass=hass
)
self._name = friendly_name = config.get(ATTR_FRIENDLY_NAME, object_id)
friendly_name = self._attr_name
self._template = config.get(CONF_VALUE_TEMPLATE)
self._on_script = Script(hass, config[ON_ACTION], friendly_name, DOMAIN)
self._off_script = Script(hass, config[OFF_ACTION], friendly_name, DOMAIN)
@ -143,11 +143,6 @@ class SwitchTemplate(TemplateEntity, SwitchEntity, RestoreEntity):
await super().async_added_to_hass()
@property
def name(self):
"""Return the name of the switch."""
return self._name
@property
def unique_id(self):
"""Return the unique id of this switch."""

View File

@ -2,6 +2,7 @@
from __future__ import annotations
from collections.abc import Callable
import contextlib
import itertools
import logging
from typing import Any
@ -11,8 +12,10 @@ import voluptuous as vol
from homeassistant.const import (
ATTR_ENTITY_ID,
CONF_ENTITY_PICTURE_TEMPLATE,
CONF_FRIENDLY_NAME,
CONF_ICON,
CONF_ICON_TEMPLATE,
CONF_NAME,
)
from homeassistant.core import EVENT_HOMEASSISTANT_START, CoreState, callback
from homeassistant.exceptions import TemplateError
@ -85,6 +88,7 @@ LEGACY_FIELDS = {
CONF_ENTITY_PICTURE_TEMPLATE: CONF_PICTURE,
CONF_AVAILABILITY_TEMPLATE: CONF_AVAILABILITY,
CONF_ATTRIBUTE_TEMPLATES: CONF_ATTRIBUTES,
CONF_FRIENDLY_NAME: CONF_NAME,
}
@ -107,6 +111,9 @@ def rewrite_common_legacy_to_modern_conf(
val = Template(val)
entity_cfg[to_key] = val
if CONF_NAME in entity_cfg and isinstance(entity_cfg[CONF_NAME], str):
entity_cfg[CONF_NAME] = Template(entity_cfg[CONF_NAME])
return entity_cfg
@ -207,12 +214,14 @@ class TemplateEntity(Entity):
def __init__(
self,
hass,
*,
availability_template=None,
icon_template=None,
entity_picture_template=None,
attribute_templates=None,
config=None,
fallback_name=None,
):
"""Template Entity."""
self._template_attrs = {}
@ -224,11 +233,22 @@ class TemplateEntity(Entity):
self._availability_template = availability_template
self._icon_template = icon_template
self._entity_picture_template = entity_picture_template
self._friendly_name_template = None
else:
self._attribute_templates = config.get(CONF_ATTRIBUTES)
self._availability_template = config.get(CONF_AVAILABILITY)
self._icon_template = config.get(CONF_ICON)
self._entity_picture_template = config.get(CONF_PICTURE)
self._friendly_name_template = config.get(CONF_NAME)
# Try to render the name as it can influence the entity ID
self._attr_name = fallback_name
if self._friendly_name_template:
self._friendly_name_template.hass = hass
with contextlib.suppress(TemplateError):
self._attr_name = self._friendly_name_template.async_render(
parse_result=False
)
@callback
def _update_available(self, result):
@ -373,6 +393,12 @@ class TemplateEntity(Entity):
self.add_template_attribute(
"_attr_entity_picture", self._entity_picture_template
)
if (
self._friendly_name_template is not None
and not self._friendly_name_template.is_static
):
self.add_template_attribute("_attr_name", self._friendly_name_template)
if self.hass.state == CoreState.running:
await self._async_template_startup()
return

View File

@ -142,11 +142,11 @@ class TemplateVacuum(TemplateEntity, StateVacuumEntity):
unique_id,
):
"""Initialize the vacuum."""
super().__init__(config=config)
super().__init__(hass, config=config, fallback_name=object_id)
self.entity_id = async_generate_entity_id(
ENTITY_ID_FORMAT, object_id, hass=hass
)
self._name = friendly_name = config.get(CONF_FRIENDLY_NAME, object_id)
friendly_name = self._attr_name
self._template = config.get(CONF_VALUE_TEMPLATE)
self._battery_level_template = config.get(CONF_BATTERY_LEVEL_TEMPLATE)
@ -205,11 +205,6 @@ class TemplateVacuum(TemplateEntity, StateVacuumEntity):
# List of valid fan speeds
self._fan_speed_list = config[CONF_FAN_SPEED_LIST]
@property
def name(self):
"""Return the display name of this vacuum."""
return self._name
@property
def unique_id(self):
"""Return the unique id of this vacuum."""

View File

@ -30,7 +30,7 @@ from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from .template_entity import TemplateEntity
from .template_entity import TemplateEntity, rewrite_common_legacy_to_modern_conf
CONDITION_CLASSES = {
ATTR_CONDITION_CLEAR_NIGHT,
@ -88,6 +88,7 @@ async def async_setup_platform(
) -> None:
"""Set up the Template weather."""
config = rewrite_common_legacy_to_modern_conf(config)
unique_id = config.get(CONF_UNIQUE_ID)
async_add_entities(
@ -111,9 +112,9 @@ class WeatherTemplate(TemplateEntity, WeatherEntity):
unique_id,
):
"""Initialize the Template weather."""
super().__init__(config=config)
super().__init__(hass, config=config)
self._name = name = config[CONF_NAME]
name = self._attr_name
self._condition_template = config[CONF_CONDITION_TEMPLATE]
self._temperature_template = config[CONF_TEMPERATURE_TEMPLATE]
self._humidity_template = config[CONF_HUMIDITY_TEMPLATE]
@ -139,11 +140,6 @@ class WeatherTemplate(TemplateEntity, WeatherEntity):
self._visibility = None
self._forecast = []
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def condition(self):
"""Return the current condition."""

View File

@ -5,9 +5,9 @@ from homeassistant.components.template import template_entity
from homeassistant.helpers import template
async def test_template_entity_requires_hass_set():
async def test_template_entity_requires_hass_set(hass):
"""Test template entity requires hass to be set before accepting templates."""
entity = template_entity.TemplateEntity()
entity = template_entity.TemplateEntity(hass)
with pytest.raises(AssertionError):
entity.add_template_attribute("_hello", template.Template("Hello"))