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."""
|
"""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.device_registry import DeviceInfo
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
|
@ -23,3 +29,27 @@ class LetPotEntity(CoordinatorEntity[LetPotDeviceCoordinator]):
|
||||||
model_id=coordinator.device_client.device_model_code,
|
model_id=coordinator.device_client.device_model_code,
|
||||||
serial_number=coordinator.device.serial_number,
|
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
|
unique-config-entry: done
|
||||||
|
|
||||||
# Silver
|
# Silver
|
||||||
action-exceptions: todo
|
action-exceptions: done
|
||||||
config-entry-unloading:
|
config-entry-unloading:
|
||||||
status: done
|
status: done
|
||||||
comment: |
|
comment: |
|
||||||
|
@ -63,7 +63,7 @@ rules:
|
||||||
entity-device-class: todo
|
entity-device-class: todo
|
||||||
entity-disabled-by-default: todo
|
entity-disabled-by-default: todo
|
||||||
entity-translations: done
|
entity-translations: done
|
||||||
exception-translations: todo
|
exception-translations: done
|
||||||
icon-translations: todo
|
icon-translations: todo
|
||||||
reconfiguration-flow: todo
|
reconfiguration-flow: todo
|
||||||
repair-issues: todo
|
repair-issues: todo
|
||||||
|
|
|
@ -40,5 +40,13 @@
|
||||||
"name": "Light on"
|
"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 . import LetPotConfigEntry
|
||||||
from .coordinator import LetPotDeviceCoordinator
|
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
|
# 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.
|
# 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 the time."""
|
||||||
return self.entity_description.value_fn(self.coordinator.data)
|
return self.entity_description.value_fn(self.coordinator.data)
|
||||||
|
|
||||||
|
@exception_handler
|
||||||
async def async_set_value(self, value: time) -> None:
|
async def async_set_value(self, value: time) -> None:
|
||||||
"""Set the time."""
|
"""Set the time."""
|
||||||
await self.entity_description.set_value_fn(
|
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