From 24e8208deb23a2059eaed34ec68be1fbff109f53 Mon Sep 17 00:00:00 2001 From: Petro31 <35082313+Petro31@users.noreply.github.com> Date: Sun, 8 Feb 2026 16:27:49 -0500 Subject: [PATCH] Update template select platform to new template entity framework (#162543) --- homeassistant/components/template/select.py | 85 +++++-------------- .../template/snapshots/test_select.ambr | 2 +- tests/components/template/test_select.py | 6 -- 3 files changed, 22 insertions(+), 71 deletions(-) diff --git a/homeassistant/components/template/select.py b/homeassistant/components/template/select.py index b22d5c52f011..a023ee26fe62 100644 --- a/homeassistant/components/template/select.py +++ b/homeassistant/components/template/select.py @@ -24,7 +24,7 @@ from homeassistant.helpers.entity_platform import ( ) from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from . import TriggerUpdateCoordinator +from . import TriggerUpdateCoordinator, validators as template_validators from .const import DOMAIN from .entity import AbstractTemplateEntity from .helpers import ( @@ -50,7 +50,7 @@ DEFAULT_NAME = "Template Select" SELECT_COMMON_SCHEMA = vol.Schema( { vol.Required(ATTR_OPTIONS): cv.template, - vol.Required(CONF_SELECT_OPTION): cv.SCRIPT_SCHEMA, + vol.Optional(CONF_SELECT_OPTION): cv.SCRIPT_SCHEMA, vol.Optional(CONF_STATE): cv.template, } ) @@ -115,13 +115,26 @@ class AbstractTemplateSelect(AbstractTemplateEntity, SelectEntity): # The super init is not called because TemplateEntity and TriggerEntity will call AbstractTemplateEntity.__init__. # This ensures that the __init__ on AbstractTemplateEntity is not called twice. - def __init__(self, config: dict[str, Any]) -> None: # pylint: disable=super-init-not-called + def __init__(self, name: str, config: dict[str, Any]) -> None: # pylint: disable=super-init-not-called """Initialize the features.""" - self._options_template = config[ATTR_OPTIONS] - self._attr_options = [] + + self.setup_state_template( + CONF_STATE, + "_attr_current_option", + cv.string, + ) + self.setup_template( + CONF_OPTIONS, + "_attr_options", + template_validators.list_of_strings(self, CONF_OPTIONS), + ) + self._attr_current_option = None + if (select_option := config.get(CONF_SELECT_OPTION)) is not None: + self.add_script(CONF_SELECT_OPTION, select_option, name, DOMAIN) + async def async_select_option(self, option: str) -> None: """Change the selected option.""" if self._attr_assumed_state: @@ -148,32 +161,10 @@ class TemplateSelect(TemplateEntity, AbstractTemplateSelect): ) -> None: """Initialize the select.""" TemplateEntity.__init__(self, hass, config, unique_id) - AbstractTemplateSelect.__init__(self, config) - name = self._attr_name if TYPE_CHECKING: assert name is not None - - if (select_option := config.get(CONF_SELECT_OPTION)) is not None: - self.add_script(CONF_SELECT_OPTION, select_option, name, DOMAIN) - - @callback - def _async_setup_templates(self) -> None: - """Set up templates.""" - if self._template is not None: - self.add_template_attribute( - "_attr_current_option", - self._template, - validator=cv.string, - none_on_template_error=True, - ) - self.add_template_attribute( - "_attr_options", - self._options_template, - validator=vol.All(cv.ensure_list, [cv.string]), - none_on_template_error=True, - ) - super()._async_setup_templates() + AbstractTemplateSelect.__init__(self, name, config) class TriggerSelectEntity(TriggerEntity, AbstractTemplateSelect): @@ -190,39 +181,5 @@ class TriggerSelectEntity(TriggerEntity, AbstractTemplateSelect): ) -> None: """Initialize the entity.""" TriggerEntity.__init__(self, hass, coordinator, config) - AbstractTemplateSelect.__init__(self, config) - - if CONF_STATE in config: - self._to_render_simple.append(CONF_STATE) - - # Scripts can be an empty list, therefore we need to check for None - if (select_option := config.get(CONF_SELECT_OPTION)) is not None: - self.add_script( - CONF_SELECT_OPTION, - select_option, - self._rendered.get(CONF_NAME, DEFAULT_NAME), - DOMAIN, - ) - - def _handle_coordinator_update(self): - """Handle updated data from the coordinator.""" - self._process_data() - - if not self.available: - return - - write_ha_state = False - if (options := self._rendered.get(ATTR_OPTIONS)) is not None: - self._attr_options = vol.All(cv.ensure_list, [cv.string])(options) - write_ha_state = True - - if (state := self._rendered.get(CONF_STATE)) is not None: - self._attr_current_option = cv.string(state) - write_ha_state = True - - if len(self._rendered) > 0: - # In case any non optimistic template - write_ha_state = True - - if write_ha_state: - self.async_write_ha_state() + name = self._rendered.get(CONF_NAME, DEFAULT_NAME) + AbstractTemplateSelect.__init__(self, name, config) diff --git a/tests/components/template/snapshots/test_select.ambr b/tests/components/template/snapshots/test_select.ambr index e2142394cba6..addaeea5083e 100644 --- a/tests/components/template/snapshots/test_select.ambr +++ b/tests/components/template/snapshots/test_select.ambr @@ -3,7 +3,7 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'My template', - 'options': Wrapper([ + 'options': list([ 'off', 'on', 'auto', diff --git a/tests/components/template/test_select.py b/tests/components/template/test_select.py index 0726c81e77da..e6ac5919eecf 100644 --- a/tests/components/template/test_select.py +++ b/tests/components/template/test_select.py @@ -91,7 +91,6 @@ async def test_setup_config_entry( "template_type": "select", "state": "{{ 'on' }}", "options": "{{ ['off', 'on', 'auto'] }}", - "select_option": [], }, title="My template", ) @@ -111,7 +110,6 @@ async def test_setup_config_entry( [ { "state": "{{ 'a' }}", - "select_option": {"service": "script.select_option"}, "options": "{{ ['a', 'b'] }}", }, ], @@ -166,10 +164,6 @@ async def test_multiple_configs(hass: HomeAssistant) -> None: "state": "{{ 'a' }}", "select_option": {"service": "script.select_option"}, }, - { - "state": "{{ 'a' }}", - "options": "{{ ['a', 'b'] }}", - }, ], ) @pytest.mark.parametrize(