Skip to content

Commit 9d124be

Browse files
Remove deprecated set state directly in alarmcontrolpanel (home-assistant#154038)
1 parent 8bca393 commit 9d124be

File tree

2 files changed

+2
-274
lines changed

2 files changed

+2
-274
lines changed

homeassistant/components/alarm_control_panel/__init__.py

Lines changed: 2 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@
22

33
from __future__ import annotations
44

5-
import asyncio
65
from datetime import timedelta
76
import logging
8-
from typing import TYPE_CHECKING, Any, Final, final
7+
from typing import Any, Final, final
98

109
from propcache.api import cached_property
1110
import voluptuous as vol
@@ -28,8 +27,6 @@
2827
from homeassistant.helpers.config_validation import make_entity_service_schema
2928
from homeassistant.helpers.entity import Entity, EntityDescription
3029
from homeassistant.helpers.entity_component import EntityComponent
31-
from homeassistant.helpers.entity_platform import EntityPlatform
32-
from homeassistant.helpers.frame import ReportBehavior, report_usage
3330
from homeassistant.helpers.typing import ConfigType
3431
from homeassistant.util.hass_dict import HassKey
3532

@@ -149,68 +146,11 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A
149146
)
150147
_alarm_control_panel_option_default_code: str | None = None
151148

152-
__alarm_legacy_state: bool = False
153-
154-
def __init_subclass__(cls, **kwargs: Any) -> None:
155-
"""Post initialisation processing."""
156-
super().__init_subclass__(**kwargs)
157-
if any(method in cls.__dict__ for method in ("_attr_state", "state")):
158-
# Integrations should use the 'alarm_state' property instead of
159-
# setting the state directly.
160-
cls.__alarm_legacy_state = True
161-
162-
def __setattr__(self, name: str, value: Any, /) -> None:
163-
"""Set attribute.
164-
165-
Deprecation warning if setting '_attr_state' directly
166-
unless already reported.
167-
"""
168-
if name == "_attr_state":
169-
self._report_deprecated_alarm_state_handling()
170-
return super().__setattr__(name, value)
171-
172-
@callback
173-
def add_to_platform_start(
174-
self,
175-
hass: HomeAssistant,
176-
platform: EntityPlatform,
177-
parallel_updates: asyncio.Semaphore | None,
178-
) -> None:
179-
"""Start adding an entity to a platform."""
180-
super().add_to_platform_start(hass, platform, parallel_updates)
181-
if self.__alarm_legacy_state:
182-
self._report_deprecated_alarm_state_handling()
183-
184-
@callback
185-
def _report_deprecated_alarm_state_handling(self) -> None:
186-
"""Report on deprecated handling of alarm state.
187-
188-
Integrations should implement alarm_state instead of using state directly.
189-
"""
190-
report_usage(
191-
"is setting state directly."
192-
f" Entity {self.entity_id} ({type(self)}) should implement the 'alarm_state'"
193-
" property and return its state using the AlarmControlPanelState enum",
194-
core_integration_behavior=ReportBehavior.ERROR,
195-
custom_integration_behavior=ReportBehavior.LOG,
196-
breaks_in_ha_version="2025.11",
197-
integration_domain=self.platform.platform_name if self.platform else None,
198-
exclude_integrations={DOMAIN},
199-
)
200-
201149
@final
202150
@property
203151
def state(self) -> str | None:
204152
"""Return the current state."""
205-
if (alarm_state := self.alarm_state) is not None:
206-
return alarm_state
207-
if self._attr_state is not None:
208-
# Backwards compatibility for integrations that set state directly
209-
# Should be removed in 2025.11
210-
if TYPE_CHECKING:
211-
assert isinstance(self._attr_state, str)
212-
return self._attr_state
213-
return None
153+
return self.alarm_state
214154

215155
@cached_property
216156
def alarm_state(self) -> AlarmControlPanelState | None:

tests/components/alarm_control_panel/test_init.py

Lines changed: 0 additions & 212 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
from homeassistant.components import alarm_control_panel
88
from homeassistant.components.alarm_control_panel import (
9-
DOMAIN,
109
AlarmControlPanelEntityFeature,
1110
CodeFormat,
1211
)
@@ -25,16 +24,8 @@
2524
from homeassistant.helpers import entity_registry as er
2625
from homeassistant.helpers.typing import UNDEFINED, UndefinedType
2726

28-
from . import help_async_setup_entry_init, help_async_unload_entry
2927
from .conftest import MockAlarmControlPanel
3028

31-
from tests.common import (
32-
MockConfigEntry,
33-
MockModule,
34-
mock_integration,
35-
setup_test_component_platform,
36-
)
37-
3829

3930
async def help_test_async_alarm_control_panel_service(
4031
hass: HomeAssistant,
@@ -233,206 +224,3 @@ async def test_alarm_control_panel_not_log_deprecated_state_warning(
233224
"the 'alarm_state' property and return its state using the AlarmControlPanelState enum"
234225
not in caplog.text
235226
)
236-
237-
238-
@pytest.mark.usefixtures("mock_as_custom_component")
239-
async def test_alarm_control_panel_log_deprecated_state_warning_using_state_prop(
240-
hass: HomeAssistant,
241-
code_format: CodeFormat | None,
242-
supported_features: AlarmControlPanelEntityFeature,
243-
code_arm_required: bool,
244-
caplog: pytest.LogCaptureFixture,
245-
) -> None:
246-
"""Test incorrectly using state property does log issue and raise repair."""
247-
248-
class MockLegacyAlarmControlPanel(MockAlarmControlPanel):
249-
"""Mocked alarm control entity."""
250-
251-
def __init__(
252-
self,
253-
supported_features: AlarmControlPanelEntityFeature = AlarmControlPanelEntityFeature(
254-
0
255-
),
256-
code_format: CodeFormat | None = None,
257-
code_arm_required: bool = True,
258-
) -> None:
259-
"""Initialize the alarm control."""
260-
super().__init__(supported_features, code_format, code_arm_required)
261-
262-
@property
263-
def state(self) -> str:
264-
"""Return the state of the entity."""
265-
return "disarmed"
266-
267-
entity = MockLegacyAlarmControlPanel(
268-
supported_features=supported_features,
269-
code_format=code_format,
270-
code_arm_required=code_arm_required,
271-
)
272-
config_entry = MockConfigEntry(domain="test")
273-
config_entry.add_to_hass(hass)
274-
mock_integration(
275-
hass,
276-
MockModule(
277-
"test",
278-
async_setup_entry=help_async_setup_entry_init,
279-
async_unload_entry=help_async_unload_entry,
280-
),
281-
built_in=False,
282-
)
283-
setup_test_component_platform(hass, DOMAIN, [entity], from_config_entry=True)
284-
assert await hass.config_entries.async_setup(config_entry.entry_id)
285-
286-
state = hass.states.get(entity.entity_id)
287-
assert state is not None
288-
289-
assert (
290-
"Detected that custom integration 'test' is setting state "
291-
"directly. Entity None (<class 'tests.components.alarm_control_panel."
292-
"test_init.test_alarm_control_panel_log_deprecated_state_warning_using"
293-
"_state_prop.<locals>.MockLegacyAlarmControlPanel'>) should implement"
294-
" the 'alarm_state' property and return its state using the AlarmControlPanelState"
295-
" enum. This will stop working in Home Assistant 2025.11, please report it to"
296-
" the author of the 'test' custom integration" in caplog.text
297-
)
298-
299-
300-
@pytest.mark.usefixtures("mock_as_custom_component")
301-
async def test_alarm_control_panel_log_deprecated_state_warning_using_attr_state_attr(
302-
hass: HomeAssistant,
303-
code_format: CodeFormat | None,
304-
supported_features: AlarmControlPanelEntityFeature,
305-
code_arm_required: bool,
306-
caplog: pytest.LogCaptureFixture,
307-
) -> None:
308-
"""Test incorrectly using _attr_state attribute does log issue and raise repair."""
309-
310-
class MockLegacyAlarmControlPanel(MockAlarmControlPanel):
311-
"""Mocked alarm control entity."""
312-
313-
def __init__(
314-
self,
315-
supported_features: AlarmControlPanelEntityFeature = AlarmControlPanelEntityFeature(
316-
0
317-
),
318-
code_format: CodeFormat | None = None,
319-
code_arm_required: bool = True,
320-
) -> None:
321-
"""Initialize the alarm control."""
322-
super().__init__(supported_features, code_format, code_arm_required)
323-
324-
def alarm_disarm(self, code: str | None = None) -> None:
325-
"""Mock alarm disarm calls."""
326-
self._attr_state = "disarmed"
327-
328-
entity = MockLegacyAlarmControlPanel(
329-
supported_features=supported_features,
330-
code_format=code_format,
331-
code_arm_required=code_arm_required,
332-
)
333-
config_entry = MockConfigEntry(domain="test")
334-
config_entry.add_to_hass(hass)
335-
mock_integration(
336-
hass,
337-
MockModule(
338-
"test",
339-
async_setup_entry=help_async_setup_entry_init,
340-
async_unload_entry=help_async_unload_entry,
341-
),
342-
built_in=False,
343-
)
344-
setup_test_component_platform(hass, DOMAIN, [entity], from_config_entry=True)
345-
assert await hass.config_entries.async_setup(config_entry.entry_id)
346-
347-
state = hass.states.get(entity.entity_id)
348-
assert state is not None
349-
350-
assert (
351-
"Detected that custom integration 'test' is setting state directly."
352-
not in caplog.text
353-
)
354-
355-
await help_test_async_alarm_control_panel_service(
356-
hass, entity.entity_id, SERVICE_ALARM_DISARM
357-
)
358-
359-
assert (
360-
"Detected that custom integration 'test' is setting state directly."
361-
" Entity alarm_control_panel.test_alarm_control_panel"
362-
" (<class 'tests.components.alarm_control_panel.test_init."
363-
"test_alarm_control_panel_log_deprecated_state_warning_using_attr_state_attr."
364-
"<locals>.MockLegacyAlarmControlPanel'>) should implement the 'alarm_state' property"
365-
" and return its state using the AlarmControlPanelState enum. "
366-
"This will stop working in Home Assistant 2025.11, please report "
367-
"it to the author of the 'test' custom integration" in caplog.text
368-
)
369-
caplog.clear()
370-
await help_test_async_alarm_control_panel_service(
371-
hass, entity.entity_id, SERVICE_ALARM_DISARM
372-
)
373-
# Test we only log once
374-
assert (
375-
"Detected that custom integration 'test' is setting state directly."
376-
not in caplog.text
377-
)
378-
379-
380-
@pytest.mark.usefixtures("mock_as_custom_component")
381-
async def test_alarm_control_panel_deprecated_state_does_not_break_state(
382-
hass: HomeAssistant,
383-
code_format: CodeFormat | None,
384-
supported_features: AlarmControlPanelEntityFeature,
385-
code_arm_required: bool,
386-
caplog: pytest.LogCaptureFixture,
387-
) -> None:
388-
"""Test using _attr_state attribute does not break state."""
389-
390-
class MockLegacyAlarmControlPanel(MockAlarmControlPanel):
391-
"""Mocked alarm control entity."""
392-
393-
def __init__(
394-
self,
395-
supported_features: AlarmControlPanelEntityFeature = AlarmControlPanelEntityFeature(
396-
0
397-
),
398-
code_format: CodeFormat | None = None,
399-
code_arm_required: bool = True,
400-
) -> None:
401-
"""Initialize the alarm control."""
402-
self._attr_state = "armed_away"
403-
super().__init__(supported_features, code_format, code_arm_required)
404-
405-
def alarm_disarm(self, code: str | None = None) -> None:
406-
"""Mock alarm disarm calls."""
407-
self._attr_state = "disarmed"
408-
409-
entity = MockLegacyAlarmControlPanel(
410-
supported_features=supported_features,
411-
code_format=code_format,
412-
code_arm_required=code_arm_required,
413-
)
414-
config_entry = MockConfigEntry(domain="test")
415-
config_entry.add_to_hass(hass)
416-
mock_integration(
417-
hass,
418-
MockModule(
419-
"test",
420-
async_setup_entry=help_async_setup_entry_init,
421-
async_unload_entry=help_async_unload_entry,
422-
),
423-
built_in=False,
424-
)
425-
setup_test_component_platform(hass, DOMAIN, [entity], from_config_entry=True)
426-
assert await hass.config_entries.async_setup(config_entry.entry_id)
427-
428-
state = hass.states.get(entity.entity_id)
429-
assert state is not None
430-
assert state.state == "armed_away"
431-
432-
await help_test_async_alarm_control_panel_service(
433-
hass, entity.entity_id, SERVICE_ALARM_DISARM
434-
)
435-
436-
state = hass.states.get(entity.entity_id)
437-
assert state is not None
438-
assert state.state == "disarmed"

0 commit comments

Comments
 (0)