mirror of
https://github.com/home-assistant/core.git
synced 2025-07-28 15:47:12 +00:00
Add translated action exceptions to Airgradient (#136322)
* Add translated action exceptions to Airgradient * Add translated action exceptions to Airgradient
This commit is contained in:
parent
d6f6961674
commit
40ed0562bc
@ -18,7 +18,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from . import AirGradientConfigEntry
|
||||
from .const import DOMAIN
|
||||
from .coordinator import AirGradientCoordinator
|
||||
from .entity import AirGradientEntity
|
||||
from .entity import AirGradientEntity, exception_handler
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
@ -102,6 +102,7 @@ class AirGradientButton(AirGradientEntity, ButtonEntity):
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"{coordinator.serial_number}-{description.key}"
|
||||
|
||||
@exception_handler
|
||||
async def async_press(self) -> None:
|
||||
"""Press the button."""
|
||||
await self.entity_description.press_fn(self.coordinator.client)
|
||||
|
@ -55,7 +55,11 @@ class AirGradientCoordinator(DataUpdateCoordinator[AirGradientData]):
|
||||
measures = await self.client.get_current_measures()
|
||||
config = await self.client.get_config()
|
||||
except AirGradientError as error:
|
||||
raise UpdateFailed(error) from error
|
||||
raise UpdateFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="update_error",
|
||||
translation_placeholders={"error": str(error)},
|
||||
) from error
|
||||
if measures.firmware_version != self._current_version:
|
||||
device_registry = dr.async_get(self.hass)
|
||||
device_entry = device_registry.async_get_device(
|
||||
|
@ -1,7 +1,11 @@
|
||||
"""Base class for AirGradient entities."""
|
||||
|
||||
from airgradient import get_model_name
|
||||
from collections.abc import Callable, Coroutine
|
||||
from typing import Any, Concatenate
|
||||
|
||||
from airgradient import AirGradientConnectionError, AirGradientError, get_model_name
|
||||
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
@ -26,3 +30,31 @@ class AirGradientEntity(CoordinatorEntity[AirGradientCoordinator]):
|
||||
serial_number=coordinator.serial_number,
|
||||
sw_version=measures.firmware_version,
|
||||
)
|
||||
|
||||
|
||||
def exception_handler[_EntityT: AirGradientEntity, **_P](
|
||||
func: Callable[Concatenate[_EntityT, _P], Coroutine[Any, Any, Any]],
|
||||
) -> Callable[Concatenate[_EntityT, _P], Coroutine[Any, Any, None]]:
|
||||
"""Decorate AirGradient calls to handle exceptions.
|
||||
|
||||
A decorator that wraps the passed in function, catches AirGradient errors.
|
||||
"""
|
||||
|
||||
async def handler(self: _EntityT, *args: _P.args, **kwargs: _P.kwargs) -> None:
|
||||
try:
|
||||
await func(self, *args, **kwargs)
|
||||
except AirGradientConnectionError as error:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="communication_error",
|
||||
translation_placeholders={"error": str(error)},
|
||||
) from error
|
||||
|
||||
except AirGradientError as error:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="unknown_error",
|
||||
translation_placeholders={"error": str(error)},
|
||||
) from error
|
||||
|
||||
return handler
|
||||
|
@ -19,7 +19,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from . import AirGradientConfigEntry
|
||||
from .const import DOMAIN
|
||||
from .coordinator import AirGradientCoordinator
|
||||
from .entity import AirGradientEntity
|
||||
from .entity import AirGradientEntity, exception_handler
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
@ -123,6 +123,7 @@ class AirGradientNumber(AirGradientEntity, NumberEntity):
|
||||
"""Return the state of the number."""
|
||||
return self.entity_description.value_fn(self.coordinator.data.config)
|
||||
|
||||
@exception_handler
|
||||
async def async_set_native_value(self, value: float) -> None:
|
||||
"""Set the selected value."""
|
||||
await self.entity_description.set_value_fn(self.coordinator.client, int(value))
|
||||
|
@ -29,7 +29,7 @@ rules:
|
||||
unique-config-entry: done
|
||||
|
||||
# Silver
|
||||
action-exceptions: todo
|
||||
action-exceptions: done
|
||||
config-entry-unloading: done
|
||||
docs-configuration-parameters:
|
||||
status: exempt
|
||||
@ -68,7 +68,7 @@ rules:
|
||||
entity-device-class: done
|
||||
entity-disabled-by-default: done
|
||||
entity-translations: done
|
||||
exception-translations: todo
|
||||
exception-translations: done
|
||||
icon-translations: done
|
||||
reconfiguration-flow: todo
|
||||
repair-issues:
|
||||
|
@ -19,7 +19,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from . import AirGradientConfigEntry
|
||||
from .const import DOMAIN, PM_STANDARD, PM_STANDARD_REVERSE
|
||||
from .coordinator import AirGradientCoordinator
|
||||
from .entity import AirGradientEntity
|
||||
from .entity import AirGradientEntity, exception_handler
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
@ -218,6 +218,7 @@ class AirGradientSelect(AirGradientEntity, SelectEntity):
|
||||
"""Return the state of the select."""
|
||||
return self.entity_description.value_fn(self.coordinator.data.config)
|
||||
|
||||
@exception_handler
|
||||
async def async_select_option(self, option: str) -> None:
|
||||
"""Change the selected option."""
|
||||
await self.entity_description.set_value_fn(self.coordinator.client, option)
|
||||
|
@ -165,5 +165,16 @@
|
||||
"name": "Post data to Airgradient"
|
||||
}
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"communication_error": {
|
||||
"message": "An error occurred while communicating with the Airgradient device: {error}"
|
||||
},
|
||||
"unknown_error": {
|
||||
"message": "An unknown error occurred while communicating with the Airgradient device: {error}"
|
||||
},
|
||||
"update_error": {
|
||||
"message": "An error occurred while communicating with the Airgradient device: {error}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from . import AirGradientConfigEntry
|
||||
from .const import DOMAIN
|
||||
from .coordinator import AirGradientCoordinator
|
||||
from .entity import AirGradientEntity
|
||||
from .entity import AirGradientEntity, exception_handler
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
@ -101,11 +101,13 @@ class AirGradientSwitch(AirGradientEntity, SwitchEntity):
|
||||
"""Return the state of the switch."""
|
||||
return self.entity_description.value_fn(self.coordinator.data.config)
|
||||
|
||||
@exception_handler
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the switch on."""
|
||||
await self.entity_description.set_value_fn(self.coordinator.client, True)
|
||||
await self.coordinator.async_request_refresh()
|
||||
|
||||
@exception_handler
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the switch off."""
|
||||
await self.entity_description.set_value_fn(self.coordinator.client, False)
|
||||
|
@ -3,14 +3,16 @@
|
||||
from datetime import timedelta
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from airgradient import Config
|
||||
from airgradient import AirGradientConnectionError, AirGradientError, Config
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.airgradient.const import DOMAIN
|
||||
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS
|
||||
from homeassistant.const import ATTR_ENTITY_ID, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from . import setup_integration
|
||||
@ -97,3 +99,37 @@ async def test_cloud_creates_no_button(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(hass.states.async_all()) == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("exception", "error_message"),
|
||||
[
|
||||
(
|
||||
AirGradientConnectionError("Something happened"),
|
||||
"An error occurred while communicating with the Airgradient device: Something happened",
|
||||
),
|
||||
(
|
||||
AirGradientError("Something else happened"),
|
||||
"An unknown error occurred while communicating with the Airgradient device: Something else happened",
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_exception_handling(
|
||||
hass: HomeAssistant,
|
||||
mock_airgradient_client: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
exception: Exception,
|
||||
error_message: str,
|
||||
) -> None:
|
||||
"""Test exception handling."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
mock_airgradient_client.request_co2_calibration.side_effect = exception
|
||||
with pytest.raises(HomeAssistantError, match=error_message):
|
||||
await hass.services.async_call(
|
||||
BUTTON_DOMAIN,
|
||||
SERVICE_PRESS,
|
||||
{
|
||||
ATTR_ENTITY_ID: "button.airgradient_calibrate_co2_sensor",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
@ -3,8 +3,9 @@
|
||||
from datetime import timedelta
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from airgradient import Config
|
||||
from airgradient import AirGradientConnectionError, AirGradientError, Config
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.airgradient.const import DOMAIN
|
||||
@ -15,6 +16,7 @@ from homeassistant.components.number import (
|
||||
)
|
||||
from homeassistant.const import ATTR_ENTITY_ID, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from . import setup_integration
|
||||
@ -99,3 +101,37 @@ async def test_cloud_creates_no_number(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(hass.states.async_all()) == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("exception", "error_message"),
|
||||
[
|
||||
(
|
||||
AirGradientConnectionError("Something happened"),
|
||||
"An error occurred while communicating with the Airgradient device: Something happened",
|
||||
),
|
||||
(
|
||||
AirGradientError("Something else happened"),
|
||||
"An unknown error occurred while communicating with the Airgradient device: Something else happened",
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_exception_handling(
|
||||
hass: HomeAssistant,
|
||||
mock_airgradient_client: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
exception: Exception,
|
||||
error_message: str,
|
||||
) -> None:
|
||||
"""Test exception handling."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
mock_airgradient_client.set_display_brightness.side_effect = exception
|
||||
with pytest.raises(HomeAssistantError, match=error_message):
|
||||
await hass.services.async_call(
|
||||
NUMBER_DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
service_data={ATTR_VALUE: 50},
|
||||
target={ATTR_ENTITY_ID: "number.airgradient_display_brightness"},
|
||||
blocking=True,
|
||||
)
|
||||
|
@ -3,7 +3,7 @@
|
||||
from datetime import timedelta
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from airgradient import Config
|
||||
from airgradient import AirGradientConnectionError, AirGradientError, Config
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
@ -15,6 +15,7 @@ from homeassistant.components.select import (
|
||||
)
|
||||
from homeassistant.const import ATTR_ENTITY_ID, ATTR_OPTION, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from . import setup_integration
|
||||
@ -94,3 +95,39 @@ async def test_cloud_creates_no_number(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(hass.states.async_all()) == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("exception", "error_message"),
|
||||
[
|
||||
(
|
||||
AirGradientConnectionError("Something happened"),
|
||||
"An error occurred while communicating with the Airgradient device: Something happened",
|
||||
),
|
||||
(
|
||||
AirGradientError("Something else happened"),
|
||||
"An unknown error occurred while communicating with the Airgradient device: Something else happened",
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_exception_handling(
|
||||
hass: HomeAssistant,
|
||||
mock_airgradient_client: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
exception: Exception,
|
||||
error_message: str,
|
||||
) -> None:
|
||||
"""Test exception handling."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
mock_airgradient_client.set_configuration_control.side_effect = exception
|
||||
with pytest.raises(HomeAssistantError, match=error_message):
|
||||
await hass.services.async_call(
|
||||
SELECT_DOMAIN,
|
||||
SERVICE_SELECT_OPTION,
|
||||
{
|
||||
ATTR_ENTITY_ID: "select.airgradient_configuration_source",
|
||||
ATTR_OPTION: "local",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
@ -3,8 +3,9 @@
|
||||
from datetime import timedelta
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from airgradient import Config
|
||||
from airgradient import AirGradientConnectionError, AirGradientError, Config
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.airgradient.const import DOMAIN
|
||||
@ -16,6 +17,7 @@ from homeassistant.const import (
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from . import setup_integration
|
||||
@ -99,3 +101,36 @@ async def test_cloud_creates_no_switch(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(hass.states.async_all()) == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("exception", "error_message"),
|
||||
[
|
||||
(
|
||||
AirGradientConnectionError("Something happened"),
|
||||
"An error occurred while communicating with the Airgradient device: Something happened",
|
||||
),
|
||||
(
|
||||
AirGradientError("Something else happened"),
|
||||
"An unknown error occurred while communicating with the Airgradient device: Something else happened",
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_exception_handling(
|
||||
hass: HomeAssistant,
|
||||
mock_airgradient_client: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
exception: Exception,
|
||||
error_message: str,
|
||||
) -> None:
|
||||
"""Test exception handling."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
mock_airgradient_client.enable_sharing_data.side_effect = exception
|
||||
with pytest.raises(HomeAssistantError, match=error_message):
|
||||
await hass.services.async_call(
|
||||
SWITCH_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
target={ATTR_ENTITY_ID: "switch.airgradient_post_data_to_airgradient"},
|
||||
blocking=True,
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user