Use report_usage for deprecation warning in alarm_control_panel (#130543)

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
This commit is contained in:
G Johansson 2024-11-27 08:57:32 +01:00 committed by GitHub
parent 2b939ce6ec
commit 1e05f98ddd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 91 additions and 78 deletions

View File

@ -35,6 +35,7 @@ from homeassistant.helpers.deprecation import (
from homeassistant.helpers.entity import Entity, EntityDescription from homeassistant.helpers.entity import Entity, EntityDescription
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity_platform import EntityPlatform from homeassistant.helpers.entity_platform import EntityPlatform
from homeassistant.helpers.frame import ReportBehavior, report_usage
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from homeassistant.util.hass_dict import HassKey from homeassistant.util.hass_dict import HassKey
@ -163,7 +164,6 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A
_alarm_control_panel_option_default_code: str | None = None _alarm_control_panel_option_default_code: str | None = None
__alarm_legacy_state: bool = False __alarm_legacy_state: bool = False
__alarm_legacy_state_reported: bool = False
def __init_subclass__(cls, **kwargs: Any) -> None: def __init_subclass__(cls, **kwargs: Any) -> None:
"""Post initialisation processing.""" """Post initialisation processing."""
@ -180,9 +180,7 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A
unless already reported. unless already reported.
""" """
if name == "_attr_state": if name == "_attr_state":
if self.__alarm_legacy_state_reported is not True: self._report_deprecated_alarm_state_handling()
self._report_deprecated_alarm_state_handling()
self.__alarm_legacy_state_reported = True
return super().__setattr__(name, value) return super().__setattr__(name, value)
@callback @callback
@ -194,7 +192,7 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A
) -> None: ) -> None:
"""Start adding an entity to a platform.""" """Start adding an entity to a platform."""
super().add_to_platform_start(hass, platform, parallel_updates) super().add_to_platform_start(hass, platform, parallel_updates)
if self.__alarm_legacy_state and not self.__alarm_legacy_state_reported: if self.__alarm_legacy_state:
self._report_deprecated_alarm_state_handling() self._report_deprecated_alarm_state_handling()
@callback @callback
@ -203,19 +201,16 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A
Integrations should implement alarm_state instead of using state directly. Integrations should implement alarm_state instead of using state directly.
""" """
self.__alarm_legacy_state_reported = True report_usage(
if "custom_components" in type(self).__module__: "is setting state directly."
# Do not report on core integrations as they have been fixed. f" Entity {self.entity_id} ({type(self)}) should implement the 'alarm_state'"
report_issue = "report it to the custom integration author." " property and return its state using the AlarmControlPanelState enum",
_LOGGER.warning( core_integration_behavior=ReportBehavior.ERROR,
"Entity %s (%s) is setting state directly" custom_integration_behavior=ReportBehavior.LOG,
" which will stop working in HA Core 2025.11." breaks_in_ha_version="2025.11",
" Entities should implement the 'alarm_state' property and" integration_domain=self.platform.platform_name if self.platform else None,
" return its state using the AlarmControlPanelState enum, please %s", exclude_integrations={DOMAIN},
self.entity_id, )
type(self),
report_issue,
)
@final @final
@property @property

View File

@ -1,7 +1,7 @@
"""Fixturs for Alarm Control Panel tests.""" """Fixturs for Alarm Control Panel tests."""
from collections.abc import Generator from collections.abc import AsyncGenerator, Generator
from unittest.mock import MagicMock from unittest.mock import MagicMock, patch
import pytest import pytest
@ -13,7 +13,7 @@ from homeassistant.components.alarm_control_panel import (
from homeassistant.components.alarm_control_panel.const import CodeFormat from homeassistant.components.alarm_control_panel.const import CodeFormat
from homeassistant.config_entries import ConfigEntry, ConfigFlow from homeassistant.config_entries import ConfigEntry, ConfigFlow
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er, frame
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .common import MockAlarm from .common import MockAlarm
@ -107,6 +107,22 @@ class MockFlow(ConfigFlow):
"""Test flow.""" """Test flow."""
@pytest.fixture(name="mock_as_custom_component")
async def mock_frame(hass: HomeAssistant) -> AsyncGenerator[None]:
"""Mock frame."""
with patch(
"homeassistant.helpers.frame.get_integration_frame",
return_value=frame.IntegrationFrame(
custom_integration=True,
integration="alarm_control_panel",
module="test_init.py",
relative_filename="test_init.py",
frame=frame.get_current_frame(),
),
):
yield
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def config_flow_fixture(hass: HomeAssistant) -> Generator[None]: def config_flow_fixture(hass: HomeAssistant) -> Generator[None]:
"""Mock config flow.""" """Mock config flow."""

View File

@ -25,7 +25,7 @@ from homeassistant.const import (
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er, frame
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import UNDEFINED, UndefinedType from homeassistant.helpers.typing import UNDEFINED, UndefinedType
@ -297,6 +297,7 @@ async def test_alarm_control_panel_with_default_code(
mock_alarm_control_panel_entity.calls_disarm.assert_called_with("1234") mock_alarm_control_panel_entity.calls_disarm.assert_called_with("1234")
@patch.object(frame, "_REPORTED_INTEGRATIONS", set())
async def test_alarm_control_panel_not_log_deprecated_state_warning( async def test_alarm_control_panel_not_log_deprecated_state_warning(
hass: HomeAssistant, hass: HomeAssistant,
mock_alarm_control_panel_entity: MockAlarmControlPanel, mock_alarm_control_panel_entity: MockAlarmControlPanel,
@ -305,9 +306,14 @@ async def test_alarm_control_panel_not_log_deprecated_state_warning(
"""Test correctly using alarm_state doesn't log issue or raise repair.""" """Test correctly using alarm_state doesn't log issue or raise repair."""
state = hass.states.get(mock_alarm_control_panel_entity.entity_id) state = hass.states.get(mock_alarm_control_panel_entity.entity_id)
assert state is not None assert state is not None
assert "Entities should implement the 'alarm_state' property and" not in caplog.text assert (
"the 'alarm_state' property and return its state using the AlarmControlPanelState enum"
not in caplog.text
)
@pytest.mark.usefixtures("mock_as_custom_component")
@patch.object(frame, "_REPORTED_INTEGRATIONS", set())
async def test_alarm_control_panel_log_deprecated_state_warning_using_state_prop( async def test_alarm_control_panel_log_deprecated_state_warning_using_state_prop(
hass: HomeAssistant, hass: HomeAssistant,
code_format: CodeFormat | None, code_format: CodeFormat | None,
@ -332,6 +338,7 @@ async def test_alarm_control_panel_log_deprecated_state_warning_using_state_prop
TEST_DOMAIN, TEST_DOMAIN,
async_setup_entry=async_setup_entry_init, async_setup_entry=async_setup_entry_init,
), ),
built_in=False,
) )
class MockLegacyAlarmControlPanel(MockAlarmControlPanel): class MockLegacyAlarmControlPanel(MockAlarmControlPanel):
@ -373,22 +380,26 @@ async def test_alarm_control_panel_log_deprecated_state_warning_using_state_prop
MockPlatform(async_setup_entry=async_setup_entry_platform), MockPlatform(async_setup_entry=async_setup_entry_platform),
) )
with patch.object( config_entry = MockConfigEntry(domain=TEST_DOMAIN)
MockLegacyAlarmControlPanel, config_entry.add_to_hass(hass)
"__module__", assert await hass.config_entries.async_setup(config_entry.entry_id)
"tests.custom_components.test.alarm_control_panel", await hass.async_block_till_done()
):
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) state = hass.states.get(entity.entity_id)
assert state is not None assert state is not None
assert "Entities should implement the 'alarm_state' property and" in caplog.text assert (
"Detected that custom integration 'alarm_control_panel' is setting state directly."
" Entity None (<class 'tests.components.alarm_control_panel.test_init."
"test_alarm_control_panel_log_deprecated_state_warning_using_state_prop.<locals>.MockLegacyAlarmControlPanel'>)"
" should implement the 'alarm_state' property and return its state using the AlarmControlPanelState enum"
" at test_init.py, line 123: yield. This will stop working in Home Assistant 2025.11, please create a bug report at"
in caplog.text
)
@pytest.mark.usefixtures("mock_as_custom_component")
@patch.object(frame, "_REPORTED_INTEGRATIONS", set())
async def test_alarm_control_panel_log_deprecated_state_warning_using_attr_state_attr( async def test_alarm_control_panel_log_deprecated_state_warning_using_attr_state_attr(
hass: HomeAssistant, hass: HomeAssistant,
code_format: CodeFormat | None, code_format: CodeFormat | None,
@ -453,44 +464,45 @@ async def test_alarm_control_panel_log_deprecated_state_warning_using_attr_state
MockPlatform(async_setup_entry=async_setup_entry_platform), MockPlatform(async_setup_entry=async_setup_entry_platform),
) )
with patch.object( config_entry = MockConfigEntry(domain=TEST_DOMAIN)
MockLegacyAlarmControlPanel, config_entry.add_to_hass(hass)
"__module__", assert await hass.config_entries.async_setup(config_entry.entry_id)
"tests.custom_components.test.alarm_control_panel", await hass.async_block_till_done()
):
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) state = hass.states.get(entity.entity_id)
assert state is not None assert state is not None
assert "Entities should implement the 'alarm_state' property and" not in caplog.text assert (
"Detected that custom integration 'alarm_control_panel' is setting state directly."
not in caplog.text
)
with patch.object( await help_test_async_alarm_control_panel_service(
MockLegacyAlarmControlPanel, hass, entity.entity_id, SERVICE_ALARM_DISARM
"__module__", )
"tests.custom_components.test.alarm_control_panel",
):
await help_test_async_alarm_control_panel_service(
hass, entity.entity_id, SERVICE_ALARM_DISARM
)
assert "Entities should implement the 'alarm_state' property and" in caplog.text assert (
"Detected that custom integration 'alarm_control_panel' is setting state directly."
" Entity alarm_control_panel.test_alarm_control_panel"
" (<class 'tests.components.alarm_control_panel.test_init."
"test_alarm_control_panel_log_deprecated_state_warning_using_attr_state_attr.<locals>.MockLegacyAlarmControlPanel'>)"
" should implement the 'alarm_state' property and return its state using the AlarmControlPanelState enum"
" at test_init.py, line 123: yield. This will stop working in Home Assistant 2025.11, please create a bug report at"
in caplog.text
)
caplog.clear() caplog.clear()
with patch.object( await help_test_async_alarm_control_panel_service(
MockLegacyAlarmControlPanel, hass, entity.entity_id, SERVICE_ALARM_DISARM
"__module__", )
"tests.custom_components.test.alarm_control_panel",
):
await help_test_async_alarm_control_panel_service(
hass, entity.entity_id, SERVICE_ALARM_DISARM
)
# Test we only log once # Test we only log once
assert "Entities should implement the 'alarm_state' property and" not in caplog.text assert (
"Detected that custom integration 'alarm_control_panel' is setting state directly."
not in caplog.text
)
@pytest.mark.usefixtures("mock_as_custom_component")
@patch.object(frame, "_REPORTED_INTEGRATIONS", set())
async def test_alarm_control_panel_deprecated_state_does_not_break_state( async def test_alarm_control_panel_deprecated_state_does_not_break_state(
hass: HomeAssistant, hass: HomeAssistant,
code_format: CodeFormat | None, code_format: CodeFormat | None,
@ -556,28 +568,18 @@ async def test_alarm_control_panel_deprecated_state_does_not_break_state(
MockPlatform(async_setup_entry=async_setup_entry_platform), MockPlatform(async_setup_entry=async_setup_entry_platform),
) )
with patch.object( config_entry = MockConfigEntry(domain=TEST_DOMAIN)
MockLegacyAlarmControlPanel, config_entry.add_to_hass(hass)
"__module__", assert await hass.config_entries.async_setup(config_entry.entry_id)
"tests.custom_components.test.alarm_control_panel", await hass.async_block_till_done()
):
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) state = hass.states.get(entity.entity_id)
assert state is not None assert state is not None
assert state.state == "armed_away" assert state.state == "armed_away"
with patch.object( await help_test_async_alarm_control_panel_service(
MockLegacyAlarmControlPanel, hass, entity.entity_id, SERVICE_ALARM_DISARM
"__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) state = hass.states.get(entity.entity_id)
assert state is not None assert state is not None