Add exception handling for updating LetPot time entities (#137033)

* Handle exceptions for entity edits for LetPot

* Set exception-translations: done
pull/137037/head
Joris Pelgröm 2025-01-31 21:28:23 +01:00 committed by GitHub
parent 164d38ac0d
commit 7103ea7e8f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 94 additions and 3 deletions

View File

@ -1,5 +1,11 @@
"""Base class for LetPot entities."""
from collections.abc import Callable, Coroutine
from typing import Any, Concatenate
from letpot.exceptions import LetPotConnectionException, LetPotException
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
@ -23,3 +29,27 @@ class LetPotEntity(CoordinatorEntity[LetPotDeviceCoordinator]):
model_id=coordinator.device_client.device_model_code,
serial_number=coordinator.device.serial_number,
)
def exception_handler[_EntityT: LetPotEntity, **_P](
func: Callable[Concatenate[_EntityT, _P], Coroutine[Any, Any, Any]],
) -> Callable[Concatenate[_EntityT, _P], Coroutine[Any, Any, None]]:
"""Decorate the function to catch LetPot exceptions and raise them correctly."""
async def handler(self: _EntityT, *args: _P.args, **kwargs: _P.kwargs) -> None:
try:
await func(self, *args, **kwargs)
except LetPotConnectionException as exception:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="communication_error",
translation_placeholders={"exception": str(exception)},
) from exception
except LetPotException as exception:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="unknown_error",
translation_placeholders={"exception": str(exception)},
) from exception
return handler

View File

@ -29,7 +29,7 @@ rules:
unique-config-entry: done
# Silver
action-exceptions: todo
action-exceptions: done
config-entry-unloading:
status: done
comment: |
@ -63,7 +63,7 @@ rules:
entity-device-class: todo
entity-disabled-by-default: todo
entity-translations: done
exception-translations: todo
exception-translations: done
icon-translations: todo
reconfiguration-flow: todo
repair-issues: todo

View File

@ -40,5 +40,13 @@
"name": "Light on"
}
}
},
"exceptions": {
"communication_error": {
"message": "An error occurred while communicating with the LetPot device: {exception}"
},
"unknown_error": {
"message": "An unknown error occurred while communicating with the LetPot device: {exception}"
}
}
}

View File

@ -15,7 +15,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import LetPotConfigEntry
from .coordinator import LetPotDeviceCoordinator
from .entity import LetPotEntity
from .entity import LetPotEntity, exception_handler
# Each change pushes a 'full' device status with the change. The library will cache
# pending changes to avoid overwriting, but try to avoid a lot of parallelism.
@ -86,6 +86,7 @@ class LetPotTimeEntity(LetPotEntity, TimeEntity):
"""Return the time."""
return self.entity_description.value_fn(self.coordinator.data)
@exception_handler
async def async_set_value(self, value: time) -> None:
"""Set the time."""
await self.entity_description.set_value_fn(

View File

@ -0,0 +1,52 @@
"""Test time entities for the LetPot integration."""
from datetime import time
from unittest.mock import MagicMock
from letpot.exceptions import LetPotConnectionException, LetPotException
import pytest
from homeassistant.components.time import SERVICE_SET_VALUE
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from . import setup_integration
from tests.common import MockConfigEntry
@pytest.mark.parametrize(
("exception", "user_error"),
[
(
LetPotConnectionException("Connection failed"),
"An error occurred while communicating with the LetPot device: Connection failed",
),
(
LetPotException("Random thing failed"),
"An unknown error occurred while communicating with the LetPot device: Random thing failed",
),
],
)
async def test_time_error(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_client: MagicMock,
mock_device_client: MagicMock,
exception: Exception,
user_error: str,
) -> None:
"""Test time entity exception handling."""
await setup_integration(hass, mock_config_entry)
mock_device_client.set_light_schedule.side_effect = exception
assert hass.states.get("time.garden_light_on") is not None
with pytest.raises(HomeAssistantError, match=user_error):
await hass.services.async_call(
"time",
SERVICE_SET_VALUE,
service_data={"time": time(hour=7, minute=0)},
blocking=True,
target={"entity_id": "time.garden_light_on"},
)