Add AbstractTemplateLock to prepare for trigger based template locks (#144978)

* Add AbstractTemplateLock

* update after rebase
This commit is contained in:
Petro31 2025-05-22 11:33:57 -04:00 committed by GitHub
parent 64d6552890
commit 9a74390143
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -2,6 +2,7 @@
from __future__ import annotations from __future__ import annotations
from collections.abc import Generator, Sequence
from typing import TYPE_CHECKING, Any from typing import TYPE_CHECKING, Any
import voluptuous as vol import voluptuous as vol
@ -27,6 +28,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from .const import CONF_PICTURE, DOMAIN from .const import CONF_PICTURE, DOMAIN
from .entity import AbstractTemplateEntity
from .template_entity import ( from .template_entity import (
LEGACY_FIELDS as TEMPLATE_ENTITY_LEGACY_FIELDS, LEGACY_FIELDS as TEMPLATE_ENTITY_LEGACY_FIELDS,
TEMPLATE_ENTITY_AVAILABILITY_SCHEMA, TEMPLATE_ENTITY_AVAILABILITY_SCHEMA,
@ -134,42 +136,33 @@ async def async_setup_platform(
) )
class TemplateLock(TemplateEntity, LockEntity): class AbstractTemplateLock(AbstractTemplateEntity, LockEntity):
"""Representation of a template lock.""" """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 self._state: LockState | None = None
name = self._attr_name
if TYPE_CHECKING:
assert name is not None
self._state_template = config.get(CONF_STATE) 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_template = config.get(CONF_CODE_FORMAT)
self._code_format: str | None = None self._code_format: str | None = None
self._code_format_template_error: TemplateError | None = None self._code_format_template_error: TemplateError | None = None
self._optimistic = config.get(CONF_OPTIMISTIC) self._optimistic = config.get(CONF_OPTIMISTIC)
self._attr_assumed_state = bool(self._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 @property
def is_locked(self) -> bool: def is_locked(self) -> bool:
"""Return true if lock is locked.""" """Return true if lock is locked."""
@ -195,14 +188,12 @@ class TemplateLock(TemplateEntity, LockEntity):
"""Return true if lock is open.""" """Return true if lock is open."""
return self._state == LockState.OPEN return self._state == LockState.OPEN
@callback @property
def _update_state(self, result: str | TemplateError) -> None: def code_format(self) -> str | None:
"""Update the state from the template.""" """Regex for code format or None if no code is required."""
super()._update_state(result) return self._code_format
if isinstance(result, TemplateError):
self._state = None
return
def _handle_state(self, result: Any) -> None:
if isinstance(result, bool): if isinstance(result, bool):
self._state = LockState.LOCKED if result else LockState.UNLOCKED self._state = LockState.LOCKED if result else LockState.UNLOCKED
return return
@ -229,28 +220,6 @@ class TemplateLock(TemplateEntity, LockEntity):
self._state = None 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 @callback
def _update_code_format(self, render: str | TemplateError | None): def _update_code_format(self, render: str | TemplateError | None):
"""Update code format from the template.""" """Update code format from the template."""
@ -330,3 +299,57 @@ class TemplateLock(TemplateEntity, LockEntity):
"cause": str(self._code_format_template_error), "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()