From 50fbcaf20f27eb4289f989dff1e0964643536393 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Sat, 6 Jan 2024 15:56:19 -0700 Subject: [PATCH] Streamline exception handling in Guardian (#107053) --- homeassistant/components/guardian/__init__.py | 8 ++-- homeassistant/components/guardian/button.py | 13 ++----- homeassistant/components/guardian/switch.py | 37 +++++-------------- homeassistant/components/guardian/util.py | 37 +++++++++++++++++-- 4 files changed, 50 insertions(+), 45 deletions(-) diff --git a/homeassistant/components/guardian/__init__.py b/homeassistant/components/guardian/__init__.py index b9f0740ea0c..2343871bd99 100644 --- a/homeassistant/components/guardian/__init__.py +++ b/homeassistant/components/guardian/__init__.py @@ -2,9 +2,9 @@ from __future__ import annotations import asyncio -from collections.abc import Awaitable, Callable, Coroutine +from collections.abc import Callable, Coroutine from dataclasses import dataclass -from typing import Any, cast +from typing import Any from aioguardian import Client from aioguardian.errors import GuardianError @@ -302,9 +302,7 @@ class PairedSensorManager: entry=self._entry, client=self._client, api_name=f"{API_SENSOR_PAIRED_SENSOR_STATUS}_{uid}", - api_coro=lambda: cast( - Awaitable, self._client.sensor.paired_sensor_status(uid) - ), + api_coro=lambda: self._client.sensor.paired_sensor_status(uid), api_lock=self._api_lock, valve_controller_uid=self._entry.data[CONF_UID], ) diff --git a/homeassistant/components/guardian/button.py b/homeassistant/components/guardian/button.py index 485de90f1d8..cb9c6f0121c 100644 --- a/homeassistant/components/guardian/button.py +++ b/homeassistant/components/guardian/button.py @@ -5,7 +5,6 @@ from collections.abc import Awaitable, Callable from dataclasses import dataclass from aioguardian import Client -from aioguardian.errors import GuardianError from homeassistant.components.button import ( ButtonDeviceClass, @@ -15,12 +14,12 @@ from homeassistant.components.button import ( from homeassistant.config_entries import ConfigEntry from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant -from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import GuardianData, ValveControllerEntity, ValveControllerEntityDescription from .const import API_SYSTEM_DIAGNOSTICS, DOMAIN +from .util import convert_exceptions_to_homeassistant_error @dataclass(frozen=True, kw_only=True) @@ -96,14 +95,10 @@ class GuardianButton(ValveControllerEntity, ButtonEntity): self._client = data.client + @convert_exceptions_to_homeassistant_error async def async_press(self) -> None: """Send out a restart command.""" - try: - async with self._client: - await self.entity_description.push_action(self._client) - except GuardianError as err: - raise HomeAssistantError( - f'Error while pressing button "{self.entity_id}": {err}' - ) from err + async with self._client: + await self.entity_description.push_action(self._client) async_dispatcher_send(self.hass, self.coordinator.signal_reboot_requested) diff --git a/homeassistant/components/guardian/switch.py b/homeassistant/components/guardian/switch.py index 81f06ba4356..69d86c10e04 100644 --- a/homeassistant/components/guardian/switch.py +++ b/homeassistant/components/guardian/switch.py @@ -6,17 +6,16 @@ from dataclasses import dataclass from typing import Any from aioguardian import Client -from aioguardian.errors import GuardianError from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription from homeassistant.config_entries import ConfigEntry from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant -from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import GuardianData, ValveControllerEntity, ValveControllerEntityDescription from .const import API_VALVE_STATUS, API_WIFI_STATUS, DOMAIN +from .util import convert_exceptions_to_homeassistant_error ATTR_AVG_CURRENT = "average_current" ATTR_CONNECTED_CLIENTS = "connected_clients" @@ -139,34 +138,16 @@ class ValveControllerSwitch(ValveControllerEntity, SwitchEntity): """Return True if entity is on.""" return self.entity_description.is_on_fn(self.coordinator.data) + @convert_exceptions_to_homeassistant_error async def async_turn_off(self, **kwargs: Any) -> None: """Turn the switch off.""" - if not self._attr_is_on: - return - - try: - async with self._client: - await self.entity_description.off_fn(self._client) - except GuardianError as err: - raise HomeAssistantError( - f'Error while turning "{self.entity_id}" off: {err}' - ) from err - - self._attr_is_on = False - self.async_write_ha_state() + async with self._client: + await self.entity_description.off_fn(self._client) + await self.coordinator.async_request_refresh() + @convert_exceptions_to_homeassistant_error async def async_turn_on(self, **kwargs: Any) -> None: """Turn the switch on.""" - if self._attr_is_on: - return - - try: - async with self._client: - await self.entity_description.on_fn(self._client) - except GuardianError as err: - raise HomeAssistantError( - f'Error while turning "{self.entity_id}" on: {err}' - ) from err - - self._attr_is_on = True - self.async_write_ha_state() + async with self._client: + await self.entity_description.on_fn(self._client) + await self.coordinator.async_request_refresh() diff --git a/homeassistant/components/guardian/util.py b/homeassistant/components/guardian/util.py index 400cd472446..048f3750d32 100644 --- a/homeassistant/components/guardian/util.py +++ b/homeassistant/components/guardian/util.py @@ -2,22 +2,29 @@ from __future__ import annotations import asyncio -from collections.abc import Awaitable, Callable, Iterable +from collections.abc import Callable, Coroutine, Iterable from dataclasses import dataclass from datetime import timedelta -from typing import Any, cast +from functools import wraps +from typing import TYPE_CHECKING, Any, Concatenate, ParamSpec, TypeVar, cast from aioguardian import Client from aioguardian.errors import GuardianError from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import LOGGER +if TYPE_CHECKING: + from . import GuardianEntity + + _GuardianEntityT = TypeVar("_GuardianEntityT", bound=GuardianEntity) + DEFAULT_UPDATE_INTERVAL = timedelta(seconds=30) SIGNAL_REBOOT_REQUESTED = "guardian_reboot_requested_{0}" @@ -68,7 +75,7 @@ class GuardianDataUpdateCoordinator(DataUpdateCoordinator[dict]): entry: ConfigEntry, client: Client, api_name: str, - api_coro: Callable[..., Awaitable], + api_coro: Callable[..., Coroutine[Any, Any, dict[str, Any]]], api_lock: asyncio.Lock, valve_controller_uid: str, ) -> None: @@ -112,3 +119,27 @@ class GuardianDataUpdateCoordinator(DataUpdateCoordinator[dict]): self.hass, self.signal_reboot_requested, async_reboot_requested ) ) + + +_P = ParamSpec("_P") + + +@callback +def convert_exceptions_to_homeassistant_error( + func: Callable[Concatenate[_GuardianEntityT, _P], Coroutine[Any, Any, Any]], +) -> Callable[Concatenate[_GuardianEntityT, _P], Coroutine[Any, Any, None]]: + """Decorate to handle exceptions from the Guardian API.""" + + @wraps(func) + async def wrapper( + entity: _GuardianEntityT, *args: _P.args, **kwargs: _P.kwargs + ) -> None: + """Wrap the provided function.""" + try: + await func(entity, *args, **kwargs) + except GuardianError as err: + raise HomeAssistantError( + f"Error while calling {func.__name__}: {err}" + ) from err + + return wrapper