From 9a74390143724f834ad20b64c37ca852225266b6 Mon Sep 17 00:00:00 2001 From: Petro31 <35082313+Petro31@users.noreply.github.com> Date: Thu, 22 May 2025 11:33:57 -0400 Subject: [PATCH] Add AbstractTemplateLock to prepare for trigger based template locks (#144978) * Add AbstractTemplateLock * update after rebase --- homeassistant/components/template/lock.py | 133 +++++++++++++--------- 1 file changed, 78 insertions(+), 55 deletions(-) diff --git a/homeassistant/components/template/lock.py b/homeassistant/components/template/lock.py index c858325e0ea..25eac8c35e4 100644 --- a/homeassistant/components/template/lock.py +++ b/homeassistant/components/template/lock.py @@ -2,6 +2,7 @@ from __future__ import annotations +from collections.abc import Generator, Sequence from typing import TYPE_CHECKING, Any import voluptuous as vol @@ -27,6 +28,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from .const import CONF_PICTURE, DOMAIN +from .entity import AbstractTemplateEntity from .template_entity import ( LEGACY_FIELDS as TEMPLATE_ENTITY_LEGACY_FIELDS, TEMPLATE_ENTITY_AVAILABILITY_SCHEMA, @@ -134,42 +136,33 @@ async def async_setup_platform( ) -class TemplateLock(TemplateEntity, LockEntity): - """Representation of a template lock.""" +class AbstractTemplateLock(AbstractTemplateEntity, LockEntity): + """Representation of a template lock features.""" - _attr_should_poll = False + # 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 + """Initialize the features.""" - def __init__( - self, - hass: HomeAssistant, - config: dict[str, Any], - unique_id: str | None, - ) -> None: - """Initialize the lock.""" - super().__init__( - hass, config=config, fallback_name=DEFAULT_NAME, unique_id=unique_id - ) self._state: LockState | None = None - name = self._attr_name - if TYPE_CHECKING: - assert name is not None - self._state_template = config.get(CONF_STATE) - for action_id, supported_feature in ( - (CONF_LOCK, 0), - (CONF_UNLOCK, 0), - (CONF_OPEN, LockEntityFeature.OPEN), - ): - # Scripts can be an empty list, therefore we need to check for None - if (action_config := config.get(action_id)) is not None: - self.add_script(action_id, action_config, name, DOMAIN) - self._attr_supported_features |= supported_feature self._code_format_template = config.get(CONF_CODE_FORMAT) self._code_format: str | None = None self._code_format_template_error: TemplateError | None = None self._optimistic = config.get(CONF_OPTIMISTIC) self._attr_assumed_state = bool(self._optimistic) + def _register_scripts( + self, config: dict[str, Any] + ) -> Generator[tuple[str, Sequence[dict[str, Any]], LockEntityFeature | int]]: + for action_id, supported_feature in ( + (CONF_LOCK, 0), + (CONF_UNLOCK, 0), + (CONF_OPEN, LockEntityFeature.OPEN), + ): + if (action_config := config.get(action_id)) is not None: + yield (action_id, action_config, supported_feature) + @property def is_locked(self) -> bool: """Return true if lock is locked.""" @@ -195,14 +188,12 @@ class TemplateLock(TemplateEntity, LockEntity): """Return true if lock is open.""" return self._state == LockState.OPEN - @callback - def _update_state(self, result: str | TemplateError) -> None: - """Update the state from the template.""" - super()._update_state(result) - if isinstance(result, TemplateError): - self._state = None - return + @property + def code_format(self) -> str | None: + """Regex for code format or None if no code is required.""" + return self._code_format + def _handle_state(self, result: Any) -> None: if isinstance(result, bool): self._state = LockState.LOCKED if result else LockState.UNLOCKED return @@ -229,28 +220,6 @@ class TemplateLock(TemplateEntity, LockEntity): self._state = None - @property - def code_format(self) -> str | None: - """Regex for code format or None if no code is required.""" - return self._code_format - - @callback - def _async_setup_templates(self) -> None: - """Set up templates.""" - if TYPE_CHECKING: - assert self._state_template is not None - self.add_template_attribute( - "_state", self._state_template, None, self._update_state - ) - if self._code_format_template: - self.add_template_attribute( - "_code_format_template", - self._code_format_template, - None, - self._update_code_format, - ) - super()._async_setup_templates() - @callback def _update_code_format(self, render: str | TemplateError | None): """Update code format from the template.""" @@ -330,3 +299,57 @@ class TemplateLock(TemplateEntity, LockEntity): "cause": str(self._code_format_template_error), }, ) + + +class TemplateLock(TemplateEntity, AbstractTemplateLock): + """Representation of a template lock.""" + + _attr_should_poll = False + + def __init__( + self, + hass: HomeAssistant, + config: dict[str, Any], + unique_id: str | None, + ) -> None: + """Initialize the lock.""" + TemplateEntity.__init__( + self, hass, config=config, fallback_name=DEFAULT_NAME, unique_id=unique_id + ) + AbstractTemplateLock.__init__(self, config) + name = self._attr_name + if TYPE_CHECKING: + assert name is not None + + for action_id, action_config, supported_feature in self._register_scripts( + config + ): + self.add_script(action_id, action_config, name, DOMAIN) + self._attr_supported_features |= supported_feature + + @callback + def _update_state(self, result: str | TemplateError) -> None: + """Update the state from the template.""" + super()._update_state(result) + if isinstance(result, TemplateError): + self._state = None + return + + self._handle_state(result) + + @callback + def _async_setup_templates(self) -> None: + """Set up templates.""" + if TYPE_CHECKING: + assert self._state_template is not None + self.add_template_attribute( + "_state", self._state_template, None, self._update_state + ) + if self._code_format_template: + self.add_template_attribute( + "_code_format_template", + self._code_format_template, + None, + self._update_code_format, + ) + super()._async_setup_templates()