From 88491c7461f37376df10ca7d57628ebc26c03b19 Mon Sep 17 00:00:00 2001 From: Andrea Pierangeli Date: Thu, 14 Nov 2024 13:14:55 +0100 Subject: [PATCH] fix(alarm): address HA 2025.x deprecations (#171) - Fix testing for Home Assistant > 2024.8 - Fix alarm panel deprecations - Fix `ConfigEntryState` deprecations - Fix `async_forward_entry_setup` deprecation (fixes #169) - Fix `alarm_state` deprecation for HA Core 2025.11 --- .../econnect_metronet/__init__.py | 3 +- .../econnect_metronet/alarm_control_panel.py | 24 ++++------ .../econnect_metronet/devices.py | 25 ++++------- pyproject.toml | 1 + tests/conftest.py | 2 + tests/test_devices.py | 45 ++++++++----------- 6 files changed, 38 insertions(+), 62 deletions(-) diff --git a/custom_components/econnect_metronet/__init__.py b/custom_components/econnect_metronet/__init__.py index 47371cf..7d8e71c 100644 --- a/custom_components/econnect_metronet/__init__.py +++ b/custom_components/econnect_metronet/__init__.py @@ -132,8 +132,7 @@ async def async_setup_entry(hass: HomeAssistant, config: ConfigEntry) -> bool: hass.services.async_register(DOMAIN, "disarm_sectors", partial(services.disarm_sectors, hass, config.entry_id)) hass.services.async_register(DOMAIN, "update_state", partial(services.update_state, hass, config.entry_id)) - for component in PLATFORMS: - hass.async_create_task(hass.config_entries.async_forward_entry_setup(config, component)) + await hass.config_entries.async_forward_entry_setups(config, PLATFORMS) return True diff --git a/custom_components/econnect_metronet/alarm_control_panel.py b/custom_components/econnect_metronet/alarm_control_panel.py index fc1cf88..e63b928 100644 --- a/custom_components/econnect_metronet/alarm_control_panel.py +++ b/custom_components/econnect_metronet/alarm_control_panel.py @@ -4,22 +4,14 @@ from homeassistant.components.alarm_control_panel import ( AlarmControlPanelEntity, + AlarmControlPanelState, CodeFormat, ) from homeassistant.components.alarm_control_panel.const import ( AlarmControlPanelEntityFeature as AlarmFeatures, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ( - CONF_USERNAME, - STATE_ALARM_ARMED_AWAY, - STATE_ALARM_ARMED_HOME, - STATE_ALARM_ARMED_NIGHT, - STATE_ALARM_ARMED_VACATION, - STATE_ALARM_ARMING, - STATE_ALARM_DISARMED, - STATE_ALARM_DISARMING, -) +from homeassistant.const import CONF_USERNAME from homeassistant.core import HomeAssistant from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -77,7 +69,7 @@ def icon(self): return "hass:shield-home" @property - def state(self): + def alarm_state(self): """Return the state of the device.""" return self._device.state @@ -91,19 +83,19 @@ def supported_features(self): """Return the list of supported features.""" return AlarmFeatures.ARM_HOME | AlarmFeatures.ARM_AWAY | AlarmFeatures.ARM_NIGHT | AlarmFeatures.ARM_VACATION - @set_device_state(STATE_ALARM_DISARMED, STATE_ALARM_DISARMING) + @set_device_state(AlarmControlPanelState.DISARMED, AlarmControlPanelState.DISARMING) @retry_refresh_token async def async_alarm_disarm(self, code=None): """Send disarm command.""" await self.hass.async_add_executor_job(self._device.disarm, code) - @set_device_state(STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMING) + @set_device_state(AlarmControlPanelState.ARMED_AWAY, AlarmControlPanelState.ARMING) @retry_refresh_token async def async_alarm_arm_away(self, code=None): """Send arm away command.""" await self.hass.async_add_executor_job(self._device.arm, code, self._device._sectors_away) - @set_device_state(STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMING) + @set_device_state(AlarmControlPanelState.ARMED_HOME, AlarmControlPanelState.ARMING) @retry_refresh_token async def async_alarm_arm_home(self, code=None): """Send arm home command.""" @@ -113,7 +105,7 @@ async def async_alarm_arm_home(self, code=None): await self.hass.async_add_executor_job(self._device.arm, code, self._device._sectors_home) - @set_device_state(STATE_ALARM_ARMED_NIGHT, STATE_ALARM_ARMING) + @set_device_state(AlarmControlPanelState.ARMED_NIGHT, AlarmControlPanelState.ARMING) @retry_refresh_token async def async_alarm_arm_night(self, code=None): """Send arm night command.""" @@ -123,7 +115,7 @@ async def async_alarm_arm_night(self, code=None): await self.hass.async_add_executor_job(self._device.arm, code, self._device._sectors_night) - @set_device_state(STATE_ALARM_ARMED_VACATION, STATE_ALARM_ARMING) + @set_device_state(AlarmControlPanelState.ARMED_VACATION, AlarmControlPanelState.ARMING) @retry_refresh_token async def async_alarm_arm_vacation(self, code=None): """Send arm vacation command.""" diff --git a/custom_components/econnect_metronet/devices.py b/custom_components/econnect_metronet/devices.py index a5319b0..670d645 100644 --- a/custom_components/econnect_metronet/devices.py +++ b/custom_components/econnect_metronet/devices.py @@ -10,16 +10,7 @@ LockError, ParseError, ) -from homeassistant.const import ( - STATE_ALARM_ARMED_AWAY, - STATE_ALARM_ARMED_HOME, - STATE_ALARM_ARMED_NIGHT, - STATE_ALARM_ARMED_VACATION, - STATE_ALARM_ARMING, - STATE_ALARM_DISARMED, - STATE_ALARM_DISARMING, - STATE_UNAVAILABLE, -) +from homeassistant.components.alarm_control_panel import AlarmControlPanelState from requests.exceptions import HTTPError from .const import ( @@ -72,7 +63,7 @@ def __init__(self, connection, config=None): self._sectors_vacation = config.get(CONF_AREAS_ARM_VACATION) or [] # Alarm state - self.state = STATE_UNAVAILABLE + self.state = None def _register_sector(self, entity): """Register a sector entity in the device's internal inventory.""" @@ -225,12 +216,12 @@ def get_state(self): """ # If the system is arming or disarming, return the current state # to prevent the state from being updated while the system is in transition. - if self.state in [STATE_ALARM_ARMING, STATE_ALARM_DISARMING]: + if self.state in [AlarmControlPanelState.ARMING, AlarmControlPanelState.DISARMING]: return self.state sectors_armed = dict(self.items(q.SECTORS, status=True)) if not sectors_armed: - return STATE_ALARM_DISARMED + return AlarmControlPanelState.DISARMED # Note: `element` is the sector ID you use to arm/disarm the sector. sectors = [sectors["element"] for sectors in sectors_armed.values()] @@ -238,15 +229,15 @@ def get_state(self): # regardless of whether the input lists were pre-sorted or not. sectors_armed_sorted = sorted(sectors) if sectors_armed_sorted == sorted(self._sectors_home): - return STATE_ALARM_ARMED_HOME + return AlarmControlPanelState.ARMED_HOME if sectors_armed_sorted == sorted(self._sectors_night): - return STATE_ALARM_ARMED_NIGHT + return AlarmControlPanelState.ARMED_NIGHT if sectors_armed_sorted == sorted(self._sectors_vacation): - return STATE_ALARM_ARMED_VACATION + return AlarmControlPanelState.ARMED_VACATION - return STATE_ALARM_ARMED_AWAY + return AlarmControlPanelState.ARMED_AWAY def get_status(self, query: int, id: int) -> Union[bool, int]: """Get the status of an item in the device inventory specified by query and id. diff --git a/pyproject.toml b/pyproject.toml index bb9a351..7e894ac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,6 +53,7 @@ dev = [ "pytest-socket", "requests-mock", "syrupy", + "respx", ] lint = [ diff --git a/tests/conftest.py b/tests/conftest.py index b393d3f..83c178d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,6 +3,7 @@ import pytest import responses from elmo.api.client import ElmoClient +from homeassistant.config_entries import ConfigEntryState from custom_components.econnect_metronet import async_setup from custom_components.econnect_metronet.alarm_control_panel import EconnectAlarm @@ -164,6 +165,7 @@ def config_entry(hass): "domain": "econnect_metronet", "system_base_url": "https://example.com", }, + state=ConfigEntryState.SETUP_IN_PROGRESS, ) config.add_to_hass(hass) return config diff --git a/tests/test_devices.py b/tests/test_devices.py index f634122..ef92a1a 100644 --- a/tests/test_devices.py +++ b/tests/test_devices.py @@ -2,16 +2,7 @@ import responses from elmo import query as q from elmo.api.exceptions import CodeError, CredentialError, LockError, ParseError -from homeassistant.const import ( - STATE_ALARM_ARMED_AWAY, - STATE_ALARM_ARMED_HOME, - STATE_ALARM_ARMED_NIGHT, - STATE_ALARM_ARMED_VACATION, - STATE_ALARM_ARMING, - STATE_ALARM_DISARMED, - STATE_ALARM_DISARMING, - STATE_UNAVAILABLE, -) +from homeassistant.components.alarm_control_panel import AlarmControlPanelState from requests.exceptions import HTTPError from requests.models import Response @@ -42,7 +33,7 @@ def test_device_constructor(client): assert device._sectors_home == [] assert device._sectors_night == [] assert device._sectors_vacation == [] - assert device.state == STATE_UNAVAILABLE + assert device.state is None def test_device_constructor_with_config(client): @@ -66,7 +57,7 @@ def test_device_constructor_with_config(client): assert device._sectors_home == [3, 4] assert device._sectors_night == [1, 2, 3] assert device._sectors_vacation == [5, 3] - assert device.state == STATE_UNAVAILABLE + assert device.state is None def test_device_constructor_with_config_empty(client): @@ -90,7 +81,7 @@ def test_device_constructor_with_config_empty(client): assert device._sectors_home == [] assert device._sectors_night == [] assert device._sectors_vacation == [] - assert device.state == STATE_UNAVAILABLE + assert device.state is None class TestItemInputs: @@ -1468,7 +1459,7 @@ def test_get_state_no_sectors_armed(alarm_device): alarm_device._sectors_night = [] alarm_device._inventory = {9: {}} # Test - assert alarm_device.get_state() == STATE_ALARM_DISARMED + assert alarm_device.get_state() == AlarmControlPanelState.DISARMED def test_get_state_armed_home(alarm_device): @@ -1482,7 +1473,7 @@ def test_get_state_armed_home(alarm_device): } } # Test - assert alarm_device.get_state() == STATE_ALARM_ARMED_HOME + assert alarm_device.get_state() == AlarmControlPanelState.ARMED_HOME def test_get_state_armed_home_out_of_order(alarm_device): @@ -1496,7 +1487,7 @@ def test_get_state_armed_home_out_of_order(alarm_device): } } # Test - assert alarm_device.get_state() == STATE_ALARM_ARMED_HOME + assert alarm_device.get_state() == AlarmControlPanelState.ARMED_HOME def test_get_state_armed_night(alarm_device): @@ -1510,7 +1501,7 @@ def test_get_state_armed_night(alarm_device): } } # Test (out of order keys to test sorting) - assert alarm_device.get_state() == STATE_ALARM_ARMED_NIGHT + assert alarm_device.get_state() == AlarmControlPanelState.ARMED_NIGHT def test_get_state_armed_night_out_of_order(alarm_device): @@ -1524,7 +1515,7 @@ def test_get_state_armed_night_out_of_order(alarm_device): } } # Test - assert alarm_device.get_state() == STATE_ALARM_ARMED_NIGHT + assert alarm_device.get_state() == AlarmControlPanelState.ARMED_NIGHT def test_get_state_armed_vacation(alarm_device): @@ -1538,7 +1529,7 @@ def test_get_state_armed_vacation(alarm_device): } } # Test (out of order keys to test sorting) - assert alarm_device.get_state() == STATE_ALARM_ARMED_VACATION + assert alarm_device.get_state() == AlarmControlPanelState.ARMED_VACATION def test_get_state_armed_vacation_out_of_order(alarm_device): @@ -1552,7 +1543,7 @@ def test_get_state_armed_vacation_out_of_order(alarm_device): } } # Test - assert alarm_device.get_state() == STATE_ALARM_ARMED_VACATION + assert alarm_device.get_state() == AlarmControlPanelState.ARMED_VACATION def test_get_state_armed_away(alarm_device): @@ -1569,7 +1560,7 @@ def test_get_state_armed_away(alarm_device): } } # Test - assert alarm_device.get_state() == STATE_ALARM_ARMED_AWAY + assert alarm_device.get_state() == AlarmControlPanelState.ARMED_AWAY def test_get_state_armed_mixed(alarm_device): @@ -1586,7 +1577,7 @@ def test_get_state_armed_mixed(alarm_device): } } # Test - assert alarm_device.get_state() == STATE_ALARM_ARMED_AWAY + assert alarm_device.get_state() == AlarmControlPanelState.ARMED_AWAY def test_get_state_armed_away_with_config(alarm_device): @@ -1603,7 +1594,7 @@ def test_get_state_armed_away_with_config(alarm_device): } } # Test - assert alarm_device.get_state() == STATE_ALARM_ARMED_AWAY + assert alarm_device.get_state() == AlarmControlPanelState.ARMED_AWAY def test_get_state_while_disarming(alarm_device): @@ -1612,9 +1603,9 @@ def test_get_state_while_disarming(alarm_device): alarm_device._sectors_home = [] alarm_device._sectors_night = [] alarm_device._inventory = {9: {}} - alarm_device.state = STATE_ALARM_DISARMING + alarm_device.state = AlarmControlPanelState.DISARMING # Test - assert alarm_device.get_state() == STATE_ALARM_DISARMING + assert alarm_device.get_state() == AlarmControlPanelState.DISARMING def test_get_state_while_arming(alarm_device): @@ -1623,9 +1614,9 @@ def test_get_state_while_arming(alarm_device): alarm_device._sectors_home = [] alarm_device._sectors_night = [] alarm_device._inventory = {9: {}} - alarm_device.state = STATE_ALARM_ARMING + alarm_device.state = AlarmControlPanelState.ARMING # Test - assert alarm_device.get_state() == STATE_ALARM_ARMING + assert alarm_device.get_state() == AlarmControlPanelState.ARMING class TestTurnOff: