Fix legacy _attr_state handling in AlarmControlPanel (#130479)
This commit is contained in:
parent
3092297979
commit
0ac00ef092
2 changed files with 103 additions and 4 deletions
|
@ -6,7 +6,7 @@ import asyncio
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from functools import partial
|
from functools import partial
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Final, final
|
from typing import TYPE_CHECKING, Any, Final, final
|
||||||
|
|
||||||
from propcache import cached_property
|
from propcache import cached_property
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
@ -221,9 +221,15 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A
|
||||||
@property
|
@property
|
||||||
def state(self) -> str | None:
|
def state(self) -> str | None:
|
||||||
"""Return the current state."""
|
"""Return the current state."""
|
||||||
if (alarm_state := self.alarm_state) is None:
|
if (alarm_state := self.alarm_state) is not None:
|
||||||
return None
|
return alarm_state
|
||||||
return alarm_state
|
if self._attr_state is not None:
|
||||||
|
# Backwards compatibility for integrations that set state directly
|
||||||
|
# Should be removed in 2025.11
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
assert isinstance(self._attr_state, str)
|
||||||
|
return self._attr_state
|
||||||
|
return None
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def alarm_state(self) -> AlarmControlPanelState | None:
|
def alarm_state(self) -> AlarmControlPanelState | None:
|
||||||
|
|
|
@ -489,3 +489,96 @@ async def test_alarm_control_panel_log_deprecated_state_warning_using_attr_state
|
||||||
)
|
)
|
||||||
# Test we only log once
|
# Test we only log once
|
||||||
assert "Entities should implement the 'alarm_state' property and" not in caplog.text
|
assert "Entities should implement the 'alarm_state' property and" not in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_alarm_control_panel_deprecated_state_does_not_break_state(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
code_format: CodeFormat | None,
|
||||||
|
supported_features: AlarmControlPanelEntityFeature,
|
||||||
|
code_arm_required: bool,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test using _attr_state attribute does not break state."""
|
||||||
|
|
||||||
|
async def async_setup_entry_init(
|
||||||
|
hass: HomeAssistant, config_entry: ConfigEntry
|
||||||
|
) -> bool:
|
||||||
|
"""Set up test config entry."""
|
||||||
|
await hass.config_entries.async_forward_entry_setups(
|
||||||
|
config_entry, [ALARM_CONTROL_PANEL_DOMAIN]
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
|
mock_integration(
|
||||||
|
hass,
|
||||||
|
MockModule(
|
||||||
|
TEST_DOMAIN,
|
||||||
|
async_setup_entry=async_setup_entry_init,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
class MockLegacyAlarmControlPanel(MockAlarmControlPanel):
|
||||||
|
"""Mocked alarm control entity."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
supported_features: AlarmControlPanelEntityFeature = AlarmControlPanelEntityFeature(
|
||||||
|
0
|
||||||
|
),
|
||||||
|
code_format: CodeFormat | None = None,
|
||||||
|
code_arm_required: bool = True,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the alarm control."""
|
||||||
|
self._attr_state = "armed_away"
|
||||||
|
super().__init__(supported_features, code_format, code_arm_required)
|
||||||
|
|
||||||
|
def alarm_disarm(self, code: str | None = None) -> None:
|
||||||
|
"""Mock alarm disarm calls."""
|
||||||
|
self._attr_state = "disarmed"
|
||||||
|
|
||||||
|
entity = MockLegacyAlarmControlPanel(
|
||||||
|
supported_features=supported_features,
|
||||||
|
code_format=code_format,
|
||||||
|
code_arm_required=code_arm_required,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_setup_entry_platform(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: ConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Set up test alarm control panel platform via config entry."""
|
||||||
|
async_add_entities([entity])
|
||||||
|
|
||||||
|
mock_platform(
|
||||||
|
hass,
|
||||||
|
f"{TEST_DOMAIN}.{ALARM_CONTROL_PANEL_DOMAIN}",
|
||||||
|
MockPlatform(async_setup_entry=async_setup_entry_platform),
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch.object(
|
||||||
|
MockLegacyAlarmControlPanel,
|
||||||
|
"__module__",
|
||||||
|
"tests.custom_components.test.alarm_control_panel",
|
||||||
|
):
|
||||||
|
config_entry = MockConfigEntry(domain=TEST_DOMAIN)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(entity.entity_id)
|
||||||
|
assert state is not None
|
||||||
|
assert state.state == "armed_away"
|
||||||
|
|
||||||
|
with patch.object(
|
||||||
|
MockLegacyAlarmControlPanel,
|
||||||
|
"__module__",
|
||||||
|
"tests.custom_components.test.alarm_control_panel",
|
||||||
|
):
|
||||||
|
await help_test_async_alarm_control_panel_service(
|
||||||
|
hass, entity.entity_id, SERVICE_ALARM_DISARM
|
||||||
|
)
|
||||||
|
|
||||||
|
state = hass.states.get(entity.entity_id)
|
||||||
|
assert state is not None
|
||||||
|
assert state.state == "disarmed"
|
||||||
|
|
Loading…
Add table
Reference in a new issue