Add exception handling for updating LetPot time entities (#137033)
* Handle exceptions for entity edits for LetPot * Set exception-translations: donepull/137037/head
parent
164d38ac0d
commit
7103ea7e8f
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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"},
|
||||
)
|
Loading…
Reference in New Issue